| // Copyright 2017 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 "garnet/drivers/bluetooth/lib/gatt/server.h" |
| |
| #include <lib/async/cpp/task.h> |
| |
| #include "garnet/drivers/bluetooth/lib/att/database.h" |
| #include "garnet/drivers/bluetooth/lib/common/test_helpers.h" |
| #include "garnet/drivers/bluetooth/lib/gatt/gatt_defs.h" |
| #include "garnet/drivers/bluetooth/lib/l2cap/fake_channel_test.h" |
| #include "lib/fxl/macros.h" |
| |
| namespace btlib { |
| namespace gatt { |
| namespace { |
| |
| using common::LowerBits; |
| using common::UpperBits; |
| |
| constexpr char kTestDeviceId[] = "11223344-1122-1122-1122-112233445566"; |
| constexpr common::UUID kTestType16((uint16_t)0xBEEF); |
| constexpr common::UUID kTestType128({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, |
| 13, 14, 15}); |
| |
| const auto kTestValue1 = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kTestValue2 = common::CreateStaticByteBuffer('b', 'a', 'r'); |
| const auto kTestValue3 = common::CreateStaticByteBuffer('b', 'a', 'z'); |
| const auto kTestValue4 = common::CreateStaticByteBuffer('l', 'o', 'l'); |
| |
| const auto kTestValueLong = common::CreateStaticByteBuffer('l', 'o', 'n', 'g'); |
| |
| inline att::AccessRequirements AllowedNoSecurity() { |
| return att::AccessRequirements(false, false, false); |
| } |
| |
| class GATT_ServerTest : public l2cap::testing::FakeChannelTest { |
| public: |
| GATT_ServerTest() = default; |
| ~GATT_ServerTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| db_ = att::Database::Create(); |
| |
| ChannelOptions options(l2cap::kATTChannelId); |
| auto fake_chan = CreateFakeChannel(options); |
| att_ = att::Bearer::Create(std::move(fake_chan)); |
| server_ = std::make_unique<Server>(kTestDeviceId, db_, att_); |
| } |
| |
| void TearDown() override { |
| server_ = nullptr; |
| att_ = nullptr; |
| db_ = nullptr; |
| } |
| |
| Server* server() const { return server_.get(); } |
| |
| att::Database* db() const { return db_.get(); } |
| |
| // TODO(armansito): Consider introducing a FakeBearer for testing (NET-318). |
| att::Bearer* att() const { return att_.get(); } |
| |
| private: |
| fxl::RefPtr<att::Database> db_; |
| fxl::RefPtr<att::Bearer> att_; |
| std::unique_ptr<Server> server_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(GATT_ServerTest); |
| }; |
| |
| TEST_F(GATT_ServerTest, ExchangeMTURequestInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x02); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x02, // request: exchange MTU |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ExchangeMTURequestValueTooSmall) { |
| const uint16_t kServerMTU = l2cap::kDefaultMTU; |
| constexpr uint16_t kClientMTU = 1; |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kClientMTU, 0x00 // client rx mtu: |kClientMTU| |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x03, // opcode: exchange MTU response |
| 0xA0, 0x02 // server rx mtu: |kServerMTU| |
| ); |
| // clang-format on |
| |
| ASSERT_EQ(kServerMTU, att()->preferred_mtu()); |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| |
| // Should default to kLEMinMTU since kClientMTU is too small. |
| EXPECT_EQ(att::kLEMinMTU, att()->mtu()); |
| } |
| |
| TEST_F(GATT_ServerTest, ExchangeMTURequest) { |
| constexpr uint16_t kServerMTU = l2cap::kDefaultMTU; |
| constexpr uint16_t kClientMTU = 0x64; |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kClientMTU, 0x00 // client rx mtu: |kClientMTU| |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x03, // opcode: exchange MTU response |
| 0xA0, 0x02 // server rx mtu: |kServerMTU| |
| ); |
| // clang-format on |
| |
| ASSERT_EQ(kServerMTU, att()->preferred_mtu()); |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| |
| EXPECT_EQ(kClientMTU, att()->mtu()); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInformationInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x04); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x04, // request: find information |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInformationInvalidHandle) { |
| // Start handle is 0 |
| // clang-format off |
| const auto kInvalidStartHandle = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information |
| 0x00, 0x00, // start: 0x0000 |
| 0xFF, 0xFF // end: 0xFFFF |
| ); |
| |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x04, // request: find information |
| 0x00, 0x00, // handle: 0x0000 (start handle in request) |
| 0x01 // error: Invalid handle |
| ); |
| |
| // End handle is smaller than start handle |
| const auto kInvalidEndHandle = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information |
| 0x02, 0x00, // start: 0x0002 |
| 0x01, 0x00 // end: 0x0001 |
| ); |
| |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x04, // request: find information |
| 0x02, 0x00, // handle: 0x0002 (start handle in request) |
| 0x01 // error: Invalid handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidStartHandle, kExpected1)); |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidEndHandle, kExpected2)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInformationAttributeNotFound) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF // end: 0xFFFF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x04, // request: find information |
| 0x01, 0x00, // handle: 0x0001 (start handle in request) |
| 0x0A // error: Attribute not found |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInformation16) { |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1); |
| grp->AddAttribute(kTestType16); |
| grp->AddAttribute(kTestType16); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF // end: 0xFFFF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit |
| 0x01, 0x00, // handle: 0x0001 |
| 0x00, 0x28, // uuid: primary service group type |
| 0x02, 0x00, // handle: 0x0002 |
| 0xEF, 0xBE, // uuid: 0xBEEF |
| 0x03, 0x00, // handle: 0x0003 |
| 0xEF, 0xBE // uuid: 0xBEEF |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInformation128) { |
| auto* grp = db()->NewGrouping(kTestType128, 0, kTestValue1); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF // end: 0xFFFF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x02, // format: 128-bit |
| 0x01, 0x00, // handle: 0x0001 |
| |
| // uuid: 0F0E0D0C-0B0A-0908-0706-050403020100 |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueSuccess) { |
| // handle: 1 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // handle: 2 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue2)->set_active(true); |
| |
| // handle: 3 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x06, // opcode: find by type value request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28, // uuid: primary service group type |
| 'f', 'o', 'o' // value: foo |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x07, // opcode: find by type value response |
| 0x01, 0x00, // handle: 0x0001 |
| 0x01, 0x00, // group handle: 0x0001 |
| 0x03, 0x00, // handle: 0x0003 |
| 0x03, 0x00 // group handle: 0x0003 |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueFail) { |
| // handle: 1 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x06, // opcode: find by type value request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28, // uuid: primary service group type |
| 'n', 'o' // value: no |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x06, // opcode: find by type value |
| 0x00, 0x00, // group handle: 0x0000 |
| 0x0a // Attribute Not Found |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueEmptyDB) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x06, // opcode: find by type value request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28, // uuid: primary service group type |
| 'n', 'o' // value: no |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x06, // opcode: find by type value |
| 0x00, 0x00, // group handle: 0x0000 |
| 0x0a // Attribute Not Found |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueInvalidHandle) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x06, // opcode: find by type value request |
| 0x02, 0x00, // start: 0x0002 |
| 0x01, 0x00, // end: 0x0001 |
| 0x00, 0x28, // uuid: primary service group type |
| 'n', 'o' // value: no |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x06, // opcode: find by type value |
| 0x00, 0x00, // group handle: 0x0000 |
| 0x01 // Invalid Handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueInvalidPDUError) { |
| // handle: 1 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x06); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x06, // opcode: find by type value |
| 0x00, 0x00, // group handle: 0x0000 |
| 0x04 // Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueZeroLengthValueError) { |
| // handle: 1 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x06, // opcode: find by type value request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // uuid: primary service group type |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x06, // opcode: find by type value |
| 0x00, 0x00, // group handle: 0x0000 |
| 0x0a // Attribute Not Found |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindByTypeValueOutsideRangeError) { |
| // handle: 1 (active) |
| auto* grp = db()->NewGrouping(kTestType16, 2, kTestValue2); |
| |
| // handle: 2 - value: "long" |
| grp->AddAttribute(kTestType16, AllowedNoSecurity())->SetValue(kTestValue2); |
| |
| // handle: 3 - value: "foo" |
| grp->AddAttribute(kTestType16, AllowedNoSecurity())->SetValue(kTestValue1); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x06, // opcode: find by type value request |
| 0x01, 0x00, // start: 0x0001 |
| 0x02, 0x00, // end: 0xFFFF |
| 0x00, 0x28, // uuid: primary service group type |
| 'f', 'o', 'o' // value: foo |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x06, // opcode: find by type value |
| 0x00, 0x00, // group handle: 0x0000 |
| 0x0a // Attribute Not Found |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInfomationInactive) { |
| // handle: 1 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // handle: 2, 3, 4 (inactive) |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1); |
| grp->AddAttribute(kTestType16); |
| grp->AddAttribute(kTestType16); |
| |
| // handle: 5 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information request |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF // end: 0xFFFF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit |
| 0x01, 0x00, // handle: 0x0001 |
| 0x00, 0x28, // uuid: primary service group type |
| 0x05, 0x00, // handle: 0x0005 |
| 0x00, 0x28 // uuid: primary service group type |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, FindInfomationRange) { |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1); |
| grp->AddAttribute(kTestType16); |
| grp->AddAttribute(kTestType16); |
| grp->set_active(true); |
| |
| // handle: 5 (active) |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x04, // opcode: find information request |
| 0x02, 0x00, // start: 0x0002 |
| 0x02, 0x00 // end: 0x0002 |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit |
| 0x02, 0x00, // handle: 0x0001 |
| 0xEF, 0xBE // uuid: 0xBEEF |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x10); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeUnsupportedGroupType) { |
| // 16-bit UUID |
| // clang-format off |
| const auto kUsing16BitType = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x01, 0x00 // group type: 1 (unsupported) |
| ); |
| |
| // 128-bit UUID |
| const auto kUsing128BitType = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| |
| // group type: 00112233-4455-6677-8899-AABBCCDDEEFF (unsupported) |
| 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, |
| 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x01, 0x00, // handle: 0x0001 (start handle in request) |
| 0x10 // error: Unsupported Group Type |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kUsing16BitType, kExpected)); |
| EXPECT_TRUE(ReceiveAndExpect(kUsing128BitType, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeInvalidHandle) { |
| // Start handle is 0 |
| // clang-format off |
| const auto kInvalidStartHandle = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x00, 0x00, // start: 0x0000 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x00, 0x00, // handle: 0x0000 (start handle in request) |
| 0x01 // error: Invalid handle |
| ); |
| |
| // End handle is smaller than start handle |
| const auto kInvalidEndHandle = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x02, 0x00, // start: 0x0002 |
| 0x01, 0x00, // end: 0x0001 |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x02, 0x00, // handle: 0x0002 (start handle in request) |
| 0x01 // error: Invalid handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidStartHandle, kExpected1)); |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidEndHandle, kExpected2)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeAttributeNotFound) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x01, 0x00, // handle: 0x0001 (start handle in request) |
| 0x0A // error: Attribute not found |
| ); |
| // clang-format on |
| |
| // Database is empty. |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| |
| // Group type does not match. |
| db()->NewGrouping(types::kSecondaryService, 0, kTestValue1)->set_active(true); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeSingle) { |
| const auto kTestValue = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| // Start: 1, end: 2 |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| grp->AddAttribute(common::UUID(), att::AccessRequirements(), |
| att::AccessRequirements()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x08, // length: 8 (strlen("test") + 4) |
| 0x01, 0x00, // start: 0x0001 |
| 0x02, 0x00, // end: 0x0002 |
| 't', 'e', 's', 't' // value: "test" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeSingle128) { |
| const auto kTestValue = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| // Start: 1, end: 2 |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| grp->AddAttribute(common::UUID(), att::AccessRequirements(), |
| att::AccessRequirements()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| |
| // group type: 00002800-0000-1000-8000-00805F9B34FB (primary service) |
| 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, |
| 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x08, // length: 8 (strlen("test") + 4) |
| 0x01, 0x00, // start: 0x0001 |
| 0x02, 0x00, // end: 0x0002 |
| 't', 'e', 's', 't' // value: "test" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeSingleTruncated) { |
| const auto kTestValue = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| // Start: 1, end: 1 |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 0, kTestValue); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x06, // length: 6 (strlen("te") + 4) |
| 0x01, 0x00, // start: 0x0001 |
| 0x01, 0x00, // end: 0x0001 |
| 't', 'e' // value: "te" |
| ); |
| // clang-format on |
| |
| // Force the MTU to exactly fit |kExpected| which partially contains |
| // |kTestValue|. |
| att()->set_mtu(kExpected.size()); |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeMultipleSameValueSize) { |
| // Start: 1, end: 1 |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // Start: 2, end: 2 |
| auto* grp2 = db()->NewGrouping(types::kPrimaryService, 0, kTestValue2); |
| grp2->set_active(true); |
| |
| // Start: 3, end: 3 |
| db()->NewGrouping(types::kSecondaryService, 0, kTestValue3)->set_active(true); |
| |
| // Start: 4, end: 4 |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue3)->set_active(true); |
| |
| // Start: 5, end: 5 |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue4)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest1 = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x07, // length: 7 (strlen("foo") + 4) |
| 0x01, 0x00, // start: 0x0001 |
| 0x01, 0x00, // end: 0x0001 |
| 'f', 'o', 'o', // value: "foo" |
| 0x02, 0x00, // start: 0x0002 |
| 0x02, 0x00, // end: 0x0002 |
| 'b', 'a', 'r', // value: "bar" |
| 0x04, 0x00, // start: 0x0004 |
| 0x04, 0x00, // end: 0x0004 |
| 'b', 'a', 'z' // value: "baz" |
| ); |
| // clang-format on |
| |
| // Set the MTU to be one byte too short to include the 5th attribute group. |
| // The 3rd group is omitted as its group type does not match. |
| att()->set_mtu(kExpected1.size() + 6); |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest1, kExpected1)); |
| |
| // Search a narrower range. Only two groups should be returned even with room |
| // in MTU. |
| // clang-format off |
| const auto kRequest2 = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x02, 0x00, // start: 0x0002 |
| 0x04, 0x00, // end: 0x0004 |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x07, // length: 7 (strlen("foo") + 4) |
| 0x02, 0x00, // start: 0x0002 |
| 0x02, 0x00, // end: 0x0002 |
| 'b', 'a', 'r', // value: "bar" |
| 0x04, 0x00, // start: 0x0004 |
| 0x04, 0x00, // end: 0x0004 |
| 'b', 'a', 'z' // value: "baz" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected2)); |
| |
| // Make the second group inactive. It should get omitted. |
| // clang-format off |
| const auto kExpected3 = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x07, // length: 7 (strlen("foo") + 4) |
| 0x04, 0x00, // start: 0x0004 |
| 0x04, 0x00, // end: 0x0004 |
| 'b', 'a', 'z' // value: "baz" |
| ); |
| // clang-format on |
| |
| grp2->set_active(false); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected3)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByGroupTypeMultipleVaryingLengths) { |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // Matching type but value of different size. The results will stop here. |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValueLong) |
| ->set_active(true); |
| |
| // Matching type and matching value length. This won't be included as the |
| // request will terminate at the second attribute. |
| db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true); |
| |
| // clang-format off |
| const auto kRequest2 = common::CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x08, // length: 8 (strlen("long") + 4) |
| 0x01, 0x00, // start: 0x0001 |
| 0x01, 0x00, // end: 0x0001 |
| 'l', 'o', 'n', 'g' // value: "bar" |
| ); |
| // clang-format on |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x08); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeInvalidHandle) { |
| // Start handle is 0 |
| // clang-format off |
| const auto kInvalidStartHandle = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x00, 0x00, // start: 0x0000 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x00, 0x00, // handle: 0x0000 (start handle in request) |
| 0x01 // error: Invalid handle |
| ); |
| |
| // End handle is smaller than start handle |
| const auto kInvalidEndHandle = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x02, 0x00, // start: 0x0002 |
| 0x01, 0x00, // end: 0x0001 |
| 0x00, 0x28 // group type: 0x2800 (primary service) |
| ); |
| |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x02, 0x00, // handle: 0x0002 (start handle in request) |
| 0x01 // error: Invalid handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidStartHandle, kExpected1)); |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidEndHandle, kExpected2)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeAttributeNotFound) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x01, 0x00, // handle: 0x0001 (start handle in request) |
| 0x0A // error: Attribute not found |
| ); |
| // clang-format on |
| |
| // Database is empty. |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| |
| // Attribute type does not match. |
| db()->NewGrouping(types::kSecondaryService, 0, kTestValue1)->set_active(true); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeDynamicValueNoHandler) { |
| const auto kTestValue = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x02, 0x00, // handle: 0x0002 (the attribute causing the error) |
| 0x02 // error: Read not permitted |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeDynamicValue) { |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity()); |
| attr->set_read_handler([attr](const auto& peer_id, auto handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(0u, offset); |
| result_cb(att::ErrorCode::kNoError, |
| common::CreateStaticByteBuffer('f', 'o', 'r', 'k')); |
| }); |
| |
| // Add a second dynamic attribute, which should be omitted. |
| attr = grp->AddAttribute(kTestType16, AllowedNoSecurity()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x06, // length: 6 (strlen("fork") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 'f', 'o', 'r', 'k' // value: "fork" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| |
| // Assign a static value to the second attribute. It should still be omitted |
| // as the first attribute is dynamic. |
| attr->SetValue(kTestValue1); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeDynamicValueError) { |
| const auto kTestValue = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| attr->set_read_handler([](const auto& peer_id, auto handle, uint16_t offset, |
| const auto& result_cb) { |
| result_cb(att::ErrorCode::kUnlikelyError, common::BufferView()); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x02, 0x00, // handle: 0x0002 (the attribute causing the error) |
| 0x0E // error: Unlikely error |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeSingle) { |
| const auto kTestValue1 = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kTestValue2 = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1); |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), att::AccessRequirements()) |
| ->SetValue(kTestValue2); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x06, // length: 6 (strlen("test") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 't', 'e', 's', 't' // value: "test" |
| ); |
| |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeSingle128) { |
| const auto kTestValue1 = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kTestValue2 = common::CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1); |
| grp->AddAttribute(kTestType128, AllowedNoSecurity(), |
| att::AccessRequirements()) |
| ->SetValue(kTestValue2); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| |
| // type: 0F0E0D0C-0B0A-0908-0706-050403020100 |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x06, // length: 6 (strlen("test") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 't', 'e', 's', 't' // value: "test" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeSingleTruncated) { |
| const auto kVeryLongValue = common::CreateStaticByteBuffer( |
| 't', 'e', 's', 't', 'i', 'n', 'g', ' ', 'i', 's', ' ', 'f', 'u', 'n'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1); |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), att::AccessRequirements()) |
| ->SetValue(kVeryLongValue); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("tes") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 't', 'e', 's' // value: "tes" |
| ); |
| // clang-format on |
| |
| // Force the MTU to exactly fit |kExpected| which partially contains |
| // |kTestValue2| (the packet is crafted so that both |kRequest| and |
| // |kExpected| fit within the MTU). |
| att()->set_mtu(kExpected.size()); |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| // When there are more than one matching attributes, the list should end at the |
| // first attribute that causes an error. |
| TEST_F(GATT_ServerTest, ReadByTypeMultipleExcludeFirstError) { |
| // handle 1: readable |
| auto* grp = db()->NewGrouping(kTestType16, 1, kTestValue1); |
| |
| // handle 2: not readable. |
| grp->AddAttribute(kTestType16)->SetValue(kTestValue1); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadByTypeMultipleSameValueSize) { |
| // handle: 1, value: foo |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1); |
| |
| // handle: 2, value: foo |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), att::AccessRequirements()) |
| ->SetValue(kTestValue1); |
| |
| // handle: 3, value: bar |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), att::AccessRequirements()) |
| ->SetValue(kTestValue2); |
| grp->set_active(true); |
| |
| // handle: 4, value: foo (new grouping) |
| grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1); |
| |
| // handle: 5, value: baz |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), att::AccessRequirements()) |
| ->SetValue(kTestValue3); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest1 = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 'f', 'o', 'o', // value: "foo" |
| 0x03, 0x00, // handle: 0x0003 |
| 'b', 'a', 'r', // value: "bar" |
| 0x05, 0x00, // handle: 0x0005 |
| 'b', 'a', 'z' // value: "baz" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest1, kExpected1)); |
| |
| // Set the MTU 1 byte too short for |kExpected1|. |
| att()->set_mtu(kExpected1.size() - 1); |
| |
| // clang-format off |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 'f', 'o', 'o', // value: "foo" |
| 0x03, 0x00, // handle: 0x0003 |
| 'b', 'a', 'r' // value: "bar" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest1, kExpected2)); |
| |
| // Try a different range. |
| // clang-format off |
| const auto kRequest2 = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x03, 0x00, // start: 0x0003 |
| 0x05, 0x00, // end: 0x0005 |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| |
| const auto kExpected3 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("bar") + 2) |
| 0x03, 0x00, // handle: 0x0003 |
| 'b', 'a', 'r', // value: "bar" |
| 0x05, 0x00, // handle: 0x0005 |
| 'b', 'a', 'z' // value: "baz" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected3)); |
| |
| // Make the second group inactive. |
| grp->set_active(false); |
| |
| // clang-format off |
| const auto kExpected4 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("bar") + 2) |
| 0x03, 0x00, // handle: 0x0003 |
| 'b', 'a', 'r' // value: "bar" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected4)); |
| } |
| |
| // A response packet should only include consecutive attributes with the same |
| // value size. |
| TEST_F(GATT_ServerTest, ReadByTypeMultipleVaryingLengths) { |
| // handle: 1 - value: "foo" |
| auto* grp = db()->NewGrouping(kTestType16, 2, kTestValue1); |
| |
| // handle: 2 - value: "long" |
| grp->AddAttribute(kTestType16, AllowedNoSecurity())->SetValue(kTestValueLong); |
| |
| // handle: 3 - value: "foo" |
| grp->AddAttribute(kTestType16, AllowedNoSecurity())->SetValue(kTestValue1); |
| grp->set_active(true); |
| |
| // Even though we have 3 attributes with a matching type, the requests below |
| // will always return one attribute at a time as their values have different |
| // sizes. |
| |
| // clang-format off |
| const auto kRequest1 = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| const auto kRequest2 = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x02, 0x00, // start: 0x0002 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x06, // length: 6 (strlen("long") + 2) |
| 0x02, 0x00, // handle: 0x0002 |
| 'l', 'o', 'n', 'g' // value: "long" |
| ); |
| const auto kRequest3 = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x03, 0x00, // start: 0x0003 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| const auto kExpected3 = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| 0x03, 0x00, // handle: 0x0003 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest1, kExpected1)); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected2)); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest3, kExpected3)); |
| } |
| |
| // When there are more than one matching attributes, the list should end at the |
| // first attribute with a dynamic value. |
| TEST_F(GATT_ServerTest, ReadByTypeMultipleExcludeFirstDynamic) { |
| // handle: 1 - value: "foo" |
| auto* grp = db()->NewGrouping(kTestType16, 1, kTestValue1); |
| |
| // handle: 2 - value: dynamic |
| grp->AddAttribute(kTestType16, AllowedNoSecurity()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| 0x01, 0x00, // start: 0x0001 |
| 0xFF, 0xFF, // end: 0xFFFF |
| 0xEF, 0xBE // type: 0xBEEF |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, WriteRequestInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x12); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, WriteRequestInvalidHandle) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| |
| // value: "test" |
| 't', 'e', 's', 't'); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x01 // error: invalid handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, WriteRequestSecurity) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| |
| // Requires encryption |
| grp->AddAttribute(kTestType16, att::AccessRequirements(), |
| att::AccessRequirements(true, false, false)); |
| grp->set_active(true); |
| |
| // We send two write requests: |
| // 1. 0x0001: not writable |
| // 2. 0x0002: writable but requires encryption |
| // |
| // clang-format off |
| const auto kRequest1 = common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| |
| // value: "test" |
| 't', 'e', 's', 't'); |
| |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x03 // error: write not permitted |
| ); |
| const auto kRequest2 = common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| 0x02, 0x00, // handle: 0x0002 |
| |
| // value: "test" |
| 't', 'e', 's', 't'); |
| |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x05 // error: insufficient authentication |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest1, kExpected1)); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected2)); |
| } |
| |
| TEST_F(GATT_ServerTest, WriteRequestNoHandler) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| |
| grp->AddAttribute(kTestType16, att::AccessRequirements(), |
| AllowedNoSecurity()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| 0x02, 0x00, // handle: 0x0002 |
| |
| // value: "test" |
| 't', 'e', 's', 't'); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x03 // error: write not permitted |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, WriteRequestError) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, att::AccessRequirements(), |
| AllowedNoSecurity()); |
| |
| attr->set_write_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& value, |
| const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(0u, offset); |
| EXPECT_TRUE(common::ContainersEqual( |
| common::CreateStaticByteBuffer('t', 'e', 's', 't'), value)); |
| |
| result_cb(att::ErrorCode::kUnlikelyError); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| 0x02, 0x00, // handle: 0x0002 |
| |
| // value: "test" |
| 't', 'e', 's', 't'); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x0E // error: unlikely error |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, WriteRequestSuccess) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, att::AccessRequirements(), |
| AllowedNoSecurity()); |
| |
| attr->set_write_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& value, |
| const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(0u, offset); |
| EXPECT_TRUE(common::ContainersEqual( |
| common::CreateStaticByteBuffer('t', 'e', 's', 't'), value)); |
| |
| result_cb(att::ErrorCode::kNoError); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| 0x02, 0x00, // handle: 0x0002 |
| |
| // value: "test" |
| 't', 'e', 's', 't'); |
| // clang-format on |
| |
| // opcode: write response |
| const auto kExpected = common::CreateStaticByteBuffer(0x13); |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| // TODO(bwb): Add test cases for the error conditions involved in a Write |
| // Command (NET-387) |
| |
| TEST_F(GATT_ServerTest, WriteCommandSuccess) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, att::AccessRequirements(), |
| AllowedNoSecurity()); |
| |
| attr->set_write_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& value, |
| const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(0u, offset); |
| EXPECT_TRUE(common::ContainersEqual( |
| common::CreateStaticByteBuffer('t', 'e', 's', 't'), value)); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kCmd = common::CreateStaticByteBuffer( |
| 0x52, // opcode: write command |
| 0x02, 0x00, // handle: 0x0002 |
| 't', 'e', 's', 't'); |
| // clang-format on |
| |
| fake_chan()->Receive(kCmd); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x0A); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0A, // request: read request |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestInvalidHandle) { |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x01, 0x00 // handle: 0x0001 |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0A, // request: read request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x01 // error: invalid handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestSecurity) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| |
| // Requires encryption |
| grp->AddAttribute(kTestType16, att::AccessRequirements(true, false, false), |
| att::AccessRequirements()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x02, 0x00 // handle: 0x0002 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0A, // request: read request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x05 // error: insufficient authentication |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestCached) { |
| const auto kDeclValue = common::CreateStaticByteBuffer('d', 'e', 'c', 'l'); |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kDeclValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| attr->SetValue(kTestValue); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest1 = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x01, 0x00 // handle: 0x0001 |
| ); |
| const auto kExpected1 = common::CreateStaticByteBuffer( |
| 0x0B, // opcode: read response |
| 'd', 'e', 'c', 'l' // value: kDeclValue |
| ); |
| const auto kRequest2 = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x02, 0x00 // handle: 0x0002 |
| ); |
| const auto kExpected2 = common::CreateStaticByteBuffer( |
| 0x0B, // opcode: read response |
| 'f', 'o', 'o' // value: kTestValue |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest1, kExpected1)); |
| EXPECT_TRUE(ReceiveAndExpect(kRequest2, kExpected2)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestNoHandler) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| |
| grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x02, 0x00 // handle: 0x0002 |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0A, // request: read request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x02 // error: read not permitted |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestError) { |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| attr->set_read_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(0u, offset); |
| |
| result_cb(att::ErrorCode::kUnlikelyError, common::BufferView()); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x02, 0x00 // handle: 0x0002 |
| ); |
| |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0A, // request: read request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x0E // error: unlikely error |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestInvalidPDU) { |
| // Just opcode |
| // clang-format off |
| const auto kInvalidPDU = common::CreateStaticByteBuffer(0x0C); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0C, // request: read blob request |
| 0x00, 0x00, // handle: 0 |
| 0x04 // error: Invalid PDU |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kInvalidPDU, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestDynamicSuccess) { |
| const auto kDeclValue = common::CreateStaticByteBuffer('d', 'e', 'c', 'l'); |
| const auto kTestValue = common::CreateStaticByteBuffer( |
| 'A', ' ', 'V', 'e', 'r', 'y', ' ', 'L', 'o', 'n', 'g', ' ', 'D', 'e', 'v', |
| 'i', 'c', 'e', ' ', 'N', 'a', 'm', 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', |
| 'A', ' ', 'L', 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u', 't', |
| 'e'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| |
| attr->set_read_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(22u, offset); |
| result_cb(att::ErrorCode::kNoError, |
| common::CreateStaticByteBuffer( |
| 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', 'A', ' ', 'L', 'o', |
| 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u')); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x16, 0x00 // offset: 0x0016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x0D, // opcode: read blob response |
| // Read Request response |
| 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', 'A', ' ', 'L', |
| 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u' |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobDynamicRequestError) { |
| const auto kTestValue = common::CreateStaticByteBuffer( |
| 'A', ' ', 'V', 'e', 'r', 'y', ' ', 'L', 'o', 'n', 'g', ' ', 'D', 'e', 'v', |
| 'i', 'c', 'e', ' ', 'N', 'a', 'm', 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', |
| 'A', ' ', 'L', 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u', 't', |
| 'e'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| attr->set_read_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| |
| result_cb(att::ErrorCode::kUnlikelyError, common::BufferView()); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x16, 0x00 // offset: 0x0016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0C, // request: read by type |
| 0x02, 0x00, // handle: 0x0002 (the attribute causing the error) |
| 0x0E // error: Unlikely error |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestStaticSuccess) { |
| const auto kTestValue = common::CreateStaticByteBuffer( |
| 'A', ' ', 'V', 'e', 'r', 'y', ' ', 'L', 'o', 'n', 'g', ' ', 'D', 'e', 'v', |
| 'i', 'c', 'e', ' ', 'N', 'a', 'm', 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', |
| 'A', ' ', 'L', 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u', 't', |
| 'e'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 0, kTestValue); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x01, 0x00, // handle: 0x0002 |
| 0x16, 0x00 // offset: 0x0016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x0D, // opcode: read blob response |
| // Read Request response |
| 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', 'A', ' ', 'L', |
| 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u' |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestStaticOverflowError) { |
| const auto kTestValue = |
| common::CreateStaticByteBuffer('s', 'h', 'o', 'r', 't', 'e', 'r'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 0, kTestValue); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x16, 0x10 // offset: 0x1016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // Error |
| 0x0C, // opcode |
| 0x01, 0x00, // handle: 0x0001 |
| 0x07 // InvalidOffset |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestInvalidHandleError) { |
| const auto kTestValue = common::CreateStaticByteBuffer( |
| 'A', ' ', 'V', 'e', 'r', 'y', ' ', 'L', 'o', 'n', 'g', ' ', 'D', 'e', 'v', |
| 'i', 'c', 'e', ' ', 'N', 'a', 'm', 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', |
| 'A', ' ', 'L', 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u', 't', |
| 'e'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 0, kTestValue); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x02, 0x30, // handle: 0x0002 |
| 0x16, 0x00 // offset: 0x0016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0C, // request: read blob request |
| 0x02, 0x30, // handle: 0x0001 |
| 0x01 // error: invalid handle |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestNotPermitedError) { |
| const auto kTestValue = common::CreateStaticByteBuffer( |
| 'A', ' ', 'V', 'e', 'r', 'y', ' ', 'L', 'o', 'n', 'g', ' ', 'D', 'e', 'v', |
| 'i', 'c', 'e', ' ', 'N', 'a', 'm', 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', |
| 'A', ' ', 'L', 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u', 't', |
| 'e'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, att::AccessRequirements(), |
| att::AccessRequirements(true, false, false)); |
| attr->set_read_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| |
| result_cb(att::ErrorCode::kUnlikelyError, common::BufferView()); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x16, 0x00 // offset: 0x0016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0C, // request: read by type |
| 0x02, 0x00, // handle: 0x0002 (the attribute causing the error) |
| 0x02 // error: Not Permitted |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadBlobRequestInvalidOffsetError) { |
| const auto kTestValue = common::CreateStaticByteBuffer( |
| 'A', ' ', 'V', 'e', 'r', 'y', ' ', 'L', 'o', 'n', 'g', ' ', 'D', 'e', 'v', |
| 'i', 'c', 'e', ' ', 'N', 'a', 'm', 'e', ' ', 'U', 's', 'i', 'n', 'g', ' ', |
| 'A', ' ', 'L', 'o', 'n', 'g', ' ', 'A', 't', 't', 'r', 'i', 'b', 'u', 't', |
| 'e'); |
| |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| attr->set_read_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| |
| result_cb(att::ErrorCode::kInvalidOffset, common::BufferView()); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob request |
| 0x02, 0x00, // handle: 0x0002 |
| 0x16, 0x40 // offset: 0x4016 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0C, // request: read by type |
| 0x02, 0x00, // handle: 0x0002 (the attribute causing the error) |
| 0x07 // error: Invalid Offset Error |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, ReadRequestSuccess) { |
| const auto kDeclValue = common::CreateStaticByteBuffer('d', 'e', 'c', 'l'); |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue); |
| auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity(), |
| att::AccessRequirements()); |
| attr->set_read_handler([&](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& result_cb) { |
| EXPECT_EQ(kTestDeviceId, peer_id); |
| EXPECT_EQ(attr->handle(), handle); |
| EXPECT_EQ(0u, offset); |
| |
| result_cb(att::ErrorCode::kNoError, kTestValue); |
| }); |
| grp->set_active(true); |
| |
| // clang-format off |
| const auto kRequest = common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| 0x02, 0x00 // handle: 0x0002 |
| ); |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x0B, // opcode: read response |
| 'f', 'o', 'o' // value: kTestValue |
| ); |
| // clang-format on |
| |
| EXPECT_TRUE(ReceiveAndExpect(kRequest, kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, SendNotificationEmpty) { |
| constexpr att::Handle kHandle = 0x1234; |
| const common::BufferView kTestValue; |
| |
| // clang-format off |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x1B, // opcode: notification |
| 0x34, 0x12 // handle: |kHandle| |
| ); |
| // clang-format on |
| |
| async::PostTask(dispatcher(), [=] { |
| server()->SendNotification(kHandle, kTestValue, false); |
| }); |
| |
| EXPECT_TRUE(Expect(kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, SendNotification) { |
| constexpr att::Handle kHandle = 0x1234; |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| |
| // clang-format off |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x1B, // opcode: notification |
| 0x34, 0x12, // handle: |kHandle| |
| 'f', 'o', 'o' // value: |kTestValue| |
| ); |
| // clang-format on |
| |
| async::PostTask(dispatcher(), [=] { |
| server()->SendNotification(kHandle, kTestValue, false); |
| }); |
| |
| EXPECT_TRUE(Expect(kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, SendIndicationEmpty) { |
| constexpr att::Handle kHandle = 0x1234; |
| const common::BufferView kTestValue; |
| |
| // clang-format off |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x1D, // opcode: indication |
| 0x34, 0x12 // handle: |kHandle| |
| ); |
| // clang-format on |
| |
| async::PostTask(dispatcher(), [=] { |
| server()->SendNotification(kHandle, kTestValue, true); |
| }); |
| |
| EXPECT_TRUE(Expect(kExpected)); |
| } |
| |
| TEST_F(GATT_ServerTest, SendIndication) { |
| constexpr att::Handle kHandle = 0x1234; |
| const auto kTestValue = common::CreateStaticByteBuffer('f', 'o', 'o'); |
| |
| // clang-format off |
| const auto kExpected = common::CreateStaticByteBuffer( |
| 0x1D, // opcode: indication |
| 0x34, 0x12, // handle: |kHandle| |
| 'f', 'o', 'o' // value: |kTestValue| |
| ); |
| // clang-format on |
| |
| async::PostTask(dispatcher(), [=] { |
| server()->SendNotification(kHandle, kTestValue, true); |
| }); |
| |
| EXPECT_TRUE(Expect(kExpected)); |
| } |
| |
| class GATT_ServerTest_Security : public GATT_ServerTest { |
| protected: |
| void InitializeAttributesForReading() { |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 4, kTestValue1); |
| |
| const att::AccessRequirements encryption(true, false, false); |
| const att::AccessRequirements authentication(false, true, false); |
| const att::AccessRequirements authorization(false, false, true); |
| |
| not_permitted_attr_ = grp->AddAttribute(kTestType16); |
| encryption_required_attr_ = grp->AddAttribute(kTestType16, encryption); |
| authentication_required_attr_ = |
| grp->AddAttribute(kTestType16, authentication); |
| authorization_required_attr_ = |
| grp->AddAttribute(kTestType16, authorization); |
| |
| // Assigns all tests attributes a static value. Intended to be used by read |
| // requests. (Note: assigning a static value makes an attribute |
| // non-writable). All attributes are assigned kTestValue1 as their static |
| // value. |
| not_permitted_attr_->SetValue(kTestValue1); |
| encryption_required_attr_->SetValue(kTestValue1); |
| authentication_required_attr_->SetValue(kTestValue1); |
| authorization_required_attr_->SetValue(kTestValue1); |
| |
| grp->set_active(true); |
| } |
| |
| void InitializeAttributesForWriting() { |
| auto* grp = db()->NewGrouping(types::kPrimaryService, 4, kTestValue1); |
| |
| const att::AccessRequirements encryption(true, false, false); |
| const att::AccessRequirements authentication(false, true, false); |
| const att::AccessRequirements authorization(false, false, true); |
| |
| auto write_handler = [this](const auto&, att::Handle, uint16_t, |
| const auto& value, auto responder) { |
| write_count_++; |
| if (responder) { |
| responder(att::ErrorCode::kNoError); |
| } |
| }; |
| |
| not_permitted_attr_ = grp->AddAttribute(kTestType16); |
| not_permitted_attr_->set_write_handler(write_handler); |
| |
| encryption_required_attr_ = |
| grp->AddAttribute(kTestType16, att::AccessRequirements(), encryption); |
| encryption_required_attr_->set_write_handler(write_handler); |
| |
| authentication_required_attr_ = grp->AddAttribute( |
| kTestType16, att::AccessRequirements(), authentication); |
| authentication_required_attr_->set_write_handler(write_handler); |
| |
| authorization_required_attr_ = grp->AddAttribute( |
| kTestType16, att::AccessRequirements(), authorization); |
| authorization_required_attr_->set_write_handler(write_handler); |
| |
| grp->set_active(true); |
| } |
| |
| // Blocks until an ATT Error Response PDU with the given parameters is |
| // received from the fake channel (i.e. received FROM the ATT bearer). |
| bool ExpectAttError(att::OpCode request, att::Handle handle, |
| att::ErrorCode ecode) { |
| return Expect(common::CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| request, // request opcode |
| LowerBits(handle), UpperBits(handle), // handle |
| ecode // error code |
| )); |
| } |
| |
| // Helpers for emulating the receipt of an ATT read/write request PDU and |
| // expecting back a security error. Expects a valid response if |
| // |expected_ecode| is att::ErrorCode::kNoError. |
| bool EmulateReadByTypeRequest(att::Handle handle, |
| att::ErrorCode expected_ecode) { |
| fake_chan()->Receive(common::CreateStaticByteBuffer( |
| 0x08, // opcode: read by type |
| LowerBits(handle), UpperBits(handle), // start handle |
| LowerBits(handle), UpperBits(handle), // end handle |
| 0xEF, 0xBE // type: 0xBEEF, i.e. kTestType16 |
| )); |
| if (expected_ecode == att::ErrorCode::kNoError) { |
| return Expect(common::CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x05, // length: 5 (strlen("foo") + 2) |
| LowerBits(handle), UpperBits(handle), // handle |
| 'f', 'o', 'o' // value: "foo", i.e. kTestValue1 |
| )); |
| } else { |
| return ExpectAttError(0x08, handle, expected_ecode); |
| } |
| } |
| |
| bool EmulateReadBlobRequest(att::Handle handle, |
| att::ErrorCode expected_ecode) { |
| fake_chan()->Receive(common::CreateStaticByteBuffer( |
| 0x0C, // opcode: read blob |
| LowerBits(handle), UpperBits(handle), // handle |
| 0x00, 0x00 // offset: 0 |
| )); |
| if (expected_ecode == att::ErrorCode::kNoError) { |
| return Expect(common::CreateStaticByteBuffer( |
| 0x0D, // opcode: read blob response |
| 'f', 'o', 'o' // value: "foo", i.e. kTestValue1 |
| )); |
| } else { |
| return ExpectAttError(0x0C, handle, expected_ecode); |
| } |
| } |
| |
| bool EmulateReadRequest(att::Handle handle, att::ErrorCode expected_ecode) { |
| fake_chan()->Receive(common::CreateStaticByteBuffer( |
| 0x0A, // opcode: read request |
| LowerBits(handle), UpperBits(handle) // handle |
| )); |
| if (expected_ecode == att::ErrorCode::kNoError) { |
| return Expect(common::CreateStaticByteBuffer( |
| 0x0B, // opcode: read response |
| 'f', 'o', 'o' // value: "foo", i.e. kTestValue1 |
| )); |
| } else { |
| return ExpectAttError(0x0A, handle, expected_ecode); |
| } |
| } |
| |
| bool EmulateWriteRequest(att::Handle handle, att::ErrorCode expected_ecode) { |
| fake_chan()->Receive(common::CreateStaticByteBuffer( |
| 0x12, // opcode: write request |
| LowerBits(handle), UpperBits(handle), // handle |
| 't', 'e', 's', 't' // value: "test" |
| )); |
| if (expected_ecode == att::ErrorCode::kNoError) { |
| return Expect(common::CreateStaticByteBuffer(0x13 // write response |
| )); |
| } else { |
| return ExpectAttError(0x12, handle, expected_ecode); |
| } |
| } |
| |
| // Emulates the receipt of a Write Command. The expected error code parameter |
| // is unused since ATT commands do not have a response. |
| bool EmulateWriteCommand(att::Handle handle, att::ErrorCode) { |
| fake_chan()->Receive(common::CreateStaticByteBuffer( |
| 0x52, // opcode: write command |
| LowerBits(handle), UpperBits(handle), // handle |
| 't', 'e', 's', 't' // value: "test" |
| )); |
| RunLoopUntilIdle(); |
| return true; |
| } |
| |
| template <bool (GATT_ServerTest_Security::*EmulateMethod)(att::Handle, |
| att::ErrorCode), |
| bool IsWrite> |
| void RunTest() { |
| const att::ErrorCode kNotPermittedError = |
| IsWrite ? att::ErrorCode::kWriteNotPermitted |
| : att::ErrorCode::kReadNotPermitted; |
| |
| // No security. |
| EXPECT_TRUE((this->*EmulateMethod)(not_permitted_attr()->handle(), |
| kNotPermittedError)); |
| EXPECT_TRUE( |
| (this->*EmulateMethod)(encryption_required_attr()->handle(), |
| att::ErrorCode::kInsufficientAuthentication)); |
| EXPECT_TRUE( |
| (this->*EmulateMethod)(authentication_required_attr()->handle(), |
| att::ErrorCode::kInsufficientAuthentication)); |
| EXPECT_TRUE( |
| (this->*EmulateMethod)(authorization_required_attr()->handle(), |
| att::ErrorCode::kInsufficientAuthentication)); |
| |
| // Link encrypted. |
| fake_chan()->set_security( |
| sm::SecurityProperties(sm::SecurityLevel::kEncrypted, 16, false)); |
| EXPECT_TRUE((this->*EmulateMethod)(not_permitted_attr()->handle(), |
| kNotPermittedError)); |
| EXPECT_TRUE((this->*EmulateMethod)(encryption_required_attr()->handle(), |
| att::ErrorCode::kNoError)); // success |
| EXPECT_TRUE( |
| (this->*EmulateMethod)(authentication_required_attr()->handle(), |
| att::ErrorCode::kInsufficientAuthentication)); |
| EXPECT_TRUE( |
| (this->*EmulateMethod)(authorization_required_attr()->handle(), |
| att::ErrorCode::kInsufficientAuthentication)); |
| |
| // Link encrypted w/ MITM. |
| fake_chan()->set_security( |
| sm::SecurityProperties(sm::SecurityLevel::kAuthenticated, 16, false)); |
| EXPECT_TRUE((this->*EmulateMethod)(not_permitted_attr()->handle(), |
| kNotPermittedError)); |
| EXPECT_TRUE((this->*EmulateMethod)(encryption_required_attr()->handle(), |
| att::ErrorCode::kNoError)); // success |
| EXPECT_TRUE((this->*EmulateMethod)(authentication_required_attr()->handle(), |
| att::ErrorCode::kNoError)); // success |
| EXPECT_TRUE((this->*EmulateMethod)(authorization_required_attr()->handle(), |
| att::ErrorCode::kNoError)); // success |
| } |
| |
| void RunReadByTypeTest() { |
| RunTest<&GATT_ServerTest_Security::EmulateReadByTypeRequest, false>(); |
| } |
| void RunReadBlobTest() { |
| RunTest<&GATT_ServerTest_Security::EmulateReadBlobRequest, false>(); |
| } |
| void RunReadRequestTest() { |
| RunTest<&GATT_ServerTest_Security::EmulateReadRequest, false>(); |
| } |
| void RunWriteRequestTest() { |
| RunTest<&GATT_ServerTest_Security::EmulateWriteRequest, true>(); |
| } |
| void RunWriteCommandTest() { |
| RunTest<&GATT_ServerTest_Security::EmulateWriteCommand, true>(); |
| } |
| |
| const att::Attribute* not_permitted_attr() const { |
| return not_permitted_attr_; |
| } |
| const att::Attribute* encryption_required_attr() const { |
| return encryption_required_attr_; |
| } |
| const att::Attribute* authentication_required_attr() const { |
| return authentication_required_attr_; |
| } |
| const att::Attribute* authorization_required_attr() const { |
| return authorization_required_attr_; |
| } |
| |
| size_t write_count() const { return write_count_; } |
| |
| private: |
| att::Attribute* not_permitted_attr_ = nullptr; |
| att::Attribute* encryption_required_attr_ = nullptr; |
| att::Attribute* authentication_required_attr_ = nullptr; |
| att::Attribute* authorization_required_attr_ = nullptr; |
| |
| size_t write_count_ = 0u; |
| }; |
| |
| // Tests receiving a Read By Type error under 3 possible link security levels. |
| TEST_F(GATT_ServerTest_Security, ReadByTypeErrorSecurity) { |
| InitializeAttributesForReading(); |
| RunReadByTypeTest(); |
| } |
| |
| TEST_F(GATT_ServerTest_Security, ReadBlobErrorSecurity) { |
| InitializeAttributesForReading(); |
| RunReadBlobTest(); |
| } |
| |
| TEST_F(GATT_ServerTest_Security, ReadErrorSecurity) { |
| InitializeAttributesForReading(); |
| RunReadRequestTest(); |
| } |
| |
| TEST_F(GATT_ServerTest_Security, WriteErrorSecurity) { |
| InitializeAttributesForWriting(); |
| RunWriteRequestTest(); |
| |
| // Only 4 writes should have gone through. |
| EXPECT_EQ(4u, write_count()); |
| } |
| |
| TEST_F(GATT_ServerTest_Security, WriteCommandErrorSecurity) { |
| InitializeAttributesForWriting(); |
| RunWriteCommandTest(); |
| |
| // Only 4 writes should have gone through. |
| EXPECT_EQ(4u, write_count()); |
| } |
| |
| } // namespace |
| } // namespace gatt |
| } // namespace btlib |