// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_bluetooth_sapphire/internal/host/gatt/server.h"

#include "pw_bluetooth_sapphire/internal/host/att/att.h"
#include "pw_bluetooth_sapphire/internal/host/att/attribute.h"
#include "pw_bluetooth_sapphire/internal/host/att/database.h"
#include "pw_bluetooth_sapphire/internal/host/common/macros.h"
#include "pw_bluetooth_sapphire/internal/host/common/uuid.h"
#include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
#include "pw_bluetooth_sapphire/internal/host/gatt/local_service_manager.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/mock_channel_test.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"

#pragma clang diagnostic ignored "-Wshadow"

namespace bt::gatt {
namespace {

constexpr UUID kTestSvcType(uint16_t{0xDEAD});
constexpr UUID kTestChrcType(uint16_t{0xFEED});
constexpr IdType kTestChrcId{0xFADE};
constexpr PeerId kTestPeerId(1);
constexpr UUID kTestType16(uint16_t{0xBEEF});
constexpr UUID kTestType128(
    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});

const StaticByteBuffer kTestValue1('f', 'o', 'o');
const StaticByteBuffer kTestValue2('b', 'a', 'r');
const StaticByteBuffer kTestValue3('b', 'a', 'z');
const StaticByteBuffer kTestValue4('l', 'o', 'l');

const StaticByteBuffer kTestValueLong('l', 'o', 'n', 'g');

inline att::AccessRequirements AllowedNoSecurity() {
  return att::AccessRequirements(/*encryption=*/false,
                                 /*authentication=*/false,
                                 /*authorization=*/false);
}

class ServerTest : public l2cap::testing::MockChannelTest {
 public:
  ServerTest() = default;
  ~ServerTest() override = default;

 protected:
  void SetUp() override {
    local_services_ = std::make_unique<LocalServiceManager>();

    ChannelOptions options(l2cap::kATTChannelId);
    fake_att_chan_ = CreateFakeChannel(options);
    att_ = att::Bearer::Create(fake_att_chan_->GetWeakPtr(), dispatcher());
    server_ = gatt::Server::Create(
        kTestPeerId, local_services_->GetWeakPtr(), att_->GetWeakPtr());
  }

  void TearDown() override {
    RunUntilIdle();
    server_ = nullptr;
    att_ = nullptr;
    fake_att_chan_ = l2cap::testing::FakeChannel::WeakPtr();
    local_services_ = nullptr;
  }

  // Registers a service with UUID |svc_type| containing a single characteristic
  // of UUID |chrc_type| and represented by |chrc_id|. The characteristic
  // supports the indicate and notify properties, but has not configured them
  // via the CCC. Returns the ID of the registered service.
  IdType RegisterSvcWithSingleChrc(UUID svc_type,
                                   IdType chrc_id,
                                   UUID chrc_type) {
    ServicePtr svc = std::make_unique<Service>(/*primary=*/true, svc_type);
    CharacteristicPtr chr = std::make_unique<Characteristic>(
        chrc_id,
        chrc_type,
        Property::kIndicate | Property::kNotify,
        /*extended_properties=*/0u,
        /*read_permission=*/att::AccessRequirements(),
        /*write_permissions=*/att::AccessRequirements(),
        /*update_permisisons=*/AllowedNoSecurity());
    svc->AddCharacteristic(std::move(chr));
    return local_services_->RegisterService(
        std::move(svc), NopReadHandler, NopWriteHandler, NopCCCallback);
  }

  struct SvcIdAndChrcHandle {
    IdType svc_id;
    att::Handle chrc_val_handle;
  };
  // RegisterSvcWithSingleChrc, but the CCC is configured to |ccc_val| for
  // kTestPeerId. Returns the ID of the service alongside the handle of the
  // registered characteristic value.
  SvcIdAndChrcHandle RegisterSvcWithConfiguredChrc(
      UUID svc_type,
      IdType chrc_id,
      UUID chrc_type,
      uint16_t ccc_val = kCCCIndicationBit | kCCCNotificationBit) {
    IdType svc_id = RegisterSvcWithSingleChrc(svc_type, chrc_id, chrc_type);
    std::vector<att::Handle> chrc_val_handle =
        SetCCCs(kTestPeerId, chrc_type, ccc_val);
    EXPECT_EQ(1u, chrc_val_handle.size());
    return SvcIdAndChrcHandle{.svc_id = svc_id,
                              .chrc_val_handle = chrc_val_handle[0]};
  }
  Server* server() const { return server_.get(); }

  att::Database::WeakPtr db() const { return local_services_->database(); }

  // TODO(armansito): Consider introducing a FakeBearer for testing
  // (fxbug.dev/42142784).
  att::Bearer* att() const { return att_.get(); }

 private:
  enum CCCSearchState {
    kSearching,
    kChrcDeclarationFound,
    kCorrectChrcUuidFound,
  };
  // Sets |local_services_|' CCCs for |peer_id| to |ccc_val| for all
  // characteristics of |chrc_type|, and returns the handles of the
  // characteristic values for which the CCC was modified.
  std::vector<att::Handle> SetCCCs(PeerId peer_id,
                                   bt::UUID chrc_type,
                                   uint16_t ccc_val) {
    std::vector<att::Handle> modified_attrs;
    CCCSearchState state = kSearching;
    for (auto& grouping : db()->groupings()) {
      att::Handle matching_chrc_value_handle = att::kInvalidHandle;
      for (auto& attr : grouping.attributes()) {
        if (attr.type() == types::kCharacteristicDeclaration) {
          EXPECT_NE(state, kChrcDeclarationFound)
              << "unexpectedly found two consecutive characteristic "
                 "declarations";
          state = kChrcDeclarationFound;
        } else if (state == kChrcDeclarationFound && attr.type() == chrc_type) {
          state = kCorrectChrcUuidFound;
          matching_chrc_value_handle = attr.handle();
        } else if (state == kChrcDeclarationFound) {
          state = kSearching;
        } else if (state == kCorrectChrcUuidFound &&
                   attr.type() == types::kClientCharacteristicConfig) {
          BT_ASSERT(matching_chrc_value_handle != att::kInvalidHandle);
          DynamicByteBuffer new_ccc(sizeof(ccc_val));
          new_ccc.WriteObj(ccc_val);
          fit::result<att::ErrorCode> write_status =
              fit::error(att::ErrorCode::kReadNotPermitted);
          EXPECT_TRUE(attr.WriteAsync(peer_id,
                                      /*offset=*/0,
                                      new_ccc,
                                      [&](fit::result<att::ErrorCode> status) {
                                        write_status = status;
                                      }));
          // Not strictly necessary with the current WriteAsync implementation,
          // but running the loop here makes this more future-proof.
          RunUntilIdle();
          EXPECT_EQ(fit::ok(), write_status);
          modified_attrs.push_back(matching_chrc_value_handle);
        }
      }
    }
    EXPECT_NE(0u, modified_attrs.size()) << "Couldn't find CCC attribute!";
    return modified_attrs;
  }

  std::unique_ptr<LocalServiceManager> local_services_;
  l2cap::testing::FakeChannel::WeakPtr fake_att_chan_;
  std::unique_ptr<att::Bearer> att_;
  std::unique_ptr<Server> server_;

  BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ServerTest);
};

TEST_F(ServerTest, ExchangeMTURequestInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x02);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x02,        // request: exchange MTU
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, ExchangeMTURequestValueTooSmall) {
  const uint16_t kServerMTU = att::kLEMaxMTU;
  constexpr uint16_t kClientMTU = 1;

  // clang-format off
  const StaticByteBuffer kRequest(
    0x02,             // opcode: exchange MTU
    kClientMTU, 0x00  // client rx mtu: |kClientMTU|
  );

  const StaticByteBuffer kExpected(
    0x03,       // opcode: exchange MTU response
    0xF7, 0x00  // server rx mtu: |kServerMTU|
  );
  // clang-format on

  ASSERT_EQ(kServerMTU, att()->preferred_mtu());

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
  // Should default to kLEMinMTU since kClientMTU is too small.
  EXPECT_EQ(att::kLEMinMTU, att()->mtu());
}

