// 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/gap/low_energy_connection.h"

#include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"

namespace bt::gap::internal {

namespace {

constexpr const char* kInspectPeerIdPropertyName = "peer_id";
constexpr const char* kInspectPeerAddressPropertyName = "peer_address";
constexpr const char* kInspectRefsPropertyName = "ref_count";

// Connection parameters to use when the peer's preferred connection parameters
// are not known.
static const hci_spec::LEPreferredConnectionParameters
    kDefaultPreferredConnectionParameters(
        hci_spec::defaults::kLEConnectionIntervalMin,
        hci_spec::defaults::kLEConnectionIntervalMax,
        /*max_latency=*/0,
        hci_spec::defaults::kLESupervisionTimeout);

}  // namespace

std::unique_ptr<LowEnergyConnection> LowEnergyConnection::Create(
    Peer::WeakPtr peer,
    std::unique_ptr<hci::LowEnergyConnection> link,
    LowEnergyConnectionOptions connection_options,
    PeerDisconnectCallback peer_disconnect_cb,
    ErrorCallback error_cb,
    WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
    l2cap::ChannelManager* l2cap,
    gatt::GATT::WeakPtr gatt,
    hci::CommandChannel::WeakPtr cmd_channel,
    pw::async::Dispatcher& dispatcher) {
  // Catch any errors/disconnects during connection initialization so that they
  // are reported by returning a nullptr. This is less error-prone than calling
  // the user's callbacks during initialization.
  bool error = false;
  auto peer_disconnect_cb_temp = [&error](auto) { error = true; };
  auto error_cb_temp = [&error] { error = true; };
  std::unique_ptr<LowEnergyConnection> connection(
      new LowEnergyConnection(std::move(peer),
                              std::move(link),
                              connection_options,
                              std::move(peer_disconnect_cb_temp),
                              std::move(error_cb_temp),
                              std::move(conn_mgr),
                              l2cap,
                              std::move(gatt),
                              std::move(cmd_channel),
                              dispatcher));

  // This looks strange, but it is possible for InitializeFixedChannels() to
  // trigger an error and still return true, so |error| can change between the
  // first and last check.
  if (error || !connection->InitializeFixedChannels() || error) {
    return nullptr;
  }

  // Now it is safe to set the user's callbacks, as no more errors/disconnects
  // can be signaled before returning.
  connection->set_peer_disconnect_callback(std::move(peer_disconnect_cb));
  connection->set_error_callback(std::move(error_cb));
  return connection;
}

LowEnergyConnection::LowEnergyConnection(
    Peer::WeakPtr peer,
    std::unique_ptr<hci::LowEnergyConnection> link,
    LowEnergyConnectionOptions connection_options,
    PeerDisconnectCallback peer_disconnect_cb,
    ErrorCallback error_cb,
    WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
    l2cap::ChannelManager* l2cap,
    gatt::GATT::WeakPtr gatt,
    hci::CommandChannel::WeakPtr cmd_channel,
    pw::async::Dispatcher& dispatcher)
    : dispatcher_(dispatcher),
      peer_(std::move(peer)),
      link_(std::move(link)),
      connection_options_(connection_options),
      conn_mgr_(std::move(conn_mgr)),
      l2cap_(l2cap),
      gatt_(std::move(gatt)),
      cmd_(std::move(cmd_channel)),
      peer_disconnect_callback_(std::move(peer_disconnect_cb)),
      error_callback_(std::move(error_cb)),
      refs_(/*convert=*/[](const auto& refs) { return refs.size(); }),
      weak_self_(this),
      weak_delegate_(this) {
  BT_ASSERT(peer_.is_alive());
  BT_ASSERT(link_);
  BT_ASSERT(conn_mgr_.is_alive());
  BT_ASSERT(gatt_.is_alive());
  BT_ASSERT(cmd_.is_alive());
  BT_ASSERT(peer_disconnect_callback_);
  BT_ASSERT(error_callback_);

  link_->set_peer_disconnect_callback(
      [this](const auto&, auto reason) { peer_disconnect_callback_(reason); });

  RegisterEventHandlers();
  StartConnectionPauseTimeout();
}

LowEnergyConnection::~LowEnergyConnection() {
  cmd_->RemoveEventHandler(conn_update_cmpl_handler_id_);

  // Unregister this link from the GATT profile and the L2CAP plane. This
  // invalidates all L2CAP channels that are associated with this link.
  gatt_->RemoveConnection(peer_id());
  l2cap_->RemoveConnection(link_->handle());

  // Notify all active references that the link is gone. This will
  // synchronously notify all refs.
  CloseRefs();
}

std::unique_ptr<bt::gap::LowEnergyConnectionHandle>
LowEnergyConnection::AddRef() {
  auto self = GetWeakPtr();
  auto release_cb = [self](LowEnergyConnectionHandle* handle) {
    if (self.is_alive()) {
      self->conn_mgr_->ReleaseReference(handle);
    }
  };
  auto bondable_cb = [self] {
    BT_ASSERT(self.is_alive());
    return self->bondable_mode();
  };
  auto security_cb = [self] {
    BT_ASSERT(self.is_alive());
    return self->security();
  };
  std::unique_ptr<bt::gap::LowEnergyConnectionHandle> conn_ref(
      new LowEnergyConnectionHandle(peer_id(),
                                    handle(),
                                    std::move(release_cb),
                                    std::move(bondable_cb),
                                    std::move(security_cb)));
  BT_ASSERT(conn_ref);

  refs_.Mutable()->insert(conn_ref.get());

  bt_log(DEBUG,
         "gap-le",
         "added ref (peer: %s, handle %#.4x, count: %lu)",
         bt_str(peer_id()),
         handle(),
         ref_count());

  return conn_ref;
}

void LowEnergyConnection::DropRef(LowEnergyConnectionHandle* ref) {
  BT_DEBUG_ASSERT(ref);

  size_t res = refs_.Mutable()->erase(ref);
  BT_ASSERT_MSG(res == 1u, "DropRef called with wrong connection reference");
  bt_log(DEBUG,
         "gap-le",
         "dropped ref (peer: %s, handle: %#.4x, count: %lu)",
         bt_str(peer_id()),
         handle(),
         ref_count());
}

// Registers this connection with L2CAP and initializes the fixed channel
// protocols.
[[nodiscard]] bool LowEnergyConnection::InitializeFixedChannels() {
  auto self = GetWeakPtr();
  // Ensure error_callback_ is only called once if link_error_cb is called
  // multiple times.
  auto link_error_cb = [self]() {
    if (self.is_alive() && self->error_callback_) {
      self->error_callback_();
    }
  };
  auto update_conn_params_cb = [self](auto params) {
    if (self.is_alive()) {
      self->OnNewLEConnectionParams(params);
    }
  };
  auto security_upgrade_cb = [self](auto handle, auto level, auto cb) {
    if (!self.is_alive()) {
      return;
    }

    bt_log(INFO,
           "gap-le",
           "received security upgrade request on L2CAP channel (level: %s, "
           "peer: %s, handle: %#.4x)",
           sm::LevelToString(level),
           bt_str(self->peer_id()),
           handle);
    BT_ASSERT(self->handle() == handle);
    self->OnSecurityRequest(level, std::move(cb));
  };
  l2cap::ChannelManager::LEFixedChannels fixed_channels =
      l2cap_->AddLEConnection(link_->handle(),
                              link_->role(),
                              std::move(link_error_cb),
                              update_conn_params_cb,
                              security_upgrade_cb);

  return OnL2capFixedChannelsOpened(std::move(fixed_channels.att),
                                    std::move(fixed_channels.smp),
                                    connection_options_);
}

// Used to respond to protocol/service requests for increased security.
void LowEnergyConnection::OnSecurityRequest(sm::SecurityLevel level,
                                            sm::ResultFunction<> cb) {
  BT_ASSERT(sm_);
  sm_->UpgradeSecurity(
      level,
      [cb = std::move(cb), peer_id = peer_id(), handle = handle()](
          sm::Result<> status, const auto& sp) {
        bt_log(INFO,
               "gap-le",
               "pairing status: %s, properties: %s (peer: %s, handle: %#.4x)",
               bt_str(status),
               bt_str(sp),
               bt_str(peer_id),
               handle);
        cb(status);
      });
}

// Handles a pairing request (i.e. security upgrade) received from "higher
// levels", likely initiated from GAP. This will only be used by pairing
// requests that are initiated in the context of testing. May only be called on
// an already-established connection.
void LowEnergyConnection::UpgradeSecurity(sm::SecurityLevel level,
                                          sm::BondableMode bondable_mode,
                                          sm::ResultFunction<> cb) {
  BT_ASSERT(sm_);
  sm_->set_bondable_mode(bondable_mode);
  OnSecurityRequest(level, std::move(cb));
}

// Cancels any on-going pairing procedures and sets up SMP to use the provided
// new I/O capabilities for future pairing procedures.
void LowEnergyConnection::ResetSecurityManager(sm::IOCapability ioc) {
  sm_->Reset(ioc);
}

void LowEnergyConnection::OnInterrogationComplete() {
  BT_ASSERT(!interrogation_completed_);
  interrogation_completed_ = true;
  MaybeUpdateConnectionParameters();
}

void LowEnergyConnection::AttachInspect(inspect::Node& parent,
                                        std::string name) {
  inspect_node_ = parent.CreateChild(name);
  inspect_properties_.peer_id = inspect_node_.CreateString(
      kInspectPeerIdPropertyName, peer_id().ToString());
  inspect_properties_.peer_address = inspect_node_.CreateString(
      kInspectPeerAddressPropertyName,
      link_.get() ? link_->peer_address().ToString() : "");
  refs_.AttachInspect(inspect_node_, kInspectRefsPropertyName);
}

void LowEnergyConnection::StartConnectionPauseTimeout() {
  if (link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
    StartConnectionPauseCentralTimeout();
  } else {
    StartConnectionPausePeripheralTimeout();
  }
}

void LowEnergyConnection::RegisterEventHandlers() {
  auto self = GetWeakPtr();
  conn_update_cmpl_handler_id_ = cmd_->AddLEMetaEventHandler(
      hci_spec::kLEConnectionUpdateCompleteSubeventCode,
      [self](const hci::EmbossEventPacket& event) {
        if (self.is_alive()) {
          self->OnLEConnectionUpdateComplete(event);
          return hci::CommandChannel::EventCallbackResult::kContinue;
        }
        return hci::CommandChannel::EventCallbackResult::kRemove;
      });
}

// Connection parameter updates by the peripheral are not allowed until the
// central has been idle for kLEConnectionPauseCentral and
// kLEConnectionPausePeripheral has passed since the connection was established
// (Core Spec v5.2, Vol 3, Part C, Sec 9.3.12).
// TODO(fxbug.dev/42159733): Wait to update connection parameters until all
// initialization procedures have completed.
void LowEnergyConnection::StartConnectionPausePeripheralTimeout() {
  BT_ASSERT(!conn_pause_peripheral_timeout_.has_value());
  conn_pause_peripheral_timeout_.emplace(
      dispatcher_, [this](pw::async::Context /*ctx*/, pw::Status status) {
        if (!status.ok()) {
          return;
        }
        // Destroying this task will invalidate the capture list,
        // so we need to save a self pointer.
        auto self = this;
        conn_pause_peripheral_timeout_.reset();
        self->MaybeUpdateConnectionParameters();
      });
  conn_pause_peripheral_timeout_->PostAfter(kLEConnectionPausePeripheral);
}

// Connection parameter updates by the central are not allowed until the central
// is idle and the peripheral has been idle for kLEConnectionPauseCentral (Core
// Spec v5.2, Vol 3, Part C, Sec 9.3.12).
// TODO(fxbug.dev/42159733): Wait to update connection parameters until all
// initialization procedures have completed.
void LowEnergyConnection::StartConnectionPauseCentralTimeout() {
  BT_ASSERT(!conn_pause_central_timeout_.has_value());
  conn_pause_central_timeout_.emplace(
      dispatcher_, [this](pw::async::Context /*ctx*/, pw::Status status) {
        if (!status.ok()) {
          return;
        }
        // Destroying this task will invalidate the capture list, so
        // we need to save a self pointer.
        auto self = this;
        conn_pause_central_timeout_.reset();
        self->MaybeUpdateConnectionParameters();
      });
  conn_pause_central_timeout_->PostAfter(kLEConnectionPauseCentral);
}

bool LowEnergyConnection::OnL2capFixedChannelsOpened(
    l2cap::Channel::WeakPtr att,
    l2cap::Channel::WeakPtr smp,
    LowEnergyConnectionOptions connection_options) {
  bt_log(DEBUG,
         "gap-le",
         "ATT and SMP fixed channels open (peer: %s)",
         bt_str(peer_id()));

  // Obtain existing pairing data, if any.
  std::optional<sm::LTK> ltk;

  if (peer_->le() && peer_->le()->bond_data()) {
    // Legacy pairing allows both devices to generate and exchange LTKs. "The
    // Central must have the security information (LTK, EDIV, and Rand)
    // distributed by the Peripheral in LE legacy [...] to setup an encrypted
    // session" (v5.3, Vol. 3 Part H 2.4.4.2). For Secure Connections peer_ltk
    // and local_ltk will be equal, so this check is unnecessary but correct.
    ltk = (link()->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL)
              ? peer_->le()->bond_data()->peer_ltk
              : peer_->le()->bond_data()->local_ltk;
  }

  // Obtain the local I/O capabilities from the delegate. Default to
  // NoInputNoOutput if no delegate is available.
  auto io_cap = sm::IOCapability::kNoInputNoOutput;
  if (conn_mgr_->pairing_delegate().is_alive()) {
    io_cap = conn_mgr_->pairing_delegate()->io_capability();
  }
  LESecurityMode security_mode = conn_mgr_->security_mode();
  sm_ = conn_mgr_->sm_factory_func()(link_->GetWeakPtr(),
                                     std::move(smp),
                                     io_cap,
                                     weak_delegate_.GetWeakPtr(),
                                     connection_options.bondable_mode,
                                     security_mode,
                                     dispatcher_);

  // Provide SMP with the correct LTK from a previous pairing with the peer, if
  // it exists. This will start encryption if the local device is the link-layer
  // central.
  if (ltk) {
    bt_log(INFO,
           "gap-le",
           "assigning existing LTK (peer: %s, handle: %#.4x)",
           bt_str(peer_id()),
           handle());
    sm_->AssignLongTermKey(*ltk);
  }

  return InitializeGatt(std::move(att), connection_options.service_uuid);
}

void LowEnergyConnection::OnNewLEConnectionParams(
    const hci_spec::LEPreferredConnectionParameters& params) {
  bt_log(INFO,
         "gap-le",
         "LE connection parameters received (peer: %s, handle: %#.4x)",
         bt_str(peer_id()),
         link_->handle());

  BT_ASSERT(peer_.is_alive());

  peer_->MutLe().SetPreferredConnectionParameters(params);

  UpdateConnectionParams(params);
}

void LowEnergyConnection::RequestConnectionParameterUpdate(
    const hci_spec::LEPreferredConnectionParameters& params) {
  BT_ASSERT_MSG(
      link_->role() == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL,
      "tried to send connection parameter update request as central");

  BT_ASSERT(peer_.is_alive());
  // Ensure interrogation has completed.
  BT_ASSERT(peer_->le()->features().has_value());

  // TODO(fxbug.dev/42126713): check local controller support for LL Connection
  // Parameters Request procedure (mask is currently in Adapter le state,
  // consider propagating down)
  bool ll_connection_parameters_req_supported =
      peer_->le()->features()->le_features &
      static_cast<uint64_t>(
          hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure);

  bt_log(TRACE,
         "gap-le",
         "ll connection parameters req procedure supported: %s",
         ll_connection_parameters_req_supported ? "true" : "false");

  if (ll_connection_parameters_req_supported) {
    auto self = weak_self_.GetWeakPtr();
    auto status_cb = [self, params](hci::Result<> status) {
      if (!self.is_alive()) {
        return;
      }

      self->HandleRequestConnectionParameterUpdateCommandStatus(params, status);
    };

    UpdateConnectionParams(params, std::move(status_cb));
  } else {
    L2capRequestConnectionParameterUpdate(params);
  }
}

void LowEnergyConnection::HandleRequestConnectionParameterUpdateCommandStatus(
    hci_spec::LEPreferredConnectionParameters params, hci::Result<> status) {
  // The next LE Connection Update complete event is for this command iff the
  // command |status| is success.
  if (status.is_error()) {
    if (status ==
        ToResult(
            pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE)) {
      // Retry connection parameter update with l2cap if the peer doesn't
      // support LL procedure.
      bt_log(INFO,
             "gap-le",
             "peer does not support HCI LE Connection Update command, trying "
             "l2cap request (peer: %s)",
             bt_str(peer_id()));
      L2capRequestConnectionParameterUpdate(params);
    }
    return;
  }

  // Note that this callback is for the Connection Update Complete event, not
  // the Connection Update status event, which is handled by the above code (see
  // v5.2, Vol. 4, Part E 7.7.15 / 7.7.65.3).
  le_conn_update_complete_command_callback_ =
      [this, params](pw::bluetooth::emboss::StatusCode status) {
        // Retry connection parameter update with l2cap if the peer doesn't
        // support LL procedure.
        if (status ==
            pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE) {
          bt_log(INFO,
                 "gap-le",
                 "peer does not support HCI LE Connection Update command, "
                 "trying l2cap request "
                 "(peer: %s)",
                 bt_str(peer_id()));
          L2capRequestConnectionParameterUpdate(params);
        }
      };
}

void LowEnergyConnection::L2capRequestConnectionParameterUpdate(
    const hci_spec::LEPreferredConnectionParameters& params) {
  BT_ASSERT_MSG(
      link_->role() == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL,
      "tried to send l2cap connection parameter update request as central");

  bt_log(DEBUG,
         "gap-le",
         "sending l2cap connection parameter update request (peer: %s)",
         bt_str(peer_id()));

  auto response_cb = [handle = handle(), peer_id = peer_id()](bool accepted) {
    if (accepted) {
      bt_log(DEBUG,
             "gap-le",
             "peer accepted l2cap connection parameter update request (peer: "
             "%s, handle: %#.4x)",
             bt_str(peer_id),
             handle);
    } else {
      bt_log(INFO,
             "gap-le",
             "peer rejected l2cap connection parameter update request (peer: "
             "%s, handle: %#.4x)",
             bt_str(peer_id),
             handle);
    }
  };

  // TODO(fxbug.dev/42126716): don't send request until after
  // kLEConnectionParameterTimeout of an l2cap conn parameter update response
  // being received (Core Spec v5.2, Vol 3, Part C, Sec 9.3.9).
  l2cap_->RequestConnectionParameterUpdate(
      handle(), params, std::move(response_cb));
}

void LowEnergyConnection::UpdateConnectionParams(
    const hci_spec::LEPreferredConnectionParameters& params,
    StatusCallback status_cb) {
  bt_log(DEBUG,
         "gap-le",
         "updating connection parameters (peer: %s)",
         bt_str(peer_id()));
  auto command = hci::EmbossCommandPacket::New<
      pw::bluetooth::emboss::LEConnectionUpdateCommandWriter>(
      hci_spec::kLEConnectionUpdate);
  auto view = command.view_t();
  view.connection_handle().Write(handle());
  // TODO(fxbug.dev/42074287): Handle invalid connection parameters before
  // sending them to the controller.
  view.connection_interval_min().UncheckedWrite(params.min_interval());
  view.connection_interval_max().UncheckedWrite(params.max_interval());
  view.max_latency().UncheckedWrite(params.max_latency());
  view.supervision_timeout().UncheckedWrite(params.supervision_timeout());
  view.min_connection_event_length().Write(0x0000);
  view.max_connection_event_length().Write(0x0000);

  auto status_cb_wrapper = [handle = handle(), cb = std::move(status_cb)](
                               auto id, const hci::EventPacket& event) mutable {
    BT_ASSERT(event.event_code() == hci_spec::kCommandStatusEventCode);
    hci_is_error(event,
                 TRACE,
                 "gap-le",
                 "controller rejected connection parameters (handle: %#.4x)",
                 handle);
    if (cb) {
      cb(event.ToResult());
    }
  };

  cmd_->SendCommand(std::move(command),
                    std::move(status_cb_wrapper),
                    hci_spec::kCommandStatusEventCode);
}

void LowEnergyConnection::OnLEConnectionUpdateComplete(
    const hci::EmbossEventPacket& event) {
  BT_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
  auto view = event.view<pw::bluetooth::emboss::LEMetaEventView>();
  BT_ASSERT(view.subevent_code().Read() ==
            hci_spec::kLEConnectionUpdateCompleteSubeventCode);

  auto payload = event.view<
      pw::bluetooth::emboss::LEConnectionUpdateCompleteSubeventView>();
  hci_spec::ConnectionHandle handle = payload.connection_handle().Read();

  // Ignore events for other connections.
  if (handle != link_->handle()) {
    return;
  }

  // This event may be the result of the LE Connection Update command.
  if (le_conn_update_complete_command_callback_) {
    le_conn_update_complete_command_callback_(payload.status().Read());
  }

  if (payload.status().Read() != pw::bluetooth::emboss::StatusCode::SUCCESS) {
    bt_log(WARN,
           "gap-le",
           "HCI LE Connection Update Complete event with error "
           "(peer: %s, status: %#.2hhx, handle: %#.4x)",
           bt_str(peer_id()),
           static_cast<unsigned char>(payload.status().Read()),
           handle);

    return;
  }

  bt_log(
      INFO, "gap-le", "conn. parameters updated (peer: %s)", bt_str(peer_id()));

  hci_spec::LEConnectionParameters params(
      payload.connection_interval().UncheckedRead(),
      payload.peripheral_latency().UncheckedRead(),
      payload.supervision_timeout().UncheckedRead());
  link_->set_low_energy_parameters(params);

  BT_ASSERT(peer_.is_alive());
  peer_->MutLe().SetConnectionParameters(params);
}

void LowEnergyConnection::MaybeUpdateConnectionParameters() {
  if (connection_parameters_update_requested_ || conn_pause_central_timeout_ ||
      conn_pause_peripheral_timeout_ || !interrogation_completed_) {
    return;
  }

  connection_parameters_update_requested_ = true;

  if (link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
    // If the GAP service preferred connection parameters characteristic has not
    // been read by now, just use the default parameters.
    // TODO(fxbug.dev/42144795): Wait for preferred connection parameters to be
    // read.
    BT_ASSERT(peer_.is_alive());
    auto conn_params = peer_->le()->preferred_connection_parameters().value_or(
        kDefaultPreferredConnectionParameters);
    UpdateConnectionParams(conn_params);
  } else {
    RequestConnectionParameterUpdate(kDefaultPreferredConnectionParameters);
  }
}

bool LowEnergyConnection::InitializeGatt(l2cap::Channel::WeakPtr att_channel,
                                         std::optional<UUID> service_uuid) {
  att_bearer_ = att::Bearer::Create(std::move(att_channel), dispatcher_);
  if (!att_bearer_) {
    // This can happen if the link closes before the Bearer activates the
    // channel.
    bt_log(WARN, "gatt", "failed to initialize ATT bearer");
    return false;
  }

  // The att::Bearer object is owned by LowEnergyConnection, so it outlives the
  // gatt::Server and Client objects. As such, they can safely take WeakPtrs to
  // the Bearer.
  auto server_factory =
      [att_bearer = att_bearer_->GetWeakPtr()](
          PeerId peer_id,
          gatt::LocalServiceManager::WeakPtr local_services) mutable {
        return gatt::Server::Create(
            peer_id, std::move(local_services), std::move(att_bearer));
      };
  std::unique_ptr<gatt::Client> gatt_client =
      gatt::Client::Create(att_bearer_->GetWeakPtr());
  gatt_->AddConnection(
      peer_id(), std::move(gatt_client), std::move(server_factory));

  std::vector<UUID> service_uuids;
  if (service_uuid) {
    // TODO(fxbug.dev/42144310): De-duplicate services.
    service_uuids = {*service_uuid, kGenericAccessService};
  }
  gatt_->InitializeClient(peer_id(), std::move(service_uuids));

  auto self = weak_self_.GetWeakPtr();
  gatt_->ListServices(
      peer_id(), {kGenericAccessService}, [self](auto status, auto services) {
        if (self.is_alive()) {
          self->OnGattServicesResult(status, std::move(services));
        }
      });

  return true;
}

void LowEnergyConnection::OnGattServicesResult(att::Result<> status,
                                               gatt::ServiceList services) {
  if (bt_is_error(status,
                  INFO,
                  "gap-le",
                  "error discovering GAP service (peer: %s)",
                  bt_str(peer_id()))) {
    return;
  }

  if (services.empty()) {
    // The GAP service is mandatory for both central and peripheral, so this is
    // unexpected.
    bt_log(
        INFO, "gap-le", "GAP service not found (peer: %s)", bt_str(peer_id()));
    return;
  }

  gap_service_client_.emplace(peer_id(), services.front());
  auto self = weak_self_.GetWeakPtr();

  gap_service_client_->ReadDeviceName([self](att::Result<std::string> result) {
    if (!self.is_alive() || result.is_error()) {
      return;
    }

    self->peer_->RegisterName(result.value(),
                              Peer::NameSource::kGenericAccessService);
  });

  gap_service_client_->ReadAppearance([self](att::Result<uint16_t> result) {
    if (!self.is_alive() || result.is_error()) {
      return;
    }

    self->peer_->SetAppearance(result.value());
  });

  if (!peer_->le()->preferred_connection_parameters().has_value()) {
    gap_service_client_->ReadPeripheralPreferredConnectionParameters(
        [self](att::Result<hci_spec::LEPreferredConnectionParameters> result) {
          if (!self.is_alive()) {
            return;
          }

          if (result.is_error()) {
            bt_log(INFO,
                   "gap-le",
                   "error reading peripheral preferred connection parameters "
                   "(status:  %s, peer: %s)",
                   ::bt::internal::ToString(result).c_str(),
                   bt_str(self->peer_id()));
            return;
          }

          auto params = result.value();
          self->peer_->MutLe().SetPreferredConnectionParameters(params);
        });
  }
}

void LowEnergyConnection::CloseRefs() {
  for (auto* ref : *refs_.Mutable()) {
    ref->MarkClosed();
  }

  refs_.Mutable()->clear();
}

void LowEnergyConnection::OnNewPairingData(
    const sm::PairingData& pairing_data) {
  const std::optional<sm::LTK> ltk =
      pairing_data.peer_ltk ? pairing_data.peer_ltk : pairing_data.local_ltk;
  // Consider the pairing temporary if no link key was received. This
  // means we'll remain encrypted with the STK without creating a bond and
  // reinitiate pairing when we reconnect in the future.
  if (!ltk.has_value()) {
    bt_log(INFO,
           "gap-le",
           "temporarily paired with peer (peer: %s)",
           bt_str(peer_id()));
    return;
  }

  bt_log(INFO,
         "gap-le",
         "new %s pairing data: [%s%s%s%s%s%s] (peer: %s)",
         ltk->security().secure_connections() ? "secure connections" : "legacy",
         pairing_data.peer_ltk ? "peer_ltk " : "",
         pairing_data.local_ltk ? "local_ltk " : "",
         pairing_data.irk ? "irk " : "",
         pairing_data.cross_transport_key ? "ct_key " : "",
         pairing_data.identity_address
             ? bt_lib_cpp_string::StringPrintf(
                   "(identity: %s) ", bt_str(*pairing_data.identity_address))
                   .c_str()
             : "",
         pairing_data.csrk ? "csrk " : "",
         bt_str(peer_id()));

  if (!peer_->MutLe().StoreBond(pairing_data)) {
    bt_log(ERROR,
           "gap-le",
           "failed to cache bonding data (id: %s)",
           bt_str(peer_id()));
  }
}

void LowEnergyConnection::OnPairingComplete(sm::Result<> status) {
  bt_log(INFO,
         "gap-le",
         "pairing complete (status: %s, peer: %s)",
         bt_str(status),
         bt_str(peer_id()));

  auto delegate = conn_mgr_->pairing_delegate();
  if (delegate.is_alive()) {
    delegate->CompletePairing(peer_id(), status);
  }
}

void LowEnergyConnection::OnAuthenticationFailure(hci::Result<> status) {
  // TODO(armansito): Clear bonding data from the remote peer cache as any
  // stored link key is not valid.
  bt_log(WARN,
         "gap-le",
         "link layer authentication failed (status: %s, peer: %s)",
         bt_str(status),
         bt_str(peer_id()));
}

void LowEnergyConnection::OnNewSecurityProperties(
    const sm::SecurityProperties& sec) {
  bt_log(INFO,
         "gap-le",
         "new link security properties (properties: %s, peer: %s)",
         bt_str(sec),
         bt_str(peer_id()));
  // Update the data plane with the correct link security level.
  l2cap_->AssignLinkSecurityProperties(link_->handle(), sec);
}

std::optional<sm::IdentityInfo>
LowEnergyConnection::OnIdentityInformationRequest() {
  if (!conn_mgr_->local_address_delegate()->irk()) {
    bt_log(TRACE, "gap-le", "no local identity information to exchange");
    return std::nullopt;
  }

  bt_log(DEBUG,
         "gap-le",
         "will distribute local identity information (peer: %s)",
         bt_str(peer_id()));
  sm::IdentityInfo id_info;
  id_info.irk = *conn_mgr_->local_address_delegate()->irk();
  id_info.address = conn_mgr_->local_address_delegate()->identity_address();

  return id_info;
}

void LowEnergyConnection::ConfirmPairing(ConfirmCallback confirm) {
  bt_log(INFO,
         "gap-le",
         "pairing delegate request for pairing confirmation w/ no passkey "
         "(peer: %s)",
         bt_str(peer_id()));

  auto delegate = conn_mgr_->pairing_delegate();
  if (!delegate.is_alive()) {
    bt_log(ERROR,
           "gap-le",
           "rejecting pairing without a PairingDelegate! (peer: %s)",
           bt_str(peer_id()));
    confirm(false);
  } else {
    delegate->ConfirmPairing(peer_id(), std::move(confirm));
  }
}

void LowEnergyConnection::DisplayPasskey(uint32_t passkey,
                                         sm::Delegate::DisplayMethod method,
                                         ConfirmCallback confirm) {
  bt_log(INFO,
         "gap-le",
         "pairing delegate request (method: %s, peer: %s)",
         sm::util::DisplayMethodToString(method).c_str(),
         bt_str(peer_id()));

  auto delegate = conn_mgr_->pairing_delegate();
  if (!delegate.is_alive()) {
    bt_log(ERROR, "gap-le", "rejecting pairing without a PairingDelegate!");
    confirm(false);
  } else {
    delegate->DisplayPasskey(peer_id(), passkey, method, std::move(confirm));
  }
}

void LowEnergyConnection::RequestPasskey(PasskeyResponseCallback respond) {
  bt_log(INFO,
         "gap-le",
         "pairing delegate request for passkey entry (peer: %s)",
         bt_str(peer_id()));

  auto delegate = conn_mgr_->pairing_delegate();
  if (!delegate.is_alive()) {
    bt_log(ERROR,
           "gap-le",
           "rejecting pairing without a PairingDelegate! (peer: %s)",
           bt_str(peer_id()));
    respond(-1);
  } else {
    delegate->RequestPasskey(peer_id(), std::move(respond));
  }
}

}  // namespace bt::gap::internal
