// 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 <lib/fit/function.h>

#include "pw_bluetooth_sapphire/internal/host/att/att.h"
#include "pw_bluetooth_sapphire/internal/host/att/database.h"
#include "pw_bluetooth_sapphire/internal/host/att/permissions.h"
#include "pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "pw_bluetooth_sapphire/internal/host/common/slab_allocator.h"
#include "pw_bluetooth_sapphire/internal/host/common/trace.h"
#include "pw_bluetooth_sapphire/internal/host/common/uuid.h"
#include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"

namespace bt::gatt {

class AttBasedServer final : public Server {
 public:
  AttBasedServer(PeerId peer_id,
                 LocalServiceManager::WeakPtr local_services,
                 att::Bearer::WeakPtr bearer)
      : peer_id_(peer_id),
        local_services_(std::move(local_services)),
        att_(std::move(bearer)),
        weak_self_(this) {
    BT_ASSERT(local_services_.is_alive());
    BT_DEBUG_ASSERT(att_.is_alive());

    exchange_mtu_id_ = att_->RegisterHandler(
        att::kExchangeMTURequest,
        fit::bind_member<&AttBasedServer::OnExchangeMTU>(this));
    find_information_id_ = att_->RegisterHandler(
        att::kFindInformationRequest,
        fit::bind_member<&AttBasedServer::OnFindInformation>(this));
    read_by_group_type_id_ = att_->RegisterHandler(
        att::kReadByGroupTypeRequest,
        fit::bind_member<&AttBasedServer::OnReadByGroupType>(this));
    read_by_type_id_ = att_->RegisterHandler(
        att::kReadByTypeRequest,
        fit::bind_member<&AttBasedServer::OnReadByType>(this));
    read_req_id_ = att_->RegisterHandler(
        att::kReadRequest,
        fit::bind_member<&AttBasedServer::OnReadRequest>(this));
    write_req_id_ = att_->RegisterHandler(
        att::kWriteRequest,
        fit::bind_member<&AttBasedServer::OnWriteRequest>(this));
    write_cmd_id_ = att_->RegisterHandler(
        att::kWriteCommand,
        fit::bind_member<&AttBasedServer::OnWriteCommand>(this));
    read_blob_req_id_ = att_->RegisterHandler(
        att::kReadBlobRequest,
        fit::bind_member<&AttBasedServer::OnReadBlobRequest>(this));
    find_by_type_value_id_ = att_->RegisterHandler(
        att::kFindByTypeValueRequest,
        fit::bind_member<&AttBasedServer::OnFindByTypeValueRequest>(this));
    prepare_write_id_ = att_->RegisterHandler(
        att::kPrepareWriteRequest,
        fit::bind_member<&AttBasedServer::OnPrepareWriteRequest>(this));
    exec_write_id_ = att_->RegisterHandler(
        att::kExecuteWriteRequest,
        fit::bind_member<&AttBasedServer::OnExecuteWriteRequest>(this));
  }

  ~AttBasedServer() override {
    att_->UnregisterHandler(exec_write_id_);
    att_->UnregisterHandler(prepare_write_id_);
    att_->UnregisterHandler(find_by_type_value_id_);
    att_->UnregisterHandler(read_blob_req_id_);
    att_->UnregisterHandler(write_cmd_id_);
    att_->UnregisterHandler(write_req_id_);
    att_->UnregisterHandler(read_req_id_);
    att_->UnregisterHandler(read_by_type_id_);
    att_->UnregisterHandler(read_by_group_type_id_);
    att_->UnregisterHandler(find_information_id_);
    att_->UnregisterHandler(exchange_mtu_id_);
  }

 private:
  // Convenience "alias"
  inline att::Database::WeakPtr db() { return local_services_->database(); }

