// Copyright 2024 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/fuchsia/host/fidl/gatt2_server_server.h"

#include <fuchsia/bluetooth/gatt2/cpp/fidl_test_base.h>
#include <gmock/gmock.h>
#include <zircon/status.h>

#include <algorithm>
#include <optional>

#include "fuchsia/bluetooth/cpp/fidl.h"
#include "fuchsia/bluetooth/gatt2/cpp/fidl.h"
#include "lib/fidl/cpp/interface_handle.h"
#include "pw_bluetooth_sapphire/fuchsia/host/fidl/fake_gatt_fixture.h"
#include "pw_unit_test/framework.h"

namespace bthost {
namespace {

namespace fbg = fuchsia::bluetooth::gatt2;
namespace fbt = fuchsia::bluetooth;

const std::vector<uint8_t> kBuffer = {0x00, 0x01, 0x02};

fbg::Characteristic BuildSimpleCharacteristic(uint64_t handle) {
  fbg::Characteristic chrc;
  fbg::Handle chrc_handle{handle};
  chrc.set_handle(chrc_handle);
  chrc.set_type(fbt::Uuid{{6}});
  chrc.set_properties(fbg::CharacteristicPropertyBits::READ);
  fbg::AttributePermissions permissions;
  fbg::SecurityRequirements security;
  security.set_encryption_required(true);
  permissions.set_read(std::move(security));
  chrc.set_permissions(std::move(permissions));
  return chrc;
}

fbg::ServiceInfo BuildSimpleService(uint64_t svc_handle = 1,
                                    bt::UInt128 svc_type = {5},
                                    uint64_t chrc_handle = 2) {
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{svc_handle});
  svc_info.set_type(fbt::Uuid{svc_type});

  std::vector<fbg::Characteristic> chrcs;
  chrcs.push_back(BuildSimpleCharacteristic(chrc_handle));
  svc_info.set_characteristics(std::move(chrcs));

  return svc_info;
}

class MockLocalService final : private ServerBase<fbg::LocalService> {
 public:
  using ReadValueFunction =
      fit::function<void(fbt::PeerId, fbg::Handle, int32_t, ReadValueCallback)>;
  using WriteValueFunction = fit::function<void(
      fbg::LocalServiceWriteValueRequest, WriteValueCallback)>;
  using CccFunction = fit::function<void(fbt::PeerId,
                                         fbg::Handle,
                                         bool,
                                         bool,
                                         CharacteristicConfigurationCallback)>;

  MockLocalService(fidl::InterfaceRequest<fbg::LocalService> request)
      : ServerBase<fbg::LocalService>(this, std::move(request)) {
    set_error_handler([this](zx_status_t status) { error_ = status; });
  }

  void set_read_value_function(ReadValueFunction func) {
    read_value_func_ = std::move(func);
  }
  void set_write_value_function(WriteValueFunction func) {
    write_value_func_ = std::move(func);
  }
  void set_ccc_function(CccFunction func) { ccc_func_ = std::move(func); }

  int credits() const { return credits_; }

  const std::vector<uint8_t>& credit_log() const { return credits_log_; }

  void NotifyValue(fbg::ValueChangedParameters update) {
    credits_--;
    binding()->events().OnNotifyValue(std::move(update));
  }

  void IndicateValue(fbg::ValueChangedParameters update,
                     zx::eventpair confirmation) {
    credits_--;
    binding()->events().OnIndicateValue(std::move(update),
                                        std::move(confirmation));
  }

  std::optional<zx_status_t> error() const { return error_; }

 private:
  // fbg::LocalService overrides:
  void CharacteristicConfiguration(
      fbt::PeerId peer_id,
      fbg::Handle handle,
      bool notify,
      bool indicate,
      CharacteristicConfigurationCallback callback) override {
    if (ccc_func_) {
      ccc_func_(peer_id, handle, notify, indicate, std::move((callback)));
    }
  }
  void ReadValue(fbt::PeerId peer_id,
                 fbg::Handle handle,
                 int32_t offset,
                 ReadValueCallback callback) override {
    if (read_value_func_) {
      read_value_func_(peer_id, handle, offset, std::move(callback));
    }
  }
  void WriteValue(fbg::LocalServiceWriteValueRequest req,
                  WriteValueCallback callback) override {
    if (write_value_func_) {
      write_value_func_(std::move(req), std::move(callback));
    }
  }
  void PeerUpdate(fbg::LocalServicePeerUpdateRequest req,
                  PeerUpdateCallback callback) override {}
  void ValueChangedCredit(uint8_t additional_credit) override {
    credits_ += additional_credit;
    credits_log_.push_back(additional_credit);
  }