TEST_F(ServerTest, ExchangeMTURequest) {
  constexpr uint16_t kServerMTU = att::kLEMaxMTU;
  constexpr uint16_t kClientMTU = 0x64;

  // clang-format off
  const StaticByteBuffer kRequest(
    0x02,             // opcode: exchange MTU
    kClientMTU, 0x00  // client rx mtu: |kClientMTU|
  );

  const StaticByteBuffer kExpected(
    0x03,       // opcode: exchange MTU response
    0xF7, 0x00  // server rx mtu: |kServerMTU|
  );
  // clang-format on

  ASSERT_EQ(kServerMTU, att()->preferred_mtu());

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
  EXPECT_EQ(kClientMTU, att()->mtu());
}

TEST_F(ServerTest, FindInformationInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x04);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x04,        // request: find information
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, FindInformationInvalidHandle) {
  // Start handle is 0
  // clang-format off
  const StaticByteBuffer kInvalidStartHandle(
      0x04,        // opcode: find information
      0x00, 0x00,  // start: 0x0000
      0xFF, 0xFF   // end: 0xFFFF
  );

  const StaticByteBuffer kExpected1(
      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 StaticByteBuffer kInvalidEndHandle(
      0x04,        // opcode: find information
      0x02, 0x00,  // start: 0x0002
      0x01, 0x00   // end: 0x0001
  );

  const StaticByteBuffer kExpected2(
      0x01,        // opcode: error response
      0x04,        // request: find information
      0x02, 0x00,  // handle: 0x0002 (start handle in request)
      0x01         // error: Invalid handle
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kInvalidStartHandle);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kInvalidEndHandle);
}