  // Server overrides:
  void SendUpdate(IdType service_id,
                  IdType chrc_id,
                  BufferView value,
                  IndicationCallback indicate_cb) override {
    auto buffer =
        NewBuffer(sizeof(att::Header) + sizeof(att::Handle) + value.size());
    BT_ASSERT(buffer);

    LocalServiceManager::ClientCharacteristicConfig config;
    if (!local_services_->GetCharacteristicConfig(
            service_id, chrc_id, peer_id_, &config)) {
      bt_log(TRACE,
             "gatt",
             "peer has not configured characteristic: %s",
             bt_str(peer_id_));
      if (indicate_cb) {
        indicate_cb(ToResult(HostError::kNotSupported));
      }
      return;
    }

    // Make sure that the client has subscribed to the requested protocol
    // method.
    if ((indicate_cb && !config.indicate) || (!indicate_cb && !config.notify)) {
      bt_log(TRACE,
             "gatt",
             "peer has not enabled (%s): %s",
             (indicate_cb ? "indications" : "notifications"),
             bt_str(peer_id_));
      if (indicate_cb) {
        indicate_cb(ToResult(HostError::kNotSupported));
      }
      return;
    }

    att::PacketWriter writer(
        indicate_cb ? att::kIndication : att::kNotification, buffer.get());
    auto rsp_params = writer.mutable_payload<att::AttributeData>();
    rsp_params->handle = htole16(config.handle);
    writer.mutable_payload_data().Write(value, sizeof(att::AttributeData));

    if (!indicate_cb) {
      [[maybe_unused]] bool _ = att_->SendWithoutResponse(std::move(buffer));
      return;
    }
    auto transaction_cb = [indicate_cb = std::move(indicate_cb)](
                              att::Bearer::TransactionResult result) mutable {
      if (result.is_ok()) {
        bt_log(DEBUG, "gatt", "got indication ACK");
        indicate_cb(fit::ok());
      } else {
        const auto& [error, handle] = result.error_value();
        bt_log(WARN,
               "gatt",
               "indication failed (error %s, handle: %#.4x)",
               bt_str(error),
               handle);
        indicate_cb(fit::error(error));
      }
    };
    att_->StartTransaction(std::move(buffer), std::move(transaction_cb));
  }

  void ShutDown() override { att_->ShutDown(); }

  // ATT protocol request handlers:
  void OnExchangeMTU(att::Bearer::TransactionId tid,
                     const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kExchangeMTURequest);