  ReadValueFunction read_value_func_;
  WriteValueFunction write_value_func_;
  CccFunction ccc_func_;
  // Use signed integer because in tests it is possible to have negative
  // credits.
  int credits_ = fbg::INITIAL_VALUE_CHANGED_CREDITS;
  std::vector<uint8_t> credits_log_;
  std::optional<zx_status_t> error_;
};

class Gatt2ServerServerTest : public bt::fidl::testing::FakeGattFixture {
 public:
  ~Gatt2ServerServerTest() override = default;

  Gatt2ServerServerTest() {}

  void SetUp() override {
    // Create and connect to production GATT2 Server implementation
    fidl::InterfaceHandle<fbg::Server> server_handle;
    server_ = std::make_unique<Gatt2ServerServer>(gatt()->GetWeakPtr(),
                                                  server_handle.NewRequest());
    server_ptr_ = server_handle.Bind();
  }

  void TearDown() override {}

 protected:
  fbg::ServerPtr& server_ptr() { return server_ptr_; }

  void DestroyServer() { server_.reset(); }

 private:
  // Proxy interface to the GATT2 Server implementation.
  fbg::ServerPtr server_ptr_;
  // Raw GATT2 Server implementation
  std::unique_ptr<Gatt2ServerServer> server_;
};

TEST_F(Gatt2ServerServerTest,
       PublishAndRemoveServiceWithTwoCharacteristicsSuccess) {
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{1});
  bt::UInt128 svc_type = {5};
  svc_info.set_type(fbt::Uuid{svc_type});

  std::vector<fbg::Characteristic> characteristics;
  characteristics.push_back(BuildSimpleCharacteristic(/*handle=*/2));
  characteristics.push_back(BuildSimpleCharacteristic(/*handle=*/3));
  svc_info.set_characteristics(std::move(characteristics));

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
  fidl::InterfaceRequest<fbg::LocalService> request =
      local_service_handle_0.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle_0), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  bt::gatt::Service* service =
      fake_gatt()->local_services().begin()->second.service.get();
  EXPECT_EQ(service->type(), svc_type);
  EXPECT_TRUE(service->primary());
  ASSERT_EQ(service->characteristics().size(), 2u);
  EXPECT_EQ(service->characteristics()[0]->id(), 2u);
  EXPECT_EQ(service->characteristics()[1]->id(), 3u);

  request.Close(ZX_ERR_PEER_CLOSED);
  RunLoopUntilIdle();
  EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, DestroyingServerUnregistersService) {
  bt::UInt128 svc_type = {5};
  fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, svc_type);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  fidl::InterfaceRequest<fbg::LocalService> request =
      local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  EXPECT_EQ(
      fake_gatt()->local_services().begin()->second.service->type().value(),
      svc_type);
  DestroyServer();
  EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, PublishServiceWithoutHandleFails) {
  fbg::ServiceInfo svc_info = BuildSimpleService();
  svc_info.clear_handle();

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  auto request = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    ASSERT_TRUE(res.is_err());
    EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_SERVICE_HANDLE);
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, PublishServiceWithoutTypeFails) {
  fbg::ServiceInfo svc_info = BuildSimpleService();
  svc_info.clear_type();

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  auto request = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    ASSERT_TRUE(res.is_err());
    EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_UUID);
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, PublishServiceWithoutCharacteristicsFails) {
  fbg::ServiceInfo svc_info = BuildSimpleService();
  svc_info.clear_characteristics();

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  auto request = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    ASSERT_TRUE(res.is_err());
    EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_CHARACTERISTICS);
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, PublishServiceWithReusedHandleFails) {
  fbg::ServiceInfo svc_info_0 = BuildSimpleService();

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
  auto request_0 = local_service_handle_0.NewRequest();

  int cb_count_0 = 0;
  auto cb_0 = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count_0++;
  };
  server_ptr()->PublishService(std::move(svc_info_0),
                               std::move(local_service_handle_0),
                               std::move(cb_0));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count_0, 1);
  EXPECT_EQ(fake_gatt()->local_services().size(), 1u);

  fbg::ServiceInfo svc_info_1 = BuildSimpleService();

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_1;
  auto request_1 = local_service_handle_1.NewRequest();

  int cb_count_1 = 0;
  auto cb_1 = [&](fbg::Server_PublishService_Result res) {
    ASSERT_TRUE(res.is_err());
    EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_SERVICE_HANDLE);
    cb_count_1++;
  };
  server_ptr()->PublishService(std::move(svc_info_1),
                               std::move(local_service_handle_1),
                               std::move(cb_1));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count_1, 1);
  EXPECT_EQ(fake_gatt()->local_services().size(), 1u);
}