TEST_F(ServerTest, FindInformationAttributeNotFound) {
  // clang-format off
  const StaticByteBuffer kRequest(
      0x04,        // opcode: find information request
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF   // end: 0xFFFF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, FindInformation16) {
  auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1);
  grp->AddAttribute(kTestType16);
  grp->AddAttribute(kTestType16);
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x04,        // opcode: find information request
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF   // end: 0xFFFF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, FindInformation128) {
  auto* grp = db()->NewGrouping(kTestType128, 0, kTestValue1);
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x04,        // opcode: find information request
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF   // end: 0xFFFF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(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 StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, FindByTypeValueFail) {
  // handle: 1 (active)
  db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      0x01,          // Error
      0x06,          // opcode: find by type value
      0x00, 0x00,    // group handle: 0x0000
      0x0a           // Attribute Not Found
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, FindByTypeValueEmptyDB) {
  // clang-format off
  const StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      0x01,          // Error
      0x06,          // opcode: find by type value
      0x00, 0x00,    // group handle: 0x0000
      0x0a           // Attribute Not Found
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, FindByTypeValueInvalidHandle) {
  // clang-format off
  const StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      0x01,          // Error
      0x06,          // opcode: find by type value
      0x00, 0x00,    // group handle: 0x0000
      0x01           // Invalid Handle
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, FindByTypeValueInvalidPDUError) {
  // handle: 1 (active)
  db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true);

  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x06);

  const StaticByteBuffer kExpected(
      0x01,          // Error
      0x06,          // opcode: find by type value
      0x00, 0x00,    // group handle: 0x0000
      0x04           // Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, FindByTypeValueZeroLengthValueError) {
  // handle: 1 (active)
  db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x06,          // opcode: find by type value request
      0x01, 0x00,    // start: 0x0001
      0xFF, 0xFF,    // end: 0xFFFF
      0x00, 0x28     // uuid: primary service group type
  );

  const StaticByteBuffer kExpected(
      0x01,          // Error
      0x06,          // opcode: find by type value
      0x00, 0x00,    // group handle: 0x0000
      0x0a           // Attribute Not Found
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(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 StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      0x01,          // Error
      0x06,          // opcode: find by type value
      0x00, 0x00,    // group handle: 0x0000
      0x0a           // Attribute Not Found
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(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 StaticByteBuffer kRequest(
      0x04,        // opcode: find information request
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF   // end: 0xFFFF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(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 StaticByteBuffer kRequest(
      0x04,        // opcode: find information request
      0x02, 0x00,  // start: 0x0002
      0x02, 0x00   // end: 0x0002
  );

  const StaticByteBuffer kExpected(
      0x05,        // opcode: find information response
      0x01,        // format: 16-bit
      0x02, 0x00,  // handle: 0x0001
      0xEF, 0xBE   // uuid: 0xBEEF
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByGroupTypeInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x10);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x10,        // request: read by group type
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, ReadByGroupTypeUnsupportedGroupType) {
  // 16-bit UUID
  // clang-format off
  const StaticByteBuffer kUsing16BitType(
      0x10,        // opcode: read by group type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0x01, 0x00   // group type: 1 (unsupported)
  );

  // 128-bit UUID
  const StaticByteBuffer kUsing128BitType(
      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 StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kUsing16BitType);
  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kUsing128BitType);
}

TEST_F(ServerTest, ReadByGroupTypeInvalidHandle) {
  // Start handle is 0
  // clang-format off
  const StaticByteBuffer kInvalidStartHandle(
      0x10,        // opcode: read by group type
      0x00, 0x00,  // start: 0x0000
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected1(
      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 StaticByteBuffer kInvalidEndHandle(
      0x10,        // opcode: read by group type
      0x02, 0x00,  // start: 0x0002
      0x01, 0x00,  // end: 0x0001
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected2(
      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_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kInvalidStartHandle);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kInvalidEndHandle);
}

TEST_F(ServerTest, ReadByGroupTypeAttributeNotFound) {
  // clang-format off
  const StaticByteBuffer kRequest(
      0x10,        // opcode: read by group type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);

  // Group type does not match.
  db()->NewGrouping(types::kSecondaryService, 0, kTestValue1)->set_active(true);
  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByGroupTypeSingle) {
  const StaticByteBuffer kTestValue('t', 'e', 's', 't');

  // Start: 1, end: 2
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  grp->AddAttribute(
      UUID(), att::AccessRequirements(), att::AccessRequirements());
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x10,        // opcode: read by group type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByGroupTypeSingle128) {
  const StaticByteBuffer kTestValue('t', 'e', 's', 't');

  // Start: 1, end: 2
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  grp->AddAttribute(
      UUID(), att::AccessRequirements(), att::AccessRequirements());
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByGroupTypeSingleTruncated) {
  const StaticByteBuffer kTestValue('t', 'e', 's', 't');

  // Start: 1, end: 1
  auto* grp = db()->NewGrouping(types::kPrimaryService, 0, kTestValue);
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x10,        // opcode: read by group type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(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 StaticByteBuffer kRequest1(
      0x10,        // opcode: read by group type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected1(
      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_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kRequest1);

  // Search a narrower range. Only two groups should be returned even with room
  // in MTU.
  // clang-format off
  const StaticByteBuffer kRequest2(
      0x10,        // opcode: read by group type
      0x02, 0x00,  // start: 0x0002
      0x04, 0x00,  // end: 0x0004
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected2(
      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_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kRequest2);

  // Make the second group inactive. It should get omitted.
  // clang-format off
  const StaticByteBuffer kExpected3(
      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_PACKET_OUT(kExpected3);
  fake_chan()->Receive(kRequest2);
}

// The responses should only include 1 value because the next value has a
// different length.
TEST_F(ServerTest, ReadByGroupTypeMultipleVaryingLengths) {
  // Start: 1, end: 1
  db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true);
  // Start: 2, end: 2
  db()->NewGrouping(types::kPrimaryService, 0, kTestValueLong)
      ->set_active(true);
  // Start: 3, end: 3
  db()->NewGrouping(types::kPrimaryService, 0, kTestValue1)->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest1(
      0x10,        // opcode: read by group type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected1(
      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"
  );

  const StaticByteBuffer kRequest2(
      0x10,        // opcode: read by group type
      0x02, 0x00,  // start: 0x0002
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected2(
      0x11,               // opcode: read by group type response
      0x08,               // length: 8 (strlen("long") + 4)
      0x02, 0x00,         // start: 0x0002
      0x02, 0x00,         // end: 0x0002
      'l', 'o', 'n', 'g'  // value
  );

  const StaticByteBuffer kRequest3(
      0x10,        // opcode: read by group type
      0x03, 0x00,  // start: 0x0003
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected3(
      0x11,           // opcode: read by group type response
      0x07,           // length: 7 (strlen("foo") + 4)
      0x03, 0x00,     // start: 0x0003
      0x03, 0x00,     // end: 0x0003
      'f', 'o', 'o'  // value: "foo"
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kRequest1);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kRequest2);
  EXPECT_PACKET_OUT(kExpected3);
  fake_chan()->Receive(kRequest3);
}

TEST_F(ServerTest, ReadByTypeInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x08);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x08,        // request: read by type
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, ReadByTypeInvalidHandle) {
  // Start handle is 0
  // clang-format off
  const StaticByteBuffer kInvalidStartHandle(
      0x08,        // opcode: read by type
      0x00, 0x00,  // start: 0x0000
      0xFF, 0xFF,  // end: 0xFFFF
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected1(
      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 StaticByteBuffer kInvalidEndHandle(
      0x08,        // opcode: read by type
      0x02, 0x00,  // start: 0x0002
      0x01, 0x00,  // end: 0x0001
      0x00, 0x28   // group type: 0x2800 (primary service)
  );

  const StaticByteBuffer kExpected2(
      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_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kInvalidStartHandle);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kInvalidEndHandle);
}

TEST_F(ServerTest, ReadByTypeAttributeNotFound) {
  // clang-format off
  const StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);

  // Attribute type does not match.
  db()->NewGrouping(types::kSecondaryService, 0, kTestValue1)->set_active(true);
  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByTypeDynamicValueNoHandler) {
  const StaticByteBuffer kTestValue('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 StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByTypeDynamicValue) {
  auto* grp = db()->NewGrouping(types::kPrimaryService, 2, kTestValue1);
  auto* attr = grp->AddAttribute(kTestType16, AllowedNoSecurity());
  attr->set_read_handler(
      [attr](PeerId peer_id, auto handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(attr->handle(), handle);
        EXPECT_EQ(0u, offset);
        result_cb(fit::ok(), StaticByteBuffer('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 StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);

  // Assign a static value to the second attribute. It should still be omitted
  // as the first attribute is dynamic.
  attr->SetValue(kTestValue1);
  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByTypeDynamicValueError) {
  const StaticByteBuffer kTestValue('t', 'e', 's', 't');

  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  auto* attr = grp->AddAttribute(
      kTestType16, AllowedNoSecurity(), att::AccessRequirements());
  attr->set_read_handler(
      [](PeerId peer_id, auto handle, uint16_t offset, auto result_cb) {
        result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
      });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByTypeSingle) {
  const StaticByteBuffer kTestValue1('f', 'o', 'o');
  const StaticByteBuffer kTestValue2('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 StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByTypeSingle128) {
  const StaticByteBuffer kTestValue1('f', 'o', 'o');
  const StaticByteBuffer kTestValue2('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 StaticByteBuffer kRequest(
      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 StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadByTypeSingleTruncated) {
  const StaticByteBuffer kVeryLongValue(
      '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 StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

// When there are more than one matching attributes, the list should end at the
// first attribute that causes an error.
TEST_F(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 StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(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 StaticByteBuffer kRequest1(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected1(
      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_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kRequest1);

  // Set the MTU 1 byte too short for |kExpected1|.
  att()->set_mtu(kExpected1.size() - 1);

  // clang-format off
  const StaticByteBuffer kExpected2(
      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_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kRequest1);

  // Try a different range.
  // clang-format off
  const StaticByteBuffer kRequest2(
      0x08,        // opcode: read by type
      0x03, 0x00,  // start: 0x0003
      0x05, 0x00,  // end: 0x0005
      0xEF, 0xBE   // type: 0xBEEF
  );

  const StaticByteBuffer kExpected3(
      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_PACKET_OUT(kExpected3);
  fake_chan()->Receive(kRequest2);

  // Make the second group inactive.
  grp->set_active(false);

  // clang-format off
  const StaticByteBuffer kExpected4(
      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_PACKET_OUT(kExpected4);
  fake_chan()->Receive(kRequest2);
}

// A response packet should only include consecutive attributes with the same
// value size.
TEST_F(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 StaticByteBuffer kRequest1(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );
  const StaticByteBuffer kExpected1(
      0x09,          // opcode: read by type response
      0x05,          // length: 5 (strlen("foo") + 2)
      0x01, 0x00,    // handle: 0x0001
      'f', 'o', 'o'  // value: "foo"
  );
  const StaticByteBuffer kRequest2(
      0x08,        // opcode: read by type
      0x02, 0x00,  // start: 0x0002
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );
  const StaticByteBuffer kExpected2(
      0x09,               // opcode: read by type response
      0x06,               // length: 6 (strlen("long") + 2)
      0x02, 0x00,         // handle: 0x0002
      'l', 'o', 'n', 'g'  // value: "long"
  );
  const StaticByteBuffer kRequest3(
      0x08,        // opcode: read by type
      0x03, 0x00,  // start: 0x0003
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );
  const StaticByteBuffer kExpected3(
      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_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kRequest1);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kRequest2);
  EXPECT_PACKET_OUT(kExpected3);
  fake_chan()->Receive(kRequest3);
}

// When there are more than one matching attributes, the list should end at the
// first attribute with a dynamic value.
TEST_F(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 StaticByteBuffer kRequest(
      0x08,        // opcode: read by type
      0x01, 0x00,  // start: 0x0001
      0xFF, 0xFF,  // end: 0xFFFF
      0xEF, 0xBE   // type: 0xBEEF
  );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, WriteRequestInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x12);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x12,        // request: write request
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, WriteRequestInvalidHandle) {
  // clang-format off
  const StaticByteBuffer kRequest(
      0x12,        // opcode: write request
      0x01, 0x00,  // handle: 0x0001

      // value: "test"
      't', 'e', 's', 't');

  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x12,        // request: write request
      0x01, 0x00,  // handle: 0x0001
      0x01         // error: invalid handle
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, WriteRequestSecurity) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);

  // Requires encryption
  grp->AddAttribute(kTestType16,
                    att::AccessRequirements(),
                    att::AccessRequirements(/*encryption=*/true,
                                            /*authentication=*/false,
                                            /*authorization=*/false));
  grp->set_active(true);

  // We send two write requests:
  //   1. 0x0001: not writable
  //   2. 0x0002: writable but requires encryption
  //
  // clang-format off
  const StaticByteBuffer kRequest1(
      0x12,        // opcode: write request
      0x01, 0x00,  // handle: 0x0001

      // value: "test"
      't', 'e', 's', 't');

  const StaticByteBuffer kExpected1(
      0x01,        // opcode: error response
      0x12,        // request: write request
      0x01, 0x00,  // handle: 0x0001
      0x03         // error: write not permitted
  );
  const StaticByteBuffer kRequest2(
      0x12,        // opcode: write request
      0x02, 0x00,  // handle: 0x0002

      // value: "test"
      't', 'e', 's', 't');

  const StaticByteBuffer kExpected2(
      0x01,        // opcode: error response
      0x12,        // request: write request
      0x02, 0x00,  // handle: 0x0002
      0x05         // error: insufficient authentication
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kRequest1);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kRequest2);
}

TEST_F(ServerTest, WriteRequestNoHandler) {
  const StaticByteBuffer kTestValue('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 StaticByteBuffer kRequest(
      0x12,        // opcode: write request
      0x02, 0x00,  // handle: 0x0002

      // value: "test"
      't', 'e', 's', 't');

  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x12,        // request: write request
      0x02, 0x00,  // handle: 0x0002
      0x03         // error: write not permitted
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, WriteRequestError) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  auto* attr = grp->AddAttribute(
      kTestType16, att::AccessRequirements(), AllowedNoSecurity());

  attr->set_write_handler([&](PeerId peer_id,
                              att::Handle handle,
                              uint16_t offset,
                              const auto& value,
                              auto result_cb) {
    EXPECT_EQ(kTestPeerId, peer_id);
    EXPECT_EQ(attr->handle(), handle);
    EXPECT_EQ(0u, offset);
    EXPECT_TRUE(ContainersEqual(StaticByteBuffer('t', 'e', 's', 't'), value));

    result_cb(fit::error(att::ErrorCode::kUnlikelyError));
  });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x12,        // opcode: write request
      0x02, 0x00,  // handle: 0x0002

      // value: "test"
      't', 'e', 's', 't');

  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x12,        // request: write request
      0x02, 0x00,  // handle: 0x0002
      0x0E         // error: unlikely error
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, WriteRequestSuccess) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  auto* attr = grp->AddAttribute(
      kTestType16, att::AccessRequirements(), AllowedNoSecurity());

  attr->set_write_handler([&](PeerId peer_id,
                              att::Handle handle,
                              uint16_t offset,
                              const auto& value,
                              auto result_cb) {
    EXPECT_EQ(kTestPeerId, peer_id);
    EXPECT_EQ(attr->handle(), handle);
    EXPECT_EQ(0u, offset);
    EXPECT_TRUE(ContainersEqual(StaticByteBuffer('t', 'e', 's', 't'), value));

    result_cb(fit::ok());
  });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x12,        // opcode: write request
      0x02, 0x00,  // handle: 0x0002

      // value: "test"
      't', 'e', 's', 't');
  // clang-format on

  // opcode: write response
  const StaticByteBuffer kExpected(0x13);

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

// TODO(bwb): Add test cases for the error conditions involved in a Write
// Command (fxbug.dev/42146420)

TEST_F(ServerTest, WriteCommandSuccess) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  auto* attr = grp->AddAttribute(
      kTestType16, att::AccessRequirements(), AllowedNoSecurity());

  attr->set_write_handler([&](PeerId peer_id,
                              att::Handle handle,
                              uint16_t offset,
                              const auto& value,
                              const auto& result_cb) {
    EXPECT_EQ(kTestPeerId, peer_id);
    EXPECT_EQ(attr->handle(), handle);
    EXPECT_EQ(0u, offset);
    EXPECT_TRUE(ContainersEqual(StaticByteBuffer('t', 'e', 's', 't'), value));
  });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kCmd(
      0x52,        // opcode: write command
      0x02, 0x00,  // handle: 0x0002
      't', 'e', 's', 't');
  // clang-format on

  fake_chan()->Receive(kCmd);
}

TEST_F(ServerTest, ReadRequestInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x0A);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0A,        // request: read request
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, ReadRequestInvalidHandle) {
  // clang-format off
  const StaticByteBuffer kRequest(
      0x0A,       // opcode: read request
      0x01, 0x00  // handle: 0x0001
  );

  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0A,        // request: read request
      0x01, 0x00,  // handle: 0x0001
      0x01         // error: invalid handle
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadRequestSecurity) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);

  // Requires encryption
  grp->AddAttribute(kTestType16,
                    att::AccessRequirements(/*encryption=*/true,
                                            /*authentication=*/false,
                                            /*authorization=*/false),
                    att::AccessRequirements());
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0A,       // opcode: read request
      0x02, 0x00  // handle: 0x0002
  );
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0A,        // request: read request
      0x02, 0x00,  // handle: 0x0002
      0x05         // error: insufficient authentication
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadRequestCached) {
  const StaticByteBuffer kDeclValue('d', 'e', 'c', 'l');
  const StaticByteBuffer kTestValue('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 StaticByteBuffer kRequest1(
      0x0A,       // opcode: read request
      0x01, 0x00  // handle: 0x0001
  );
  const StaticByteBuffer kExpected1(
      0x0B,               // opcode: read response
      'd', 'e', 'c', 'l'  // value: kDeclValue
  );
  const StaticByteBuffer kRequest2(
      0x0A,       // opcode: read request
      0x02, 0x00  // handle: 0x0002
  );
  const StaticByteBuffer kExpected2(
      0x0B,          // opcode: read response
      'f', 'o', 'o'  // value: kTestValue
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected1);
  fake_chan()->Receive(kRequest1);
  EXPECT_PACKET_OUT(kExpected2);
  fake_chan()->Receive(kRequest2);
}

TEST_F(ServerTest, ReadRequestNoHandler) {
  const StaticByteBuffer kTestValue('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 StaticByteBuffer kRequest(
      0x0A,       // opcode: read request
      0x02, 0x00  // handle: 0x0002
  );

  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0A,        // request: read request
      0x02, 0x00,  // handle: 0x0002
      0x02         // error: read not permitted
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadRequestError) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  auto* attr = grp->AddAttribute(
      kTestType16, AllowedNoSecurity(), att::AccessRequirements());
  attr->set_read_handler(
      [&](PeerId peer_id, att::Handle handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(kTestPeerId, peer_id);
        EXPECT_EQ(attr->handle(), handle);
        EXPECT_EQ(0u, offset);

        result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
      });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0A,       // opcode: read request
      0x02, 0x00  // handle: 0x0002
  );

  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0A,        // request: read request
      0x02, 0x00,  // handle: 0x0002
      0x0E         // error: unlikely error
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobRequestInvalidPDU) {
  // Just opcode
  // clang-format off
  const StaticByteBuffer kInvalidPDU(0x0C);
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0C,        // request: read blob request
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, ReadBlobRequestDynamicSuccess) {
  const StaticByteBuffer kDeclValue('d', 'e', 'c', 'l');
  const StaticByteBuffer kTestValue('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(
      [&](PeerId peer_id, att::Handle handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(kTestPeerId, peer_id);
        EXPECT_EQ(attr->handle(), handle);
        EXPECT_EQ(22u, offset);
        result_cb(fit::ok(),
                  StaticByteBuffer('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 StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x02, 0x00, // handle: 0x0002
      0x16, 0x00  // offset: 0x0016
  );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobDynamicRequestError) {
  const StaticByteBuffer kTestValue('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(
      [&](PeerId peer_id, att::Handle handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(kTestPeerId, peer_id);
        EXPECT_EQ(attr->handle(), handle);

        result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
      });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x02, 0x00, // handle: 0x0002
      0x16, 0x00  // offset: 0x0016
      );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobRequestStaticSuccess) {
  const StaticByteBuffer kTestValue('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 StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x01, 0x00, // handle: 0x0002
      0x16, 0x00  // offset: 0x0016
  );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobRequestStaticOverflowError) {
  const StaticByteBuffer kTestValue('s', 'h', 'o', 'r', 't', 'e', 'r');

  auto* grp = db()->NewGrouping(types::kPrimaryService, 0, kTestValue);
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x01, 0x00, // handle: 0x0001
      0x16, 0x10  // offset: 0x1016
  );
  const StaticByteBuffer kExpected(
      0x01,       // Error
      0x0C,       // opcode
      0x01, 0x00, // handle: 0x0001
      0x07        // InvalidOffset
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobRequestInvalidHandleError) {
  const StaticByteBuffer kTestValue('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 StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x02, 0x30, // handle: 0x0002
      0x16, 0x00  // offset: 0x0016
      );
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x0C,        // request: read blob request
      0x02, 0x30,  // handle: 0x0001
      0x01         // error: invalid handle
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobRequestNotPermitedError) {
  const StaticByteBuffer kTestValue('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(/*encryption=*/true,
                                                /*authentication=*/false,
                                                /*authorization=*/false));
  attr->set_read_handler(
      [&](PeerId peer_id, att::Handle handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(kTestPeerId, peer_id);
        EXPECT_EQ(attr->handle(), handle);

        result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
      });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x02, 0x00, // handle: 0x0002
      0x16, 0x00  // offset: 0x0016
      );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadBlobRequestInvalidOffsetError) {
  const StaticByteBuffer kTestValue('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(
      [&](PeerId peer_id, att::Handle handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(kTestPeerId, peer_id);
        EXPECT_EQ(attr->handle(), handle);

        result_cb(fit::error(att::ErrorCode::kInvalidOffset), BufferView());
      });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0C,       // opcode: read blob request
      0x02, 0x00, // handle: 0x0002
      0x16, 0x40  // offset: 0x4016
      );
  const StaticByteBuffer kExpected(
      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_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ReadRequestSuccess) {
  const StaticByteBuffer kDeclValue('d', 'e', 'c', 'l');
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);
  auto* attr = grp->AddAttribute(
      kTestType16, AllowedNoSecurity(), att::AccessRequirements());
  attr->set_read_handler(
      [&](PeerId peer_id, att::Handle handle, uint16_t offset, auto result_cb) {
        EXPECT_EQ(kTestPeerId, peer_id);
        EXPECT_EQ(attr->handle(), handle);
        EXPECT_EQ(0u, offset);

        result_cb(fit::ok(), kTestValue);
      });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kRequest(
      0x0A,       // opcode: read request
      0x02, 0x00  // handle: 0x0002
  );
  const StaticByteBuffer kExpected(
      0x0B,          // opcode: read response
      'f', 'o', 'o'  // value: kTestValue
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, PrepareWriteRequestInvalidPDU) {
  // Payload is one byte too short.
  // clang-format off
  const StaticByteBuffer kInvalidPDU(
      0x16,        // opcode: prepare write request
      0x01, 0x00,  // handle: 0x0001
      0x01         // offset (should be 2 bytes).
  );
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x16,        // request: prepare write request
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, PrepareWriteRequestInvalidHandle) {
  // clang-format off
  const StaticByteBuffer kRequest(
      0x16,              // opcode: prepare write request
      0x01, 0x00,         // handle: 0x0001
      0x00, 0x00,         // offset: 0
      't', 'e', 's', 't'  // value: "test"
  );
  const StaticByteBuffer kResponse(
      0x01,        // opcode: error response
      0x16,        // request: prepare write request
      0x01, 0x00,  // handle: 0x0001
      0x01         // error: invalid handle
  );
  // clang-format on

  EXPECT_PACKET_OUT(kResponse);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, PrepareWriteRequestSucceeds) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);

  // No security requirement
  auto* attr =
      grp->AddAttribute(kTestType16,
                        att::AccessRequirements(),
                        att::AccessRequirements(/*encryption=*/false,
                                                /*authentication=*/false,
                                                /*authorization=*/false));
  grp->set_active(true);

  int write_count = 0;
  attr->set_write_handler(
      [&](PeerId, att::Handle, uint16_t, const auto&, const auto&) {
        write_count++;
      });

  ASSERT_EQ(0x0002, attr->handle());

  // clang-format off
  const StaticByteBuffer kRequest(
      0x16,              // opcode: prepare write request
      0x02, 0x00,         // handle: 0x0002
      0x00, 0x00,         // offset: 0
      't', 'e', 's', 't'  // value: "test"
  );
  const StaticByteBuffer kResponse(
      0x17,              // opcode: prepare write response
      0x02, 0x00,         // handle: 0x0002
      0x00, 0x00,         // offset: 0
      't', 'e', 's', 't'  // value: "test"
  );
  // clang-format on

  EXPECT_PACKET_OUT(kResponse);
  fake_chan()->Receive(kRequest);
  // The attribute should not have been written yet.
  EXPECT_EQ(0, write_count);
}

