text: // Copyright 2018 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "generic_attribute_service.h"
  
  #include <zircon/assert.h>
  
  #include <gtest/gtest.h>
  
  #include "src/connectivity/bluetooth/core/bt-host/gatt/gatt_defs.h"
  #include "src/connectivity/bluetooth/core/bt-host/gatt/persisted_data.h"
  
  namespace bt::gatt {
  namespace {
  // Handles for the third attribute (Service Changed characteristic) and fourth
  // attribute (corresponding client config).
  constexpr att::Handle kChrcHandle = 0x0003;
  constexpr att::Handle kCCCHandle = 0x0004;
  constexpr PeerId kTestPeerId(1);
  constexpr uint16_t kEnableInd = 0x0002;
  
  class GenericAttributeServiceTest : public ::testing::Test {
   protected:
    bool WriteServiceChangedCcc(PeerId peer_id, uint16_t ccc_value,
                                fitx::result<att::ErrorCode>* out_status) {
      ZX_ASSERT(out_status);
  
      auto* attr = mgr.database()->FindAttribute(kCCCHandle);
      ZX_ASSERT(attr);
      auto result_cb = [&out_status](auto cb_status) { *out_status = cb_status; };
      uint16_t value = htole16(ccc_value);
      return attr->WriteAsync(peer_id, 0u, BufferView(&value, sizeof(value)), result_cb);
    }
  
    LocalServiceManager mgr;
  };
  
  // Test registration and unregistration of the local GATT service itself.
  TEST_F(GenericAttributeServiceTest, RegisterUnregister) {
    {
      GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
  
      // Check that the local attribute database has a grouping for the GATT GATT
      // service with four attributes.
      auto iter = mgr.database()->groupings().begin();
      EXPECT_TRUE(iter->complete());
      EXPECT_EQ(4u, iter->attributes().size());
      EXPECT_TRUE(iter->active());
      EXPECT_EQ(0x0001, iter->start_handle());
      EXPECT_EQ(0x0004, iter->end_handle());
      EXPECT_EQ(types::kPrimaryService, iter->group_type());
  
      auto const* ccc_attr = mgr.database()->FindAttribute(kCCCHandle);
      ASSERT_TRUE(ccc_attr != nullptr);
      EXPECT_EQ(types::kClientCharacteristicConfig, ccc_attr->type());
    }
  
    // The service should now be unregistered, so no characeteristic attributes
    // should be active.
    auto const* chrc_attr = mgr.database()->FindAttribute(kChrcHandle);
    ASSERT_TRUE(chrc_attr == nullptr);
  }
  
  // Tests that registering the GATT service, enabling indication on its Service
  // Changed characteristic, then registering a different service invokes the
  // callback to send an indication to the "client."
  TEST_F(GenericAttributeServiceTest, IndicateOnRegister) {
    std::optional<IdType> indicated_svc_id;
    auto send_indication = [&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
      EXPECT_EQ(kTestPeerId, peer_id);
      EXPECT_EQ(kServiceChangedChrcId, chrc_id);
      indicated_svc_id = service_id;
  
      ASSERT_EQ(4u, value.size());
      // The second service following the four-attribute GATT service should span
      // the subsequent three handles.
      EXPECT_EQ(0x05, value[0]);
      EXPECT_EQ(0x00, value[1]);
      EXPECT_EQ(0x07, value[2]);
      EXPECT_EQ(0x00, value[3]);
    };
  
    // Register the GATT service.
    GenericAttributeService gatt_service(mgr.GetWeakPtr(), std::move(send_indication));
  
    // Enable Service Changed indications for the test client.
    fitx::result<att::ErrorCode> status = fitx::ok();
    WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
    EXPECT_EQ(std::nullopt, indicated_svc_id);
  
    constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
    constexpr IdType kChrcId = 12;
    constexpr uint8_t kChrcProps = Property::kRead;
    constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
    const att::AccessRequirements kReadReqs(/*encryption=*/true, /*authentication=*/true,
                                            /*authorization=*/true);
    const att::AccessRequirements kWriteReqs, kUpdateReqs;
    auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
    service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId, kTestChrcType, kChrcProps, 0,
                                                                kReadReqs, kWriteReqs, kUpdateReqs));
    auto service_id =
        mgr.RegisterService(std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
    // Verify that service registration succeeded
    EXPECT_NE(kInvalidId, service_id);
    // Verify that |send_indication| was invoked to indicate the Service Changed chrc within the
    // gatt_service.
    EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
  }
  
  // Same test as above, but the indication is enabled just prior unregistering
  // the latter test service.
  TEST_F(GenericAttributeServiceTest, IndicateOnUnregister) {
    std::optional<IdType> indicated_svc_id;
    auto send_indication = [&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
      EXPECT_EQ(kTestPeerId, peer_id);
      EXPECT_EQ(kServiceChangedChrcId, chrc_id);
      indicated_svc_id = service_id;
  
      ASSERT_EQ(4u, value.size());
      // The second service following the four-attribute GATT service should span
      // the subsequent four handles (update enabled).
      EXPECT_EQ(0x05, value[0]);
      EXPECT_EQ(0x00, value[1]);
      EXPECT_EQ(0x08, value[2]);
      EXPECT_EQ(0x00, value[3]);
    };
  
    // Register the GATT service.
    GenericAttributeService gatt_service(mgr.GetWeakPtr(), std::move(send_indication));
  
    constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
    constexpr IdType kChrcId = 0;
    constexpr uint8_t kChrcProps = Property::kNotify;
    constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
    const att::AccessRequirements kReadReqs, kWriteReqs;
    const att::AccessRequirements kUpdateReqs(/*encryption=*/true, /*authentication=*/true,
                                              /*authorization=*/true);
    auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
    service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId, kTestChrcType, kChrcProps, 0,
                                                                kReadReqs, kWriteReqs, kUpdateReqs));
    auto service_id =
        mgr.RegisterService(std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
    // Verify that service registration succeeded
    EXPECT_NE(kInvalidId, service_id);
  
    // Enable Service Changed indications for the test client.
    fitx::result<att::ErrorCode> status = fitx::ok();
    WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
    EXPECT_EQ(std::nullopt, indicated_svc_id);
  
    mgr.UnregisterService(service_id);
    // Verify that |send_indication| was invoked to indicate the Service Changed chrc within the
    // gatt_service.
    EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
  }
  
  // Tests that registering the GATT service reads a persisted value for the service changed
  // characteristic's ccc, and that enabling indication on its service changed characteristic writes a
  // persisted value.
  TEST_F(GenericAttributeServiceTest, PersistIndicate) {
    int persist_callback_count = 0;
  
    auto persist_callback = [&persist_callback_count](PeerId peer_id,
                                                      ServiceChangedCCCPersistedData gatt_data) {
      EXPECT_EQ(peer_id, kTestPeerId);
      EXPECT_EQ(gatt_data.indicate, true);
      persist_callback_count++;
    };
  
    // Register the GATT service.
    GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
    gatt_service.SetPersistServiceChangedCCCCallback(std::move(persist_callback));
    EXPECT_EQ(persist_callback_count, 0);
  
    // Enable Service Changed indications for the test client.
    fitx::result<att::ErrorCode> status = fitx::ok();
    WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
    EXPECT_EQ(persist_callback_count, 1);
  }
  }  // namespace
  }  // namespace bt::gatt
  