TEST_F(Gatt2ServerServerTest,
       PublishServiceWithReusedHandleAcrossTwoGattServersSuccceeds) {
  // Both services are identical.
  fbg::ServiceInfo svc_info_0 = BuildSimpleService();
  fbg::ServiceInfo svc_info_1 = BuildSimpleService();

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
  auto request_0 = local_service_handle_0.NewRequest();

  int cb_count_0 = 0;
  auto cb_0 = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count_0++;
  };
  server_ptr()->PublishService(std::move(svc_info_0),
                               std::move(local_service_handle_0),
                               std::move(cb_0));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count_0, 1);
  EXPECT_EQ(fake_gatt()->local_services().size(), 1u);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_1;
  auto request_1 = local_service_handle_1.NewRequest();

  int cb_count_1 = 0;
  auto cb_1 = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count_1++;
  };

  // Create and connect to a second GATT Server implementation
  fidl::InterfaceHandle<fbg::Server> server_handle_1;
  auto server_1 = std::make_unique<Gatt2ServerServer>(
      gatt()->GetWeakPtr(), server_handle_1.NewRequest());
  fidl::InterfacePtr<fuchsia::bluetooth::gatt2::Server> server_ptr_1 =
      server_handle_1.Bind();
  // Publish an identical service.
  server_ptr_1->PublishService(std::move(svc_info_1),
                               std::move(local_service_handle_1),
                               std::move(cb_1));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count_1, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 2u);
}

TEST_F(Gatt2ServerServerTest, PublishTwoServicesSuccess) {
  bt::UInt128 svc_type_0 = {5};
  fbg::ServiceInfo svc_info_0 =
      BuildSimpleService(/*svc_handle=*/1, svc_type_0);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
  fidl::InterfaceRequest<fbg::LocalService> request_0 =
      local_service_handle_0.NewRequest();

  int cb_count_0 = 0;
  auto cb_0 = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count_0++;
  };
  server_ptr()->PublishService(std::move(svc_info_0),
                               std::move(local_service_handle_0),
                               std::move(cb_0));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count_0, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  bt::gatt::Service* service =
      fake_gatt()->local_services().begin()->second.service.get();
  EXPECT_EQ(service->type(), svc_type_0);

  bt::UInt128 svc_type_1 = {6};
  fbg::ServiceInfo svc_info_1 =
      BuildSimpleService(/*svc_handle=*/9, svc_type_1);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_1;
  fidl::InterfaceRequest<fbg::LocalService> request_1 =
      local_service_handle_1.NewRequest();

  int cb_count_1 = 0;
  auto cb_1 = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count_1++;
  };
  server_ptr()->PublishService(std::move(svc_info_1),
                               std::move(local_service_handle_1),
                               std::move(cb_1));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count_1, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 2u);
  auto svc_iter = fake_gatt()->local_services().begin();
  EXPECT_EQ(svc_iter->second.service->type(), svc_type_0);
  svc_iter++;
  EXPECT_EQ(svc_iter->second.service->type(), svc_type_1);

  request_0.Close(ZX_ERR_PEER_CLOSED);
  request_1.Close(ZX_ERR_PEER_CLOSED);
  RunLoopUntilIdle();
  EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, PublishSecondaryService) {
  bt::UInt128 svc_type = {5};
  fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, svc_type);
  svc_info.set_kind(fbg::ServiceKind::SECONDARY);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  fidl::InterfaceRequest<fbg::LocalService> request =
      local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();
  EXPECT_EQ(svc_iter->second.service->type(), svc_type);
  EXPECT_FALSE(svc_iter->second.service->primary());
}