TEST_F(ServerTest, PrepareWriteRequestPrepareQueueFull) {
  const StaticByteBuffer kTestValue('f', 'o', 'o');
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue);

  // No security requirement
  const auto* attr =
      grp->AddAttribute(kTestType16,
                        att::AccessRequirements(),
                        att::AccessRequirements(/*encryption=*/false,
                                                /*authentication=*/false,
                                                /*authorization=*/false));
  grp->set_active(true);

  ASSERT_EQ(0x0002, attr->handle());

  // clang-format off
  const StaticByteBuffer kRequest(
      0x16,              // opcode: prepare write request
      0x02, 0x00,         // handle: 0x0002
      0x00, 0x00,         // offset: 0
      't', 'e', 's', 't'  // value: "test"
  );
  const StaticByteBuffer kSuccessResponse(
      0x17,              // opcode: prepare write response
      0x02, 0x00,         // handle: 0x0002
      0x00, 0x00,         // offset: 0
      't', 'e', 's', 't'  // value: "test"
  );
  const StaticByteBuffer kErrorResponse(
      0x01,        // opcode: error response
      0x16,        // request: prepare write request
      0x02, 0x00,  // handle: 0x0002
      0x09         // error: prepare queue full
  );
  // clang-format on

  // Write requests should succeed until capacity is filled.
  for (unsigned i = 0; i < att::kPrepareQueueMaxCapacity; i++) {
    EXPECT_PACKET_OUT(kSuccessResponse);
    fake_chan()->Receive(kRequest);
    ASSERT_TRUE(AllExpectedPacketsSent())
        << "Unexpected failure at attempt: " << i;
  }

  // The next request should fail with a capacity error.
  EXPECT_PACKET_OUT(kErrorResponse);
  fake_chan()->Receive(kRequest);
}