    if (packet.payload_size() != sizeof(att::ExchangeMTURequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::ExchangeMTURequestParams>();
    uint16_t client_mtu = le16toh(params.client_rx_mtu);
    uint16_t server_mtu = att_->preferred_mtu();

    auto buffer =
        NewBuffer(sizeof(att::Header) + sizeof(att::ExchangeMTUResponseParams));
    BT_ASSERT(buffer);

    att::PacketWriter writer(att::kExchangeMTUResponse, buffer.get());
    auto rsp_params = writer.mutable_payload<att::ExchangeMTUResponseParams>();
    rsp_params->server_rx_mtu = htole16(server_mtu);

    att_->Reply(tid, std::move(buffer));

    // If the minimum value is less than the default MTU, then go with the
    // default MTU (Vol 3, Part F, 3.4.2.2).
    // TODO(armansito): This needs to use on kBREDRMinATTMTU for BR/EDR. Make
    // the default MTU configurable.
    att_->set_mtu(std::max(att::kLEMinMTU, std::min(client_mtu, server_mtu)));
  }

  void OnFindInformation(att::Bearer::TransactionId tid,
                         const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kFindInformationRequest);
    TRACE_DURATION("bluetooth", "gatt::Server::OnFindInformation");

    if (packet.payload_size() != sizeof(att::FindInformationRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::FindInformationRequestParams>();
    att::Handle start = le16toh(params.start_handle);
    att::Handle end = le16toh(params.end_handle);

    constexpr size_t kRspStructSize =
        sizeof(att::FindInformationResponseParams);
    constexpr size_t kHeaderSize = sizeof(att::Header) + kRspStructSize;
    BT_DEBUG_ASSERT(kHeaderSize <= att_->mtu());

    if (start == att::kInvalidHandle || start > end) {
      att_->ReplyWithError(tid, start, att::ErrorCode::kInvalidHandle);
      return;
    }

    // Find all attributes within range with the same compact UUID size that can
    // fit within the current MTU.
    size_t max_payload_size = att_->mtu() - kHeaderSize;
    size_t uuid_size;
    size_t entry_size;
    std::list<const att::Attribute*> results;
    for (auto it = db()->GetIterator(start, end); !it.AtEnd(); it.Advance()) {
      const auto* attr = it.get();
      BT_DEBUG_ASSERT(attr);

      // GATT does not allow 32-bit UUIDs
      size_t compact_size = attr->type().CompactSize(/*allow_32bit=*/false);
      if (results.empty()) {
        // |uuid_size| is determined by the first attribute.
        uuid_size = compact_size;
        entry_size =
            std::min(uuid_size + sizeof(att::Handle), max_payload_size);
      } else if (compact_size != uuid_size || entry_size > max_payload_size) {
        break;
      }

      results.push_back(attr);
      max_payload_size -= entry_size;
    }

    if (results.empty()) {
      att_->ReplyWithError(tid, start, att::ErrorCode::kAttributeNotFound);
      return;
    }

    BT_DEBUG_ASSERT(!results.empty());

    size_t pdu_size = kHeaderSize + entry_size * results.size();

    auto buffer = NewBuffer(pdu_size);
    BT_ASSERT(buffer);

    att::PacketWriter writer(att::kFindInformationResponse, buffer.get());
    auto rsp_params =
        writer.mutable_payload<att::FindInformationResponseParams>();
    rsp_params->format =
        (entry_size == 4) ? att::UUIDType::k16Bit : att::UUIDType::k128Bit;

    // |out_entries| initially references |params->information_data|. The loop
    // below modifies it as entries are written into the list.
    auto out_entries =
        writer.mutable_payload_data().mutable_view(kRspStructSize);
    for (const auto& attr : results) {
      *reinterpret_cast<att::Handle*>(out_entries.mutable_data()) =
          htole16(attr->handle());
      auto uuid_view = out_entries.mutable_view(sizeof(att::Handle));
      attr->type().ToBytes(&uuid_view, /*allow_32bit=*/false);

      // advance
      out_entries = out_entries.mutable_view(entry_size);
    }

    att_->Reply(tid, std::move(buffer));
  }

  void OnFindByTypeValueRequest(att::Bearer::TransactionId tid,
                                const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kFindByTypeValueRequest);

    if (packet.payload_size() < sizeof(att::FindByTypeValueRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::FindByTypeValueRequestParams>();
    att::Handle start = le16toh(params.start_handle);
    att::Handle end = le16toh(params.end_handle);
    UUID type(params.type);
    constexpr size_t kParamsSize = sizeof(att::FindByTypeValueRequestParams);

    BufferView value = packet.payload_data().view(
        kParamsSize, packet.payload_size() - kParamsSize);

    if (start == att::kInvalidHandle || start > end) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidHandle);
      return;
    }

    auto iter = db()->GetIterator(start, end, &type, /*groups_only=*/false);
    if (iter.AtEnd()) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kAttributeNotFound);
      return;
    }

    std::list<const att::Attribute*> results;

    // Filter for identical values
    for (; !iter.AtEnd(); iter.Advance()) {
      const auto* attr = iter.get();
      BT_DEBUG_ASSERT(attr);

      // Only support static values for this Request type
      if (attr->value()) {
        if (*attr->value() == value) {
          results.push_back(attr);
        }
      }
    }