TEST_F(Gatt2ServerServerTest, ReadSuccess) {
  uint64_t svc_handle = 1;
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{svc_handle});
  svc_info.set_type(fbt::Uuid{{5}});
  fbg::Characteristic chrc;
  fbg::Handle chrc_handle{2};
  chrc.set_handle(chrc_handle);
  chrc.set_type(fbt::Uuid{{0}});
  chrc.set_properties(fbg::CharacteristicPropertyBits::READ);
  chrc.set_permissions(fbg::AttributePermissions());
  std::vector<fbg::Characteristic> chrcs;
  chrcs.push_back(std::move(chrc));
  svc_info.set_characteristics(std::move(chrcs));

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc(local_service_handle.NewRequest());

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();
  ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);

  bt::gatt::IdType svc_id = svc_iter->first;
  bt::gatt::IdType chrc_id =
      svc_iter->second.service->characteristics()[0]->id();
  bt::PeerId peer_id(99);

  int read_value_count = 0;
  local_svc.set_read_value_function(
      [&](fbt::PeerId cb_peer_id,
          fbg::Handle handle,
          int32_t offset,
          fbg::LocalService::ReadValueCallback callback) {
        read_value_count++;
        EXPECT_EQ(peer_id.value(), cb_peer_id.value);
        EXPECT_EQ(handle.value, chrc_handle.value);
        EXPECT_EQ(offset, 3);
        callback(fpromise::ok(kBuffer));
      });

  int read_responder_count = 0;
  auto read_responder = [&](fit::result<bt::att::ErrorCode> status,
                            const bt::ByteBuffer& value) {
    read_responder_count++;
    EXPECT_TRUE(status.is_ok());
    EXPECT_THAT(value, ::testing::ElementsAreArray(kBuffer));
  };

  svc_iter->second.read_handler(
      peer_id, svc_id, chrc_id, /*offset=*/3, read_responder);
  RunLoopUntilIdle();
  EXPECT_EQ(read_value_count, 1);
  EXPECT_EQ(read_responder_count, 1);
}

TEST_F(Gatt2ServerServerTest, ReadErrorResponse) {
  uint64_t svc_handle = 1;
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{svc_handle});
  svc_info.set_type(fbt::Uuid{{5}});
  fbg::Characteristic chrc;
  fbg::Handle chrc_handle{2};
  chrc.set_handle(chrc_handle);
  chrc.set_type(fbt::Uuid{{0}});
  chrc.set_properties(fbg::CharacteristicPropertyBits::READ);
  chrc.set_permissions(fbg::AttributePermissions());
  std::vector<fbg::Characteristic> chrcs;
  chrcs.push_back(std::move(chrc));
  svc_info.set_characteristics(std::move(chrcs));

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc(local_service_handle.NewRequest());

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();
  ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);

  bt::gatt::IdType svc_id = svc_iter->first;
  bt::gatt::IdType chrc_id =
      svc_iter->second.service->characteristics()[0]->id();

  int read_value_count = 0;
  local_svc.set_read_value_function(
      [&](fbt::PeerId cb_peer_id,
          fbg::Handle handle,
          int32_t offset,
          fbg::LocalService::ReadValueCallback callback) {
        read_value_count++;
        callback(fpromise::error(fbg::Error::READ_NOT_PERMITTED));
      });

  int read_responder_count = 0;
  auto read_responder = [&](fit::result<bt::att::ErrorCode> status,
                            const bt::ByteBuffer& value) {
    read_responder_count++;
    ASSERT_TRUE(status.is_error());
    EXPECT_EQ(status.error_value(), bt::att::ErrorCode::kReadNotPermitted);
    EXPECT_EQ(value.size(), 0u);
  };

  svc_iter->second.read_handler(
      bt::PeerId(42), svc_id, chrc_id, /*offset=*/0, read_responder);
  RunLoopUntilIdle();
  EXPECT_EQ(read_value_count, 1);
  EXPECT_EQ(read_responder_count, 1);
}

TEST_F(Gatt2ServerServerTest, WriteSuccess) {
  uint64_t svc_handle = 1;
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{svc_handle});
  svc_info.set_type(fbt::Uuid{{5}});
  fbg::Characteristic chrc;
  fbg::Handle chrc_handle{2};
  chrc.set_handle(chrc_handle);
  chrc.set_type(fbt::Uuid{{0}});
  chrc.set_properties(fbg::CharacteristicPropertyBits::WRITE);
  chrc.set_permissions(fbg::AttributePermissions());
  std::vector<fbg::Characteristic> chrcs;
  chrcs.push_back(std::move(chrc));
  svc_info.set_characteristics(std::move(chrcs));

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc(local_service_handle.NewRequest());

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();
  ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);

  bt::gatt::IdType svc_id = svc_iter->first;
  bt::gatt::IdType chrc_id =
      svc_iter->second.service->characteristics()[0]->id();
  bt::PeerId peer_id(99);
  bt::StaticByteBuffer value_buffer(0x00, 0x01, 0x02);

  int write_value_count = 0;
  local_svc.set_write_value_function(
      [&](fbg::LocalServiceWriteValueRequest req,
          fbg::LocalService::WriteValueCallback cb) {
        write_value_count++;
        ASSERT_TRUE(req.has_peer_id());
        EXPECT_EQ(req.peer_id().value, peer_id.value());
        ASSERT_TRUE(req.has_handle());
        EXPECT_EQ(req.handle().value, chrc_handle.value);
        ASSERT_TRUE(req.has_offset());
        EXPECT_EQ(req.offset(), 3u);
        ASSERT_TRUE(req.has_value());
        EXPECT_THAT(req.value(), ::testing::ElementsAreArray(value_buffer));
        cb(fpromise::ok());
      });
  int write_responder_count = 0;
  auto write_responder = [&](fit::result<bt::att::ErrorCode> status) {
    write_responder_count++;
    EXPECT_TRUE(status.is_ok());
  };
  svc_iter->second.write_handler(peer_id,
                                 svc_id,
                                 chrc_id,
                                 /*offset=*/3,
                                 value_buffer,
                                 std::move(write_responder));
  RunLoopUntilIdle();
  EXPECT_EQ(write_value_count, 1);
  EXPECT_EQ(write_responder_count, 1);
}