TEST_F(ServerTest, ExecuteWriteMalformedPayload) {
  // Payload is one byte too short.
  // clang-format off
  const StaticByteBuffer kInvalidPDU(
      0x18  // opcode: execute write request
  );
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x18,        // request: execute write request
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

TEST_F(ServerTest, ExecuteWriteInvalidFlag) {
  // Payload is one byte too short.
  // clang-format off
  const StaticByteBuffer kInvalidPDU(
      0x18,  // opcode: execute write request
      0xFF   // flag: invalid
  );
  const StaticByteBuffer kExpected(
      0x01,        // opcode: error response
      0x18,        // request: execute write request
      0x00, 0x00,  // handle: 0
      0x04         // error: Invalid PDU
  );
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  fake_chan()->Receive(kInvalidPDU);
}

// Tests that an "execute write request" without any prepared writes returns
// success without writing to any attributes.
TEST_F(ServerTest, ExecuteWriteQueueEmpty) {
  // clang-format off
  const StaticByteBuffer kExecute(
    0x18,  // opcode: execute write request
    0x01   // flag: "write pending"
  );
  const StaticByteBuffer kExecuteResponse(
    0x19  // opcode: execute write response
  );
  // clang-format on

  // |buffer| should contain the partial writes.
  EXPECT_PACKET_OUT(kExecuteResponse);
  fake_chan()->Receive(kExecute);
}

TEST_F(ServerTest, ExecuteWriteSuccess) {
  StaticByteBuffer buffer('x', 'x', 'x', 'x', 'x', 'x');

  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1);
  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,
                              auto result_cb) {
    EXPECT_EQ(kTestPeerId, peer_id);
    EXPECT_EQ(attr->handle(), handle);

    // Write the contents into |buffer|.
    buffer.Write(value, offset);
    result_cb(fit::ok());
  });
  grp->set_active(true);

  // Prepare two partial writes of the string "hello!".
  // clang-format off
  const StaticByteBuffer kPrepare1(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    'h', 'e', 'l', 'l'  // value: "hell"
  );
  const StaticByteBuffer kPrepareResponse1(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    'h', 'e', 'l', 'l'  // value: "hell"
  );
  const StaticByteBuffer kPrepare2(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x04, 0x00,         // offset: 4
    'o', '!'            // value: "o!"
  );
  const StaticByteBuffer kPrepareResponse2(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x04, 0x00,         // offset: 4
    'o', '!'            // value: "o!"
  );

  // Add an overlapping write that partial overwrites data from previous
  // payloads.
  const StaticByteBuffer kPrepare3(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x02, 0x00,         // offset: 2
    'r', 'p', '?'       // value: "rp?"
  );
  const StaticByteBuffer kPrepareResponse3(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x02, 0x00,         // offset: 2
    'r', 'p', '?'       // value: "rp?"
  );

  // clang-format on

  EXPECT_PACKET_OUT(kPrepareResponse1);
  fake_chan()->Receive(kPrepare1);
  EXPECT_PACKET_OUT(kPrepareResponse2);
  fake_chan()->Receive(kPrepare2);
  EXPECT_PACKET_OUT(kPrepareResponse3);
  fake_chan()->Receive(kPrepare3);

  // The writes should not be committed yet.
  EXPECT_EQ("xxxxxx", buffer.AsString());

  // clang-format off
  const StaticByteBuffer kExecute(
    0x18,  // opcode: execute write request
    0x01   // flag: "write pending"
  );
  const StaticByteBuffer kExecuteResponse(
    0x19  // opcode: execute write response
  );
  // clang-format on

  // |buffer| should contain the partial writes.
  EXPECT_PACKET_OUT(kExecuteResponse);
  fake_chan()->Receive(kExecute);
  EXPECT_EQ("herp?!", buffer.AsString());
}

