// 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/hci/acl_connection.h"

#include "pw_bluetooth_sapphire/internal/host/transport/transport.h"

namespace bt::hci {

namespace {

template <CommandChannel::EventCallbackResult (
    AclConnection::* EventHandlerMethod)(const EventPacket&)>
CommandChannel::EventCallback BindEventHandler(
    const WeakSelf<AclConnection>::WeakPtr& conn) {
  return [conn](const EventPacket& event) {
    if (conn.is_alive()) {
      return (conn.get().*EventHandlerMethod)(event);
    }
    return CommandChannel::EventCallbackResult::kRemove;
  };
}

template <CommandChannel::EventCallbackResult (
    AclConnection::* EventHandlerMethod)(const EmbossEventPacket&)>
CommandChannel::EmbossEventCallback BindEventHandler(
    const WeakPtr<AclConnection>& conn) {
  return [conn](const EmbossEventPacket& event) {
    if (conn.is_alive()) {
      return (conn.get().*EventHandlerMethod)(event);
    }
    return CommandChannel::EventCallbackResult::kRemove;
  };
}

}  // namespace

AclConnection::AclConnection(hci_spec::ConnectionHandle handle,
                             const DeviceAddress& local_address,
                             const DeviceAddress& peer_address,
                             pw::bluetooth::emboss::ConnectionRole role,
                             const Transport::WeakPtr& hci)
    : Connection(handle,
                 local_address,
                 peer_address,
                 hci,
                 [handle, hci] {
                   AclConnection::OnDisconnectionComplete(handle, hci);
                 }),
      role_(role),
      weak_self_(this) {
  auto self = weak_self_.GetWeakPtr();
  enc_change_id_ = hci->command_channel()->AddEventHandler(
      hci_spec::kEncryptionChangeEventCode,
      BindEventHandler<&AclConnection::OnEncryptionChangeEvent>(self));
  enc_key_refresh_cmpl_id_ = hci->command_channel()->AddEventHandler(
      hci_spec::kEncryptionKeyRefreshCompleteEventCode,
      BindEventHandler<&AclConnection::OnEncryptionKeyRefreshCompleteEvent>(
          self));
}

AclConnection::~AclConnection() {
  // Unregister HCI event handlers.
  hci()->command_channel()->RemoveEventHandler(enc_change_id_);
  hci()->command_channel()->RemoveEventHandler(enc_key_refresh_cmpl_id_);
}

void AclConnection::OnDisconnectionComplete(hci_spec::ConnectionHandle handle,
                                            const Transport::WeakPtr& hci) {
  if (!hci.is_alive()) {
    return;
  }
  // Notify ACL data channel that packets have been flushed from controller
  // buffer.
  hci->acl_data_channel()->ClearControllerPacketCount(handle);
}

CommandChannel::EventCallbackResult AclConnection::OnEncryptionChangeEvent(
    const EmbossEventPacket& event) {
  BT_ASSERT(event.event_code() == hci_spec::kEncryptionChangeEventCode);

  auto params =
      event
          .unchecked_view<pw::bluetooth::emboss::EncryptionChangeEventV1View>();
  if (!params.Ok()) {
    bt_log(WARN, "hci", "malformed encryption change event");
    return CommandChannel::EventCallbackResult::kContinue;
  }

  hci_spec::ConnectionHandle handle = params.connection_handle().Read();

  // Silently ignore the event as it isn't meant for this connection.
  if (handle != this->handle()) {
    return CommandChannel::EventCallbackResult::kContinue;
  }

  if (state() != Connection::State::kConnected) {
    bt_log(DEBUG, "hci", "encryption change ignored for closed connection");
    return CommandChannel::EventCallbackResult::kContinue;
  }

  Result<> result = event.ToResult();
  encryption_status_ = params.encryption_enabled().Read();
  bool encryption_enabled =
      encryption_status_ != pw::bluetooth::emboss::EncryptionStatus::OFF;

  bt_log(DEBUG,
         "hci",
         "encryption change (%s) %s",
         encryption_enabled ? "enabled" : "disabled",
         bt_str(result));

  // If peer and local Secure Connections support are present, the pairing logic
  // needs to verify that the status received in the Encryption Changed event is
  // for AES encryption.
  if (use_secure_connections_ &&
      encryption_status_ !=
          pw::bluetooth::emboss::EncryptionStatus::ON_WITH_AES_FOR_BREDR) {
    bt_log(DEBUG,
           "hci",
           "BR/EDR Secure Connection must use AES encryption. Closing "
           "connection...");
    HandleEncryptionStatus(fit::error(Error(HostError::kInsufficientSecurity)),
                           /*key_refreshed=*/false);
    return CommandChannel::EventCallbackResult::kContinue;
  }

  HandleEncryptionStatus(result.is_ok()
                             ? Result<bool>(fit::ok(encryption_enabled))
                             : result.take_error(),
                         /*key_refreshed=*/false);
  return CommandChannel::EventCallbackResult::kContinue;
}

CommandChannel::EventCallbackResult
AclConnection::OnEncryptionKeyRefreshCompleteEvent(
    const EmbossEventPacket& event) {
  const auto params =
      event
          .view<pw::bluetooth::emboss::EncryptionKeyRefreshCompleteEventView>();
  const hci_spec::ConnectionHandle handle = params.connection_handle().Read();

  // Silently ignore this event as it isn't meant for this connection.
  if (handle != this->handle()) {
    return CommandChannel::EventCallbackResult::kContinue;
  }

  if (state() != Connection::State::kConnected) {
    bt_log(
        DEBUG, "hci", "encryption key refresh ignored for closed connection");
    return CommandChannel::EventCallbackResult::kContinue;
  }

  Result<> status = event.ToResult();
  bt_log(DEBUG, "hci", "encryption key refresh %s", bt_str(status));

  // Report that encryption got disabled on failure status. The accuracy of this
  // isn't that important since the link will be disconnected.
  HandleEncryptionStatus(status.is_ok()
                             ? Result<bool>(fit::ok(/*enabled=*/true))
                             : status.take_error(),
                         /*key_refreshed=*/true);

  return CommandChannel::EventCallbackResult::kContinue;
}

}  // namespace bt::hci