TEST_F(Gatt2ServerServerTest, WriteError) {
  uint64_t svc_handle = 1;
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{svc_handle});
  svc_info.set_type(fbt::Uuid{{5}});
  fbg::Characteristic chrc;
  fbg::Handle chrc_handle{2};
  chrc.set_handle(chrc_handle);
  chrc.set_type(fbt::Uuid{{0}});
  chrc.set_properties(fbg::CharacteristicPropertyBits::WRITE);
  chrc.set_permissions(fbg::AttributePermissions());
  std::vector<fbg::Characteristic> chrcs;
  chrcs.push_back(std::move(chrc));
  svc_info.set_characteristics(std::move(chrcs));

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc(local_service_handle.NewRequest());

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();
  ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);

  bt::gatt::IdType svc_id = svc_iter->first;
  bt::gatt::IdType chrc_id =
      svc_iter->second.service->characteristics()[0]->id();

  int write_value_count = 0;
  local_svc.set_write_value_function(
      [&](fbg::LocalServiceWriteValueRequest req,
          fbg::LocalService::WriteValueCallback cb) {
        write_value_count++;
        cb(fpromise::error(fbg::Error::WRITE_NOT_PERMITTED));
      });
  int write_responder_count = 0;
  auto write_responder = [&](fit::result<bt::att::ErrorCode> status) {
    write_responder_count++;
    ASSERT_TRUE(status.is_error());
    EXPECT_EQ(status.error_value(), bt::att::ErrorCode::kWriteNotPermitted);
  };
  svc_iter->second.write_handler(bt::PeerId(4),
                                 svc_id,
                                 chrc_id,
                                 /*offset=*/0,
                                 bt::BufferView(),
                                 std::move(write_responder));
  RunLoopUntilIdle();
  EXPECT_EQ(write_value_count, 1);
  EXPECT_EQ(write_responder_count, 1);
}

TEST_F(Gatt2ServerServerTest, ClientCharacteristicConfiguration) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();
  bt::gatt::IdType svc_id = svc_iter->first;

  bt::PeerId peer_id(4);
  int ccc_count = 0;
  local_svc.set_ccc_function(
      [&](fbt::PeerId cb_peer_id,
          fbg::Handle handle,
          bool notify,
          bool indicate,
          fbg::LocalService::CharacteristicConfigurationCallback cb) {
        ccc_count++;
        EXPECT_EQ(peer_id.value(), cb_peer_id.value);
        EXPECT_EQ(handle.value, chrc_handle);
        EXPECT_TRUE(notify);
        EXPECT_FALSE(indicate);
        cb();
      });
  svc_iter->second.ccc_callback(
      svc_id, chrc_handle, peer_id, /*notify=*/true, /*indicate=*/false);
  RunLoopUntilIdle();
  EXPECT_EQ(ccc_count, 1);
}