// Tests that the rest of the queue is dropped if a prepared write fails.
TEST_F(ServerTest, ExecuteWriteError) {
  StaticByteBuffer buffer('x', 'x', 'x', 'x', 'x', 'x');

  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1);
  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,
                              auto result_cb) {
    EXPECT_EQ(kTestPeerId, peer_id);
    EXPECT_EQ(attr->handle(), handle);

    // Make the write to non-zero offsets fail (this corresponds to the second
    // partial write we prepare below.
    if (offset) {
      result_cb(fit::error(att::ErrorCode::kUnlikelyError));
    } else {
      buffer.Write(value);
      result_cb(fit::ok());
    }
  });
  grp->set_active(true);

  // Prepare two partial writes of the string "hello!".
  // clang-format off
  const StaticByteBuffer kPrepare1(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    'h', 'e', 'l', 'l'  // value: "hell"
  );
  const StaticByteBuffer kPrepareResponse1(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    'h', 'e', 'l', 'l'  // value: "hell"
  );
  const StaticByteBuffer kPrepare2(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x04, 0x00,         // offset: 4
    'o', '!'            // value: "o!"
  );
  const StaticByteBuffer kPrepareResponse2(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x04, 0x00,         // offset: 4
    'o', '!'            // value: "o!"
  );
  // clang-format on

  EXPECT_PACKET_OUT(kPrepareResponse1);
  fake_chan()->Receive(kPrepare1);
  EXPECT_PACKET_OUT(kPrepareResponse2);
  fake_chan()->Receive(kPrepare2);

  // The writes should not be committed yet.
  EXPECT_EQ("xxxxxx", buffer.AsString());

  // clang-format off
  const StaticByteBuffer kExecute(
    0x18,  // opcode: execute write request
    0x01   // flag: "write pending"
  );
  const StaticByteBuffer kExecuteResponse(
    0x01,        // opcode: error response
    0x18,        // request: execute write request
    0x02, 0x00,  // handle: 2 (the attribute in error)
    0x0E         // error: Unlikely Error (returned by callback above).
  );
  // clang-format on

  // Only the first partial write should have gone through as the second one
  // is expected to fail.
  EXPECT_PACKET_OUT(kExecuteResponse);
  fake_chan()->Receive(kExecute);
  EXPECT_EQ("hellxx", buffer.AsString());
}

TEST_F(ServerTest, ExecuteWriteAbort) {
  auto* grp = db()->NewGrouping(types::kPrimaryService, 1, kTestValue1);
  // |attr| has handle "2".
  auto* attr = grp->AddAttribute(
      kTestType16, att::AccessRequirements(), AllowedNoSecurity());

  int write_count = 0;
  attr->set_write_handler([&](const auto& peer_id,
                              att::Handle handle,
                              uint16_t offset,
                              const auto& value,
                              auto result_cb) {
    write_count++;

    EXPECT_EQ(kTestPeerId, peer_id);
    EXPECT_EQ(attr->handle(), handle);
    EXPECT_EQ(0u, offset);
    EXPECT_TRUE(ContainersEqual(StaticByteBuffer('l', 'o', 'l'), value));
    result_cb(fit::ok());
  });
  grp->set_active(true);

  // clang-format off
  const StaticByteBuffer kPrepareToAbort(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    't', 'e', 's', 't'  // value: "test"
  );
  const StaticByteBuffer kPrepareToAbortResponse(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    't', 'e', 's', 't'  // value: "test"
  );
  // clang-format on

  // Prepare writes. These should get committed right away.
  EXPECT_PACKET_OUT(kPrepareToAbortResponse);
  fake_chan()->Receive(kPrepareToAbort);
  EXPECT_PACKET_OUT(kPrepareToAbortResponse);
  fake_chan()->Receive(kPrepareToAbort);
  EXPECT_PACKET_OUT(kPrepareToAbortResponse);
  fake_chan()->Receive(kPrepareToAbort);
  EXPECT_PACKET_OUT(kPrepareToAbortResponse);
  fake_chan()->Receive(kPrepareToAbort);
  EXPECT_TRUE(AllExpectedPacketsSent());
  EXPECT_EQ(0, write_count);

  // Abort the writes. They should get dropped.
  // clang-format off
  const StaticByteBuffer kAbort(
    0x18,  // opcode: execute write request
    0x00   // flag: "cancel all"
  );
  const StaticByteBuffer kAbortResponse(
    0x19  // opcode: execute write response
  );
  // clang-format on
  EXPECT_PACKET_OUT(kAbortResponse);
  fake_chan()->Receive(kAbort);
  EXPECT_EQ(0, write_count);

  // Prepare and commit a new write request. This one should take effect without
  // involving the previously aborted writes.
  // clang-format off
  const StaticByteBuffer kPrepareToCommit(
    0x016,              // opcode: prepare write request
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    'l', 'o', 'l'       // value: "lol"
  );
  const StaticByteBuffer kPrepareToCommitResponse(
    0x017,              // opcode: prepare write response
    0x02, 0x00,         // handle: 0x0002
    0x00, 0x00,         // offset: 0
    'l', 'o', 'l'       // value: "lol"
  );
  const StaticByteBuffer kCommit(
    0x18,  // opcode: execute write request
    0x01   // flag: "write pending"
  );
  const StaticByteBuffer kCommitResponse(
    0x19  // opcode: execute write response
  );
  // clang-format on

  EXPECT_PACKET_OUT(kPrepareToCommitResponse);
  fake_chan()->Receive(kPrepareToCommit);
  EXPECT_PACKET_OUT(kCommitResponse);
  fake_chan()->Receive(kCommit);
  EXPECT_EQ(1, write_count);
}