    // No attributes match the value
    if (results.size() == 0) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kAttributeNotFound);
      return;
    }

    constexpr size_t kRspStructSize = sizeof(att::HandlesInformationList);
    size_t pdu_size = sizeof(att::Header) + kRspStructSize * results.size();
    auto buffer = NewBuffer(pdu_size);
    BT_ASSERT(buffer);

    att::PacketWriter writer(att::kFindByTypeValueResponse, buffer.get());

    // Points to the next entry in the target PDU.
    auto next_entry = writer.mutable_payload_data();
    for (const auto& attr : results) {
      auto* entry = reinterpret_cast<att::HandlesInformationList*>(
          next_entry.mutable_data());
      entry->handle = htole16(attr->handle());
      if (attr->group().active()) {
        entry->group_end_handle = htole16(attr->group().end_handle());
      } else {
        entry->group_end_handle = htole16(attr->handle());
      }
      next_entry = next_entry.mutable_view(kRspStructSize);
    }

    att_->Reply(tid, std::move(buffer));
  }

  void OnReadByGroupType(att::Bearer::TransactionId tid,
                         const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kReadByGroupTypeRequest);
    TRACE_DURATION("bluetooth", "gatt::Server::OnReadByGroupType");

    att::Handle start, end;
    UUID group_type;

    // The group type is represented as either a 16-bit or 128-bit UUID.
    if (packet.payload_size() == sizeof(att::ReadByTypeRequestParams16)) {
      const auto& params = packet.payload<att::ReadByTypeRequestParams16>();
      start = le16toh(params.start_handle);
      end = le16toh(params.end_handle);
      group_type = UUID(le16toh(params.type));
    } else if (packet.payload_size() ==
               sizeof(att::ReadByTypeRequestParams128)) {
      const auto& params = packet.payload<att::ReadByTypeRequestParams128>();
      start = le16toh(params.start_handle);
      end = le16toh(params.end_handle);
      group_type = UUID(params.type);
    } else {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    if (group_type != types::kPrimaryService &&
        group_type != types::kSecondaryService) {
      att_->ReplyWithError(tid, start, att::ErrorCode::kUnsupportedGroupType);
      return;
    }

    constexpr size_t kRspStructSize =
        sizeof(att::ReadByGroupTypeResponseParams);
    constexpr size_t kHeaderSize = sizeof(att::Header) + kRspStructSize;
    BT_DEBUG_ASSERT(kHeaderSize <= att_->mtu());

    size_t value_size;
    std::list<const att::Attribute*> results;
    fit::result<att::ErrorCode> status =
        ReadByTypeHelper(start,
                         end,
                         group_type,
                         /*group_type=*/true,
                         att_->mtu() - kHeaderSize,
                         att::kMaxReadByGroupTypeValueLength,
                         sizeof(att::AttributeGroupDataEntry),
                         &value_size,
                         &results);
    if (status.is_error()) {
      att_->ReplyWithError(tid, start, status.error_value());
      return;
    }

    BT_DEBUG_ASSERT(!results.empty());

    size_t entry_size = value_size + sizeof(att::AttributeGroupDataEntry);
    size_t pdu_size = kHeaderSize + entry_size * results.size();
    BT_DEBUG_ASSERT(pdu_size <= att_->mtu());

    auto buffer = NewBuffer(pdu_size);
    BT_ASSERT(buffer);

    att::PacketWriter writer(att::kReadByGroupTypeResponse, buffer.get());
    auto params = writer.mutable_payload<att::ReadByGroupTypeResponseParams>();

    BT_DEBUG_ASSERT(entry_size <= std::numeric_limits<uint8_t>::max());
    params->length = static_cast<uint8_t>(entry_size);

    // Points to the next entry in the target PDU.
    auto next_entry =
        writer.mutable_payload_data().mutable_view(kRspStructSize);
    for (const auto& attr : results) {
      auto* entry = reinterpret_cast<att::AttributeGroupDataEntry*>(
          next_entry.mutable_data());
      entry->start_handle = htole16(attr->group().start_handle());
      entry->group_end_handle = htole16(attr->group().end_handle());
      next_entry.Write(attr->group().decl_value().view(0, value_size),
                       sizeof(att::AttributeGroupDataEntry));

      next_entry = next_entry.mutable_view(entry_size);
    }

    att_->Reply(tid, std::move(buffer));
  }

  void OnReadByType(att::Bearer::TransactionId tid,
                    const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kReadByTypeRequest);
    TRACE_DURATION("bluetooth", "gatt::Server::OnReadByType");

    att::Handle start, end;
    UUID type;

    // The attribute type is represented as either a 16-bit or 128-bit UUID.
    if (packet.payload_size() == sizeof(att::ReadByTypeRequestParams16)) {
      const auto& params = packet.payload<att::ReadByTypeRequestParams16>();
      start = le16toh(params.start_handle);
      end = le16toh(params.end_handle);
      type = UUID(le16toh(params.type));
    } else if (packet.payload_size() ==
               sizeof(att::ReadByTypeRequestParams128)) {
      const auto& params = packet.payload<att::ReadByTypeRequestParams128>();
      start = le16toh(params.start_handle);
      end = le16toh(params.end_handle);
      type = UUID(params.type);
    } else {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    constexpr size_t kRspStructSize = sizeof(att::ReadByTypeResponseParams);
    constexpr size_t kHeaderSize = sizeof(att::Header) + kRspStructSize;
    BT_DEBUG_ASSERT(kHeaderSize <= att_->mtu());

    size_t value_size;
    std::list<const att::Attribute*> results;
    fit::result<att::ErrorCode> status =
        ReadByTypeHelper(start,
                         end,
                         type,
                         /*group_type=*/false,
                         att_->mtu() - kHeaderSize,
                         att::kMaxReadByTypeValueLength,
                         sizeof(att::AttributeData),
                         &value_size,
                         &results);
    if (status.is_error()) {
      att_->ReplyWithError(tid, start, status.error_value());
      return;
    }

    BT_DEBUG_ASSERT(!results.empty());

    // If the value is dynamic, then delegate the read to any registered
    // handler.
    if (!results.front()->value()) {
      BT_DEBUG_ASSERT(results.size() == 1u);

      const size_t kMaxValueSize =
          std::min(att_->mtu() - kHeaderSize - sizeof(att::AttributeData),
                   static_cast<size_t>(att::kMaxReadByTypeValueLength));

      att::Handle handle = results.front()->handle();
      auto self = weak_self_.GetWeakPtr();
      auto result_cb = [self, tid, handle, kMaxValueSize](
                           fit::result<att::ErrorCode> status,
                           const auto& value) {
        if (!self.is_alive())
          return;

        if (status.is_error()) {
          self->att_->ReplyWithError(tid, handle, status.error_value());
          return;
        }

        // Respond with just a single entry.
        size_t value_size = std::min(value.size(), kMaxValueSize);
        size_t entry_size = value_size + sizeof(att::AttributeData);
        auto buffer = NewBuffer(entry_size + kHeaderSize);
        att::PacketWriter writer(att::kReadByTypeResponse, buffer.get());

        auto params = writer.mutable_payload<att::ReadByTypeResponseParams>();
        params->length = static_cast<uint8_t>(entry_size);
        params->attribute_data_list->handle = htole16(handle);
        writer.mutable_payload_data().Write(
            value.data(), value_size, sizeof(params->length) + sizeof(handle));

        self->att_->Reply(tid, std::move(buffer));
      };

      // Respond with an error if no read handler was registered.
      if (!results.front()->ReadAsync(peer_id_, 0, result_cb)) {
        att_->ReplyWithError(tid, handle, att::ErrorCode::kReadNotPermitted);
      }
      return;
    }

    size_t entry_size = sizeof(att::AttributeData) + value_size;
    BT_DEBUG_ASSERT(entry_size <= std::numeric_limits<uint8_t>::max());

    size_t pdu_size = kHeaderSize + entry_size * results.size();
    BT_DEBUG_ASSERT(pdu_size <= att_->mtu());

    auto buffer = NewBuffer(pdu_size);
    BT_ASSERT(buffer);

    att::PacketWriter writer(att::kReadByTypeResponse, buffer.get());
    auto params = writer.mutable_payload<att::ReadByTypeResponseParams>();
    params->length = static_cast<uint8_t>(entry_size);

    // Points to the next entry in the target PDU.
    auto next_entry =
        writer.mutable_payload_data().mutable_view(kRspStructSize);
    for (const auto& attr : results) {
      auto* entry =
          reinterpret_cast<att::AttributeData*>(next_entry.mutable_data());
      entry->handle = htole16(attr->handle());
      next_entry.Write(attr->value()->view(0, value_size),
                       sizeof(entry->handle));

      next_entry = next_entry.mutable_view(entry_size);
    }

    att_->Reply(tid, std::move(buffer));
  }

  void OnReadBlobRequest(att::Bearer::TransactionId tid,
                         const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kReadBlobRequest);

    if (packet.payload_size() != sizeof(att::ReadBlobRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::ReadBlobRequestParams>();
    att::Handle handle = le16toh(params.handle);
    uint16_t offset = le16toh(params.offset);

    const auto* attr = db()->FindAttribute(handle);
    if (!attr) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle);
      return;
    }

    fit::result<att::ErrorCode> status =
        att::CheckReadPermissions(attr->read_reqs(), att_->security());
    if (status.is_error()) {
      att_->ReplyWithError(tid, handle, status.error_value());
      return;
    }

    constexpr size_t kHeaderSize = sizeof(att::Header);

    auto self = weak_self_.GetWeakPtr();
    auto callback = [self, tid, handle](fit::result<att::ErrorCode> status,
                                        const auto& value) {
      if (!self.is_alive())
        return;

      if (status.is_error()) {
        self->att_->ReplyWithError(tid, handle, status.error_value());
        return;
      }

      size_t value_size =
          std::min(value.size(), self->att_->mtu() - kHeaderSize);
      auto buffer = NewBuffer(value_size + kHeaderSize);
      BT_ASSERT(buffer);

      att::PacketWriter writer(att::kReadBlobResponse, buffer.get());
      writer.mutable_payload_data().Write(value.view(0, value_size));

      self->att_->Reply(tid, std::move(buffer));
    };

    // Use the cached value if there is one.
    if (attr->value()) {
      if (offset >= attr->value()->size()) {
        att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidOffset);
        return;
      }
      size_t value_size =
          std::min(attr->value()->size(), self->att_->mtu() - kHeaderSize);
      callback(fit::ok(), attr->value()->view(offset, value_size));
      return;
    }

    // TODO(fxbug.dev/42142121): Add a timeout to this
    if (!attr->ReadAsync(peer_id_, offset, callback)) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kReadNotPermitted);
    }
  }

  void OnReadRequest(att::Bearer::TransactionId tid,
                     const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kReadRequest);

    if (packet.payload_size() != sizeof(att::ReadRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::WriteRequestParams>();
    att::Handle handle = le16toh(params.handle);

    const auto* attr = db()->FindAttribute(handle);
    if (!attr) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle);
      return;
    }

    fit::result<att::ErrorCode> status =
        att::CheckReadPermissions(attr->read_reqs(), att_->security());
    if (status.is_error()) {
      att_->ReplyWithError(tid, handle, status.error_value());
      return;
    }

    constexpr size_t kHeaderSize = sizeof(att::Header);

    auto self = weak_self_.GetWeakPtr();
    auto callback = [self, tid, handle](fit::result<att::ErrorCode> status,
                                        const auto& value) {
      if (!self.is_alive())
        return;

      if (status.is_error()) {
        self->att_->ReplyWithError(tid, handle, status.error_value());
        return;
      }

      size_t value_size =
          std::min(value.size(), self->att_->mtu() - kHeaderSize);
      auto buffer = NewBuffer(value_size + kHeaderSize);
      BT_ASSERT(buffer);

      att::PacketWriter writer(att::kReadResponse, buffer.get());
      writer.mutable_payload_data().Write(value.view(0, value_size));

      self->att_->Reply(tid, std::move(buffer));
    };

    // Use the cached value if there is one.
    if (attr->value()) {
      callback(fit::ok(), *attr->value());
      return;
    }

    if (!attr->ReadAsync(peer_id_, 0, callback)) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kReadNotPermitted);
    }
  }

  void OnWriteCommand(att::Bearer::TransactionId tid,
                      const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kWriteCommand);

    if (packet.payload_size() < sizeof(att::WriteRequestParams)) {
      // Ignore if wrong size, no response allowed
      return;
    }

    const auto& params = packet.payload<att::WriteRequestParams>();
    att::Handle handle = le16toh(params.handle);
    const auto* attr = db()->FindAttribute(handle);

    // Attributes can be invalid if the handle is invalid
    if (!attr) {
      return;
    }

    fit::result<att::ErrorCode> status =
        att::CheckWritePermissions(attr->write_reqs(), att_->security());
    if (status.is_error()) {
      return;
    }

    // Attributes with a static value cannot be written.
    if (attr->value()) {
      return;
    }

    auto value_view = packet.payload_data().view(sizeof(params.handle));
    if (value_view.size() > att::kMaxAttributeValueLength) {
      return;
    }

    // No response allowed for commands, ignore the cb
    attr->WriteAsync(peer_id_, 0, value_view, nullptr);
  }

  void OnWriteRequest(att::Bearer::TransactionId tid,
                      const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kWriteRequest);

    if (packet.payload_size() < sizeof(att::WriteRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::WriteRequestParams>();
    att::Handle handle = le16toh(params.handle);

    const auto* attr = db()->FindAttribute(handle);
    if (!attr) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle);
      return;
    }

    fit::result<att::ErrorCode> status =
        att::CheckWritePermissions(attr->write_reqs(), att_->security());
    if (status.is_error()) {
      att_->ReplyWithError(tid, handle, status.error_value());
      return;
    }

    // Attributes with a static value cannot be written.
    if (attr->value()) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kWriteNotPermitted);
      return;
    }

    auto value_view = packet.payload_data().view(sizeof(params.handle));
    if (value_view.size() > att::kMaxAttributeValueLength) {
      att_->ReplyWithError(
          tid, handle, att::ErrorCode::kInvalidAttributeValueLength);
      return;
    }

    auto self = weak_self_.GetWeakPtr();
    auto result_cb = [self, tid, handle](fit::result<att::ErrorCode> status) {
      if (!self.is_alive())
        return;

      if (status.is_error()) {
        self->att_->ReplyWithError(tid, handle, status.error_value());
        return;
      }

      auto buffer = NewBuffer(1);
      (*buffer)[0] = att::kWriteResponse;
      self->att_->Reply(tid, std::move(buffer));
    };

    if (!attr->WriteAsync(peer_id_, 0, value_view, result_cb)) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kWriteNotPermitted);
    }
  }

  void OnPrepareWriteRequest(att::Bearer::TransactionId tid,
                             const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kPrepareWriteRequest);

    if (packet.payload_size() < sizeof(att::PrepareWriteRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::PrepareWriteRequestParams>();
    att::Handle handle = le16toh(params.handle);
    uint16_t offset = le16toh(params.offset);
    auto value_view = packet.payload_data().view(sizeof(params.handle) +
                                                 sizeof(params.offset));

    if (prepare_queue_.size() >= att::kPrepareQueueMaxCapacity) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kPrepareQueueFull);
      return;
    }

    // Validate attribute handle and perform security checks (see Vol 3, Part F,
    // 3.4.6.1 for required checks)
    const auto* attr = db()->FindAttribute(handle);
    if (!attr) {
      att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle);
      return;
    }

    fit::result<att::ErrorCode> status =
        att::CheckWritePermissions(attr->write_reqs(), att_->security());
    if (status.is_error()) {
      att_->ReplyWithError(tid, handle, status.error_value());
      return;
    }

    prepare_queue_.push(att::QueuedWrite(handle, offset, value_view));

    // Reply back with the request payload.
    auto buffer = std::make_unique<DynamicByteBuffer>(packet.size());
    att::PacketWriter writer(att::kPrepareWriteResponse, buffer.get());
    writer.mutable_payload_data().Write(packet.payload_data());

    att_->Reply(tid, std::move(buffer));
  }

  void OnExecuteWriteRequest(att::Bearer::TransactionId tid,
                             const att::PacketReader& packet) {
    BT_DEBUG_ASSERT(packet.opcode() == att::kExecuteWriteRequest);

    if (packet.payload_size() != sizeof(att::ExecuteWriteRequestParams)) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    const auto& params = packet.payload<att::ExecuteWriteRequestParams>();
    if (params.flags == att::ExecuteWriteFlag::kCancelAll) {
      prepare_queue_ = {};

      auto buffer = std::make_unique<DynamicByteBuffer>(1);
      att::PacketWriter writer(att::kExecuteWriteResponse, buffer.get());
      att_->Reply(tid, std::move(buffer));
      return;
    }

    if (params.flags != att::ExecuteWriteFlag::kWritePending) {
      att_->ReplyWithError(
          tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU);
      return;
    }

    auto self = weak_self_.GetWeakPtr();
    auto result_cb = [self,
                      tid](fit::result<std::tuple<att::Handle, att::ErrorCode>>
                               result) mutable {
      if (!self.is_alive())
        return;

      if (result.is_error()) {
        auto [handle, ecode] = result.error_value();
        self->att_->ReplyWithError(tid, handle, ecode);
        return;
      }

      auto rsp = std::make_unique<DynamicByteBuffer>(1);
      att::PacketWriter writer(att::kExecuteWriteResponse, rsp.get());
      self->att_->Reply(tid, std::move(rsp));
    };
    db()->ExecuteWriteQueue(peer_id_,
                            std::move(prepare_queue_),
                            att_->security(),
                            std::move(result_cb));
  }

  // Helper function to serve the Read By Type and Read By Group Type requests.
  // This searches |db| for attributes with the given |type| and adds as many
  // attributes as it can fit within the given |max_data_list_size|. The
  // attribute value that should be included within each attribute data list
  // entry is returned in |out_value_size|.
  //
  // If the result is a dynamic attribute, |out_results| will contain at most
  // one entry. |out_value_size| will point to an undefined value in that case.
  //
  // On error, returns an error code that can be used in a ATT Error Response.
  fit::result<att::ErrorCode> ReadByTypeHelper(
      att::Handle start,
      att::Handle end,
      const UUID& type,
      bool group_type,
      size_t max_data_list_size,
      size_t max_value_size,
      size_t entry_prefix_size,
      size_t* out_value_size,
      std::list<const att::Attribute*>* out_results) {
    BT_DEBUG_ASSERT(out_results);
    BT_DEBUG_ASSERT(out_value_size);

    if (start == att::kInvalidHandle || start > end)
      return fit::error(att::ErrorCode::kInvalidHandle);

    auto iter = db()->GetIterator(start, end, &type, group_type);
    if (iter.AtEnd())
      return fit::error(att::ErrorCode::kAttributeNotFound);

    // |value_size| is the size of the complete attribute value for each result
    // entry. |entry_size| = |value_size| + |entry_prefix_size|. We store these
    // separately to avoid recalculating one every it gets checked.
    size_t value_size;
    size_t entry_size;
    std::list<const att::Attribute*> results;

    for (; !iter.AtEnd(); iter.Advance()) {
      const auto* attr = iter.get();
      BT_DEBUG_ASSERT(attr);

      fit::result<att::ErrorCode> security_result =
          att::CheckReadPermissions(attr->read_reqs(), att_->security());
      if (security_result.is_error()) {
        // Return error only if this is the first result that matched. We simply
        // stop the search otherwise.
        if (results.empty()) {
          return security_result;
        }
        break;
      }

      // The first result determines |value_size| and |entry_size|.
      if (results.empty()) {
        if (!attr->value()) {
          // If the first value is dynamic then this is the only attribute that
          // this call will return. No need to calculate the value size.
          results.push_back(attr);
          break;
        }

        value_size = attr->value()->size();  // untruncated value size
        entry_size =
            std::min(std::min(value_size, max_value_size) + entry_prefix_size,
                     max_data_list_size);

        // Actual value size to include in a PDU.
        *out_value_size = entry_size - entry_prefix_size;

      } else if (!attr->value() || attr->value()->size() != value_size ||
                 entry_size > max_data_list_size) {
        // Stop the search and exclude this attribute because either:
        // a. we ran into a dynamic value in a result that contains static
        // values, b. the matching attribute has a different value size than the
        // first
        //    attribute,
        // c. there is no remaining space in the response PDU.
        break;
      }

      results.push_back(attr);
      max_data_list_size -= entry_size;
    }

    if (results.empty())
      return fit::error(att::ErrorCode::kAttributeNotFound);

    *out_results = std::move(results);
    return fit::ok();
  }

  PeerId peer_id_;
  LocalServiceManager::WeakPtr local_services_;
  att::Bearer::WeakPtr att_;

  // The queue data structure used for queued writes (see Vol 3, Part F, 3.4.6).
  att::PrepareWriteQueue prepare_queue_;

  // ATT protocol request handler IDs
  // TODO(armansito): Storing all these IDs here feels wasteful. Provide a way
  // to unregister GATT server callbacks collectively from an att::Bearer, given
  // that it's server-role functionalities are uniquely handled by this class.
  att::Bearer::HandlerId exchange_mtu_id_;
  att::Bearer::HandlerId find_information_id_;
  att::Bearer::HandlerId read_by_group_type_id_;
  att::Bearer::HandlerId read_by_type_id_;
  att::Bearer::HandlerId read_req_id_;
  att::Bearer::HandlerId write_req_id_;
  att::Bearer::HandlerId write_cmd_id_;
  att::Bearer::HandlerId read_blob_req_id_;
  att::Bearer::HandlerId find_by_type_value_id_;
  att::Bearer::HandlerId prepare_write_id_;
  att::Bearer::HandlerId exec_write_id_;

  WeakSelf<AttBasedServer> weak_self_;

  BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(AttBasedServer);
};

// static
std::unique_ptr<Server> Server::Create(
    PeerId peer_id,
    LocalServiceManager::WeakPtr local_services,
    att::Bearer::WeakPtr bearer) {
  return std::make_unique<AttBasedServer>(
      peer_id, std::move(local_services), std::move(bearer));
}
}  // namespace bt::gatt