TEST_F(Gatt2ServerServerTest, IndicateAllPeers) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  fbg::ValueChangedParameters params_0;
  params_0.set_handle(fbg::Handle{chrc_handle});
  std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
  params_0.set_value(buffer);
  zx::eventpair confirm_ours_0;
  zx::eventpair confirm_theirs_0;
  zx::eventpair::create(/*options=*/0, &confirm_ours_0, &confirm_theirs_0);
  local_svc.IndicateValue(std::move(params_0), std::move(confirm_theirs_0));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 1u);
  EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[0].value, buffer);

  // Eventpair should not be signalled until indicate_cb called.
  zx_signals_t observed;
  zx_status_t status =
      confirm_ours_0.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                              /*deadline=*/zx::time(0),
                              &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
  EXPECT_EQ(observed, 0u);

  svc_iter->second.updates[0].indicate_cb(fit::ok());

  status =
      confirm_ours_0.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                              /*deadline=*/zx::time(0),
                              &observed);
  ASSERT_EQ(status, ZX_OK);
  // The eventpair should have been signaled before it was closed.
  EXPECT_EQ(observed, ZX_EVENTPAIR_SIGNALED | ZX_EVENTPAIR_PEER_CLOSED);
  observed = 0;

  // Also test with an empty peer_ids vector
  fbg::ValueChangedParameters params_1;
  params_1.set_peer_ids({});
  params_1.set_handle(fbg::Handle{chrc_handle});
  params_1.set_value(buffer);
  zx::eventpair confirm_ours_1;
  zx::eventpair confirm_theirs_1;
  zx::eventpair::create(/*options=*/0, &confirm_ours_1, &confirm_theirs_1);
  local_svc.IndicateValue(std::move(params_1), std::move(confirm_theirs_1));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 2u);
  status =
      confirm_ours_1.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                              /*deadline=*/zx::time(0),
                              &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
  EXPECT_EQ(observed, 0u);

  svc_iter->second.updates[1].indicate_cb(fit::ok());

  status =
      confirm_ours_1.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                              /*deadline=*/zx::time(0),
                              &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_SIGNALED | ZX_EVENTPAIR_PEER_CLOSED);
}

TEST_F(Gatt2ServerServerTest, IndicateAllPeersError) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  fbg::ValueChangedParameters params;
  params.set_handle(fbg::Handle{chrc_handle});
  std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
  params.set_value(buffer);
  zx::eventpair confirm_ours;
  zx::eventpair confirm_theirs;
  zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
  local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 1u);
  EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[0].value, buffer);

  // Eventpair should not be signalled until indicate_cb called.
  zx_signals_t observed;
  zx_status_t status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
  EXPECT_EQ(observed, 0u);

  svc_iter->second.updates[0].indicate_cb(
      fit::error(bt::att::ErrorCode::kUnlikelyError));

  status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
}

TEST_F(Gatt2ServerServerTest,
       IndicateValueChangedParametersMissingHandleClosesService) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  ASSERT_FALSE(local_svc.error());

  // No handle is set.
  fbg::ValueChangedParameters params_0;
  std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
  params_0.set_value(buffer);
  zx::eventpair confirm_ours_0;
  zx::eventpair confirm_theirs_0;
  zx::eventpair::create(/*options=*/0, &confirm_ours_0, &confirm_theirs_0);
  local_svc.IndicateValue(std::move(params_0), std::move(confirm_theirs_0));
  RunLoopUntilIdle();
  ASSERT_TRUE(local_svc.error());
  EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
  EXPECT_TRUE(fake_gatt()->local_services().empty());
  zx_signals_t observed;
  zx_status_t status =
      confirm_ours_0.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                              /*deadline=*/zx::time(0),
                              &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
}

TEST_F(Gatt2ServerServerTest,
       IndicateValueChangedParametersMissingValueClosesService) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_FALSE(local_svc.error());
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);

  // No value is set.
  fbg::ValueChangedParameters params_1;
  params_1.set_handle(fbg::Handle{chrc_handle});
  zx::eventpair confirm_ours_1;
  zx::eventpair confirm_theirs_1;
  zx::eventpair::create(/*options=*/0, &confirm_ours_1, &confirm_theirs_1);
  local_svc.IndicateValue(std::move(params_1), std::move(confirm_theirs_1));
  RunLoopUntilIdle();
  ASSERT_TRUE(local_svc.error());
  EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
  EXPECT_TRUE(fake_gatt()->local_services().empty());
  zx_signals_t observed;
  zx_status_t status =
      confirm_ours_1.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                              /*deadline=*/zx::time(0),
                              &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
}