TEST_F(ServerTest, TrySendNotificationNoCccConfig) {
  IdType svc_id =
      RegisterSvcWithSingleChrc(kTestSvcType, kTestChrcId, kTestChrcType);
  const BufferView kTestValue;
  server()->SendUpdate(
      svc_id, kTestChrcId, kTestValue, /*indicate_cb=*/nullptr);
}

TEST_F(ServerTest, TrySendNotificationConfiguredForIndicationsOnly) {
  SvcIdAndChrcHandle registered = RegisterSvcWithConfiguredChrc(
      kTestSvcType, kTestChrcId, kTestChrcType, kCCCIndicationBit);
  const BufferView kTestValue;
  server()->SendUpdate(
      registered.svc_id, kTestChrcId, kTestValue, /*indicate_cb=*/nullptr);
}

TEST_F(ServerTest, SendNotificationEmpty) {
  SvcIdAndChrcHandle registered =
      RegisterSvcWithConfiguredChrc(kTestSvcType, kTestChrcId, kTestChrcType);
  const BufferView kTestValue;

  // clang-format off
  const StaticByteBuffer kExpected{
    att::kNotification,  // Opcode
    // Handle of the characteristic value being notified
    LowerBits(registered.chrc_val_handle), UpperBits(registered.chrc_val_handle)
  };
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  server()->SendUpdate(
      registered.svc_id, kTestChrcId, kTestValue, /*indicate_cb=*/nullptr);
}

TEST_F(ServerTest, SendNotification) {
  SvcIdAndChrcHandle registered =
      RegisterSvcWithConfiguredChrc(kTestSvcType, kTestChrcId, kTestChrcType);
  const StaticByteBuffer kTestValue('f', 'o', 'o');

  // clang-format off
  const StaticByteBuffer kExpected{
    att::kNotification,  // Opcode
    // Handle of the characteristic value being notified
    LowerBits(registered.chrc_val_handle), UpperBits(registered.chrc_val_handle),
    kTestValue[0], kTestValue[1], kTestValue[2]
  };
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  server()->SendUpdate(registered.svc_id,
                       kTestChrcId,
                       kTestValue.view(),
                       /*indicate_cb=*/nullptr);
}

TEST_F(ServerTest, TrySendIndicationNoCccConfig) {
  IdType svc_id =
      RegisterSvcWithSingleChrc(kTestSvcType, kTestChrcId, kTestChrcType);
  const BufferView kTestValue;

  att::Result<> indicate_res = fit::ok();
  auto indicate_cb = [&](att::Result<> res) { indicate_res = res; };

  server()->SendUpdate(svc_id, kTestChrcId, kTestValue, std::move(indicate_cb));
  EXPECT_EQ(fit::failed(), indicate_res);
}

TEST_F(ServerTest, TrySendIndicationConfiguredForNotificationsOnly) {
  SvcIdAndChrcHandle registered = RegisterSvcWithConfiguredChrc(
      kTestSvcType, kTestChrcId, kTestChrcType, kCCCNotificationBit);
  const BufferView kTestValue;

  att::Result<> indicate_res = fit::ok();
  auto indicate_cb = [&](att::Result<> res) { indicate_res = res; };

  server()->SendUpdate(
      registered.svc_id, kTestChrcId, kTestValue, std::move(indicate_cb));
  EXPECT_EQ(fit::failed(), indicate_res);
}

TEST_F(ServerTest, SendIndicationEmpty) {
  SvcIdAndChrcHandle registered =
      RegisterSvcWithConfiguredChrc(kTestSvcType, kTestChrcId, kTestChrcType);
  const BufferView kTestValue;

  att::Result<> indicate_res = ToResult(HostError::kFailed);
  auto indicate_cb = [&](att::Result<> res) { indicate_res = res; };

  // clang-format off
  const StaticByteBuffer kExpected{
    att::kIndication,  // Opcode
    // Handle of the characteristic value being notified
    LowerBits(registered.chrc_val_handle), UpperBits(registered.chrc_val_handle)
  };
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  server()->SendUpdate(
      registered.svc_id, kTestChrcId, kTestValue, std::move(indicate_cb));
  EXPECT_TRUE(AllExpectedPacketsSent());

  const StaticByteBuffer kIndicationConfirmation{att::kConfirmation};
  fake_chan()->Receive(kIndicationConfirmation);
  EXPECT_EQ(fit::ok(), indicate_res);
}

TEST_F(ServerTest, SendIndication) {
  SvcIdAndChrcHandle registered =
      RegisterSvcWithConfiguredChrc(kTestSvcType, kTestChrcId, kTestChrcType);
  const StaticByteBuffer kTestValue('f', 'o', 'o');

  att::Result<> indicate_res = ToResult(HostError::kFailed);
  auto indicate_cb = [&](att::Result<> res) { indicate_res = res; };

  // clang-format off
  const StaticByteBuffer kExpected{
    att::kIndication,  // Opcode
    // Handle of the characteristic value being notified
    LowerBits(registered.chrc_val_handle), UpperBits(registered.chrc_val_handle),
    kTestValue[0], kTestValue[1], kTestValue[2]
  };
  // clang-format on

  EXPECT_PACKET_OUT(kExpected);
  server()->SendUpdate(registered.svc_id,
                       kTestChrcId,
                       kTestValue.view(),
                       std::move(indicate_cb));
  EXPECT_TRUE(AllExpectedPacketsSent());

  const StaticByteBuffer kIndicationConfirmation{att::kConfirmation};
  fake_chan()->Receive(kIndicationConfirmation);
  EXPECT_EQ(fit::ok(), indicate_res);
}

class ServerTestSecurity : public ServerTest {
 protected:
  void InitializeAttributesForReading() {
    auto* grp = db()->NewGrouping(types::kPrimaryService, 4, kTestValue1);

    const att::AccessRequirements encryption(/*encryption=*/true,
                                             /*authentication=*/false,
                                             /*authorization=*/false);
    const att::AccessRequirements authentication(/*encryption=*/false,
                                                 /*authentication=*/true,
                                                 /*authorization=*/false);
    const att::AccessRequirements authorization(/*encryption=*/false,
                                                /*authentication=*/false,
                                                /*authorization=*/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(/*encryption=*/true,
                                             /*authentication=*/false,
                                             /*authorization=*/false);
    const att::AccessRequirements authentication(/*encryption=*/false,
                                                 /*authentication=*/true,
                                                 /*authorization=*/false);
    const att::AccessRequirements authorization(/*encryption=*/false,
                                                /*authentication=*/false,
                                                /*authorization=*/true);

    auto write_handler = [this](const auto&,
                                att::Handle,
                                uint16_t,
                                const auto& value,
                                auto responder) {
      write_count_++;
      if (responder) {
        responder(fit::ok());
      }
    };

    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);
  }

  auto MakeAttError(att::OpCode request,
                    att::Handle handle,
                    att::ErrorCode ecode) {
    return StaticByteBuffer(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 successful response if
  // |expected_status| is fit::ok().
  bool EmulateReadByTypeRequest(att::Handle handle,
                                fit::result<att::ErrorCode> expected_status) {
    const StaticByteBuffer kReadByTypeRequestPdu(
        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_status.is_ok()) {
      EXPECT_PACKET_OUT(StaticByteBuffer(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 {
      EXPECT_PACKET_OUT(
          MakeAttError(0x08, handle, expected_status.error_value()));
    }
    fake_chan()->Receive(kReadByTypeRequestPdu);
    return AllExpectedPacketsSent();
  }

  bool EmulateReadBlobRequest(att::Handle handle,
                              fit::result<att::ErrorCode> expected_status) {
    const StaticByteBuffer kReadBlobRequestPdu(0x0C,  // opcode: read blob
                                               LowerBits(handle),
                                               UpperBits(handle),  // handle
                                               0x00,
                                               0x00);  // offset: 0
    if (expected_status.is_ok()) {
      EXPECT_PACKET_OUT(StaticByteBuffer(0x0D,  // opcode: read blob response
                                         'f',
                                         'o',
                                         'o'  // value: "foo", i.e. kTestValue1
                                         ));
    } else {
      EXPECT_PACKET_OUT(
          MakeAttError(0x0C, handle, expected_status.error_value()));
    }
    fake_chan()->Receive(kReadBlobRequestPdu);
    return AllExpectedPacketsSent();
  }

  bool EmulateReadRequest(att::Handle handle,
                          fit::result<att::ErrorCode> expected_status) {
    const StaticByteBuffer kReadRequestPdu(0x0A,  // opcode: read request
                                           LowerBits(handle),
                                           UpperBits(handle));  // handle
    if (expected_status.is_ok()) {
      EXPECT_PACKET_OUT(StaticByteBuffer(0x0B,  // opcode: read response
                                         'f',
                                         'o',
                                         'o'  // value: "foo", i.e. kTestValue1
                                         ));
    } else {
      EXPECT_PACKET_OUT(
          MakeAttError(0x0A, handle, expected_status.error_value()));
    }
    fake_chan()->Receive(kReadRequestPdu);
    return AllExpectedPacketsSent();
  }

  bool EmulateWriteRequest(att::Handle handle,
                           fit::result<att::ErrorCode> expected_status) {
    const StaticByteBuffer kWriteRequestPdu(0x12,  // opcode: write request
                                            LowerBits(handle),
                                            UpperBits(handle),  // handle
                                            't',
                                            'e',
                                            's',
                                            't');  // value: "test"
    if (expected_status.is_ok()) {
      EXPECT_PACKET_OUT(StaticByteBuffer(0x13));
    } else {
      EXPECT_PACKET_OUT(
          MakeAttError(0x12, handle, expected_status.error_value()));
    }
    fake_chan()->Receive(kWriteRequestPdu);
    return AllExpectedPacketsSent();
  }

  bool EmulatePrepareWriteRequest(att::Handle handle,
                                  fit::result<att::ErrorCode> expected_status) {
    const auto kPrepareWriteRequestPdu =
        StaticByteBuffer(0x16,  // opcode: prepare write request
                         LowerBits(handle),
                         UpperBits(handle),  // handle
                         0x00,
                         0x00,  // offset: 0
                         't',
                         'e',
                         's',
                         't'  // value: "test"
        );
    if (expected_status.is_ok()) {
      EXPECT_PACKET_OUT(StaticByteBuffer(0x17,  // prepare write response
                                         LowerBits(handle),
                                         UpperBits(handle),  // handle
                                         0x00,
                                         0x00,  // offset: 0
                                         't',
                                         'e',
                                         's',
                                         't'  // value: "test"
                                         ));
    } else {
      EXPECT_PACKET_OUT(
          MakeAttError(0x16, handle, expected_status.error_value()));
    }
    fake_chan()->Receive(kPrepareWriteRequestPdu);
    return AllExpectedPacketsSent();
  }

  // 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, fit::result<att::ErrorCode>) {
    fake_chan()->Receive(StaticByteBuffer(0x52,  // opcode: write command
                                          LowerBits(handle),
                                          UpperBits(handle),  // handle
                                          't',
                                          'e',
                                          's',
                                          't'  // value: "test"
                                          ));
    RunUntilIdle();
    return true;
  }

  template <bool (ServerTestSecurity::* EmulateMethod)(
                att::Handle, fit::result<att::ErrorCode>),
            bool IsWrite>
  void RunTest() {
    const fit::error<att::ErrorCode> kNotPermittedError =
        fit::error(IsWrite ? att::ErrorCode::kWriteNotPermitted
                           : att::ErrorCode::kReadNotPermitted);

    // No security.
    EXPECT_TRUE((this->*EmulateMethod)(not_permitted_attr()->handle(),
                                       fit::error(kNotPermittedError)));
    EXPECT_TRUE((this->*EmulateMethod)(
        encryption_required_attr()->handle(),
        fit::error(att::ErrorCode::kInsufficientAuthentication)));
    EXPECT_TRUE((this->*EmulateMethod)(
        authentication_required_attr()->handle(),
        fit::error(att::ErrorCode::kInsufficientAuthentication)));
    EXPECT_TRUE((this->*EmulateMethod)(
        authorization_required_attr()->handle(),
        fit::error(att::ErrorCode::kInsufficientAuthentication)));

    // Link encrypted.
    fake_chan()->set_security(sm::SecurityProperties(
        sm::SecurityLevel::kEncrypted, 16, /*secure_connections=*/false));
    EXPECT_TRUE((this->*EmulateMethod)(not_permitted_attr()->handle(),
                                       kNotPermittedError));
    EXPECT_TRUE((this->*EmulateMethod)(encryption_required_attr()->handle(),
                                       fit::ok()));
    EXPECT_TRUE((this->*EmulateMethod)(
        authentication_required_attr()->handle(),
        fit::error(att::ErrorCode::kInsufficientAuthentication)));
    EXPECT_TRUE((this->*EmulateMethod)(
        authorization_required_attr()->handle(),
        fit::error(att::ErrorCode::kInsufficientAuthentication)));

    // inclusive-language: ignore
    // Link encrypted w/ MITM.
    fake_chan()->set_security(
        sm::SecurityProperties(sm::SecurityLevel::kAuthenticated,
                               16,
                               /*secure_connections=*/false));
    EXPECT_TRUE((this->*EmulateMethod)(not_permitted_attr()->handle(),
                                       kNotPermittedError));
    EXPECT_TRUE((this->*EmulateMethod)(encryption_required_attr()->handle(),
                                       fit::ok()));
    EXPECT_TRUE((this->*EmulateMethod)(authentication_required_attr()->handle(),
                                       fit::ok()));
    EXPECT_TRUE((this->*EmulateMethod)(authorization_required_attr()->handle(),
                                       fit::ok()));
  }

  void RunReadByTypeTest() {
    RunTest<&ServerTestSecurity::EmulateReadByTypeRequest, false>();
  }
  void RunReadBlobTest() {
    RunTest<&ServerTestSecurity::EmulateReadBlobRequest, false>();
  }
  void RunReadRequestTest() {
    RunTest<&ServerTestSecurity::EmulateReadRequest, false>();
  }
  void RunWriteRequestTest() {
    RunTest<&ServerTestSecurity::EmulateWriteRequest, true>();
  }
  void RunPrepareWriteRequestTest() {
    RunTest<&ServerTestSecurity::EmulatePrepareWriteRequest, true>();
  }
  void RunWriteCommandTest() {
    RunTest<&ServerTestSecurity::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(ServerTestSecurity, ReadByTypeErrorSecurity) {
  InitializeAttributesForReading();
  RunReadByTypeTest();
}

TEST_F(ServerTestSecurity, ReadBlobErrorSecurity) {
  InitializeAttributesForReading();
  RunReadBlobTest();
}

TEST_F(ServerTestSecurity, ReadErrorSecurity) {
  InitializeAttributesForReading();
  RunReadRequestTest();
}

TEST_F(ServerTestSecurity, WriteErrorSecurity) {
  InitializeAttributesForWriting();
  RunWriteRequestTest();

  // Only 4 writes should have gone through.
  EXPECT_EQ(4u, write_count());
}

TEST_F(ServerTestSecurity, WriteCommandErrorSecurity) {
  InitializeAttributesForWriting();
  RunWriteCommandTest();

  // Only 4 writes should have gone through.
  EXPECT_EQ(4u, write_count());
}

TEST_F(ServerTestSecurity, PrepareWriteRequestSecurity) {
  InitializeAttributesForWriting();
  RunPrepareWriteRequestTest();

  // None of the write handlers should have been called since no execute write
  // request has been sent.
  EXPECT_EQ(0u, write_count());
}

}  // namespace
}  // namespace bt::gatt