TEST_F(Gatt2ServerServerTest, Indicate2PeersSuccess) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  bt::PeerId peer_0(0);
  bt::PeerId peer_1(1);
  fbg::ValueChangedParameters params;
  params.set_handle(fbg::Handle{chrc_handle});
  std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
  params.set_value(buffer);
  params.set_peer_ids(
      {fbt::PeerId{peer_0.value()}, fbt::PeerId{peer_1.value()}});
  zx::eventpair confirm_ours;
  zx::eventpair confirm_theirs;
  zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
  local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 2u);
  EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[0].value, buffer);
  EXPECT_THAT(svc_iter->second.updates[0].peer, ::testing::Optional(peer_0));
  EXPECT_EQ(svc_iter->second.updates[1].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[1].value, buffer);
  EXPECT_THAT(svc_iter->second.updates[1].peer, ::testing::Optional(peer_1));

  // Eventpair should not be signaled until indicate_cb called for both peers.
  zx_signals_t observed;
  zx_status_t status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);

  svc_iter->second.updates[1].indicate_cb(fit::ok());

  status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);

  svc_iter->second.updates[0].indicate_cb(fit::ok());

  status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED);
}

TEST_F(Gatt2ServerServerTest, Indicate2PeersFirstOneFails) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  fbg::ValueChangedParameters params;
  params.set_handle(fbg::Handle{chrc_handle});
  params.set_value(kBuffer);
  params.set_peer_ids({fbt::PeerId{0}, fbt::PeerId{1}});
  zx::eventpair confirm_ours;
  zx::eventpair confirm_theirs;
  zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
  local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 2u);

  zx_signals_t observed;
  zx_status_t status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);

  svc_iter->second.updates[0].indicate_cb(
      fit::error(bt::att::ErrorCode::kUnlikelyError));

  status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);

  svc_iter->second.updates[1].indicate_cb(fit::ok());
}

TEST_F(Gatt2ServerServerTest, Indicate2PeersBothFail) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  fbg::ValueChangedParameters params;
  params.set_handle(fbg::Handle{chrc_handle});
  params.set_value(kBuffer);
  params.set_peer_ids({fbt::PeerId{0}, fbt::PeerId{1}});
  zx::eventpair confirm_ours;
  zx::eventpair confirm_theirs;
  zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
  local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 2u);

  zx_signals_t observed;
  zx_status_t status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_ERR_TIMED_OUT);

  svc_iter->second.updates[1].indicate_cb(
      fit::error(bt::att::ErrorCode::kUnlikelyError));

  status =
      confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
                            /*deadline=*/zx::time(0),
                            &observed);
  ASSERT_EQ(status, ZX_OK);
  EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);

  svc_iter->second.updates[0].indicate_cb(
      fit::error(bt::att::ErrorCode::kUnlikelyError));
}

TEST_F(Gatt2ServerServerTest, NotifyAllPeers) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  fbg::ValueChangedParameters params_0;
  params_0.set_handle(fbg::Handle{chrc_handle});
  params_0.set_value(kBuffer);
  local_svc.NotifyValue(std::move(params_0));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 1u);
  EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[0].value, kBuffer);
  EXPECT_FALSE(svc_iter->second.updates[0].peer);
  EXPECT_FALSE(svc_iter->second.updates[0].indicate_cb);

  // Also test with an empty peer_ids vector
  fbg::ValueChangedParameters params_1;
  params_1.set_peer_ids({});
  params_1.set_handle(fbg::Handle{chrc_handle});
  params_1.set_value(kBuffer);
  local_svc.NotifyValue(std::move(params_1));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 2u);
  EXPECT_EQ(svc_iter->second.updates[1].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[1].value, kBuffer);
  EXPECT_FALSE(svc_iter->second.updates[1].peer);
  EXPECT_FALSE(svc_iter->second.updates[1].indicate_cb);
}

TEST_F(Gatt2ServerServerTest, NotifyTwoPeers) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  fbg::ValueChangedParameters params_0;
  params_0.set_handle(fbg::Handle{chrc_handle});
  params_0.set_value(kBuffer);
  params_0.set_peer_ids({fbt::PeerId{0}, fbt::PeerId{1}});
  local_svc.NotifyValue(std::move(params_0));
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(), 2u);
  EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[0].value, kBuffer);
  EXPECT_THAT(svc_iter->second.updates[0].peer,
              ::testing::Optional(bt::PeerId(0)));
  EXPECT_FALSE(svc_iter->second.updates[0].indicate_cb);
  EXPECT_EQ(svc_iter->second.updates[1].chrc_id, chrc_handle);
  EXPECT_EQ(svc_iter->second.updates[1].value, kBuffer);
  EXPECT_THAT(svc_iter->second.updates[1].peer,
              ::testing::Optional(bt::PeerId(1)));
  EXPECT_FALSE(svc_iter->second.updates[1].indicate_cb);
}

TEST_F(Gatt2ServerServerTest,
       NotifyInvalidParametersMissingHandleClosesService) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  ASSERT_FALSE(local_svc.error());

  // Missing handle.
  fbg::ValueChangedParameters params_0;
  params_0.set_value(kBuffer);
  local_svc.NotifyValue(std::move(params_0));
  RunLoopUntilIdle();
  ASSERT_TRUE(local_svc.error());
  EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
  EXPECT_TRUE(fake_gatt()->local_services().empty());
}

TEST_F(Gatt2ServerServerTest,
       NotifyInvalidParametersMissingValueClosesService) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  ASSERT_FALSE(local_svc.error());

  // Missing value.
  fbg::ValueChangedParameters params_1;
  params_1.set_handle(fbg::Handle{chrc_handle});
  local_svc.NotifyValue(std::move(params_1));
  RunLoopUntilIdle();
  ASSERT_TRUE(local_svc.error());
  EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
  EXPECT_TRUE(fake_gatt()->local_services().empty());
}

TEST_F(Gatt2ServerServerTest, ValueChangedFlowControl) {
  const uint64_t chrc_handle = 2;
  fbg::ServiceInfo svc_info =
      BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  MockLocalService local_svc = local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    EXPECT_TRUE(res.is_response());
    cb_count++;
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
  auto svc_iter = fake_gatt()->local_services().begin();

  for (size_t i = 0; i < 3 * fbg::INITIAL_VALUE_CHANGED_CREDITS; i++) {
    fbg::ValueChangedParameters params;
    params.set_handle(fbg::Handle{chrc_handle});
    params.set_value(kBuffer);
    local_svc.NotifyValue(std::move(params));
  }
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(),
            3 * fbg::INITIAL_VALUE_CHANGED_CREDITS);
  EXPECT_GT(local_svc.credits(), 0);
  EXPECT_LE(local_svc.credits(),
            static_cast<int>(fbg::INITIAL_VALUE_CHANGED_CREDITS));
  EXPECT_GE(local_svc.credit_log().size(), 2u);

  for (size_t i = 0; i < 3 * fbg::INITIAL_VALUE_CHANGED_CREDITS; i++) {
    fbg::ValueChangedParameters params;
    params.set_handle(fbg::Handle{chrc_handle});
    params.set_value(kBuffer);
    zx::eventpair confirm_ours;
    zx::eventpair confirm_theirs;
    zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
    local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
  }
  RunLoopUntilIdle();
  ASSERT_EQ(svc_iter->second.updates.size(),
            6 * fbg::INITIAL_VALUE_CHANGED_CREDITS);
  EXPECT_GT(local_svc.credits(), 0);
  EXPECT_LE(local_svc.credits(),
            static_cast<int>(fbg::INITIAL_VALUE_CHANGED_CREDITS));
  EXPECT_GE(local_svc.credit_log().size(), 5u);
}

TEST_F(Gatt2ServerServerTest, PublishServiceWithInvalidCharacteristic) {
  fbg::ServiceInfo svc_info;
  svc_info.set_handle(fbg::ServiceHandle{1});
  bt::UInt128 svc_type = {5};
  svc_info.set_type(fbt::Uuid{svc_type});

  fbg::Characteristic chrc_0;
  fbg::Handle chrc_handle_0{2};
  chrc_0.set_handle(chrc_handle_0);

  std::vector<fbg::Characteristic> characteristics;
  characteristics.push_back(std::move(chrc_0));
  svc_info.set_characteristics(std::move(characteristics));

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
  fidl::InterfaceRequest<fbg::LocalService> request =
      local_service_handle_0.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    cb_count++;
    ASSERT_TRUE(res.is_err());
    EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_CHARACTERISTICS);
  };
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle_0), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 0u);
}

TEST_F(Gatt2ServerServerTest, PublishServiceReturnsInvalidId) {
  bt::UInt128 svc_type = {5};
  fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, svc_type);

  fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
  fidl::InterfaceRequest<fbg::LocalService> request =
      local_service_handle.NewRequest();

  int cb_count = 0;
  auto cb = [&](fbg::Server_PublishService_Result res) {
    cb_count++;
    ASSERT_TRUE(res.is_err());
    EXPECT_EQ(res.err(), fbg::PublishServiceError::UNLIKELY_ERROR);
  };

  fake_gatt()->set_register_service_fails(true);
  server_ptr()->PublishService(
      std::move(svc_info), std::move(local_service_handle), std::move(cb));
  RunLoopUntilIdle();
  EXPECT_EQ(cb_count, 1);
  ASSERT_EQ(fake_gatt()->local_services().size(), 0u);
}

}  // namespace
}  // namespace bthost
