// 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_proxy_private/test_utils.h"

#include <cstdint>

#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/hci_common.emb.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/hci_events.emb.h"
#include "pw_bluetooth/hci_h4.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_bluetooth_proxy/basic_l2cap_channel.h"
#include "pw_bluetooth_proxy/direction.h"
#include "pw_bluetooth_proxy/h4_packet.h"
#include "pw_bluetooth_proxy/internal/logical_transport.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_bluetooth_proxy/proxy_host.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
#include "pw_unit_test/framework.h"

namespace pw::bluetooth::proxy {

// Allocate storage and populate an ACL packet header with the given length.
Result<AclFrameWithStorage> SetupAcl(uint16_t handle, uint16_t l2cap_length) {
  AclFrameWithStorage frame;

  frame.storage.resize(l2cap_length + emboss::AclDataFrame::MinSizeInBytes() +
                       AclFrameWithStorage::kH4HeaderSize);
  std::fill(frame.storage.begin(), frame.storage.end(), 0);
  frame.storage[0] = cpp23::to_underlying(emboss::H4PacketType::ACL_DATA);
  PW_TRY_ASSIGN(frame.writer,
                MakeEmbossWriter<emboss::AclDataFrameWriter>(frame.hci_span()));
  frame.writer.header().handle().Write(handle);
  frame.writer.header().packet_boundary_flag().Write(
      emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE);
  frame.writer.header().broadcast_flag().Write(
      emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
  frame.writer.data_total_length().Write(l2cap_length);
  EXPECT_EQ(l2cap_length,
            frame.writer.payload().BackingStorage().SizeInBytes());
  return frame;
}

Result<BFrameWithStorage> SetupBFrame(uint16_t handle,
                                      uint16_t channel_id,
                                      uint16_t bframe_len) {
  BFrameWithStorage frame;
  PW_TRY_ASSIGN(
      frame.acl,
      SetupAcl(handle,
               bframe_len + emboss::BasicL2capHeader::IntrinsicSizeInBytes()));

  PW_TRY_ASSIGN(frame.writer,
                MakeEmbossWriter<emboss::BFrameWriter>(
                    frame.acl.writer.payload().BackingStorage().data(),
                    frame.acl.writer.payload().SizeInBytes()));
  frame.writer.pdu_length().Write(bframe_len);
  frame.writer.channel_id().Write(channel_id);
  EXPECT_TRUE(frame.writer.Ok());
  EXPECT_EQ(frame.writer.payload().SizeInBytes(), bframe_len);
  return frame;
}

Result<CFrameWithStorage> SetupCFrame(uint16_t handle,
                                      uint16_t channel_id,
                                      uint16_t cframe_len) {
  CFrameWithStorage frame;
  PW_TRY_ASSIGN(
      frame.acl,
      SetupAcl(handle,
               cframe_len + emboss::BasicL2capHeader::IntrinsicSizeInBytes()));

  PW_TRY_ASSIGN(frame.writer,
                MakeEmbossWriter<emboss::CFrameWriter>(
                    frame.acl.writer.payload().BackingStorage().data(),
                    frame.acl.writer.payload().SizeInBytes()));
  frame.writer.pdu_length().Write(cframe_len);
  frame.writer.channel_id().Write(channel_id);
  EXPECT_TRUE(frame.writer.Ok());
  EXPECT_EQ(frame.writer.payload().SizeInBytes(), cframe_len);
  return frame;
}

Result<KFrameWithStorage> SetupKFrame(uint16_t handle,
                                      uint16_t channel_id,
                                      uint16_t mps,
                                      uint16_t segment_no,
                                      span<const uint8_t> payload) {
  uint16_t sdu_length_field_offset = segment_no == 0 ? kSduLengthFieldSize : 0;
  // Requested segment encodes payload starting at `payload[payload_offset]`.
  uint16_t payload_offset =
      segment_no * mps - (segment_no == 0 ? 0 : kSduLengthFieldSize);
  if (payload_offset >= payload.size()) {
    return Status::OutOfRange();
  }
  uint16_t remaining_payload_length = payload.size() - payload_offset;
  uint16_t segment_pdu_length =
      remaining_payload_length + sdu_length_field_offset;
  if (segment_pdu_length > mps) {
    segment_pdu_length = mps;
  }

  KFrameWithStorage kframe;
  PW_TRY_ASSIGN(kframe.acl,
                SetupAcl(handle,
                         segment_pdu_length +
                             emboss::BasicL2capHeader::IntrinsicSizeInBytes()));

  if (segment_no == 0) {
    PW_TRY_ASSIGN(kframe.writer,
                  MakeEmbossWriter<emboss::FirstKFrameWriter>(
                      kframe.acl.writer.payload().BackingStorage().data(),
                      kframe.acl.writer.payload().SizeInBytes()));
    emboss::FirstKFrameWriter first_kframe_writer =
        std::get<emboss::FirstKFrameWriter>(kframe.writer);
    first_kframe_writer.pdu_length().Write(segment_pdu_length);
    first_kframe_writer.channel_id().Write(channel_id);
    first_kframe_writer.sdu_length().Write(payload.size());
    EXPECT_TRUE(first_kframe_writer.Ok());
    PW_CHECK(TryToCopyToEmbossStruct(
        /*emboss_dest=*/first_kframe_writer.payload(),
        /*src=*/payload.subspan(payload_offset,
                                segment_pdu_length - sdu_length_field_offset)));
  } else {
    PW_TRY_ASSIGN(kframe.writer,
                  MakeEmbossWriter<emboss::SubsequentKFrameWriter>(
                      kframe.acl.writer.payload().BackingStorage().data(),
                      kframe.acl.writer.payload().SizeInBytes()));
    emboss::SubsequentKFrameWriter subsequent_kframe_writer =
        std::get<emboss::SubsequentKFrameWriter>(kframe.writer);
    subsequent_kframe_writer.pdu_length().Write(segment_pdu_length);
    subsequent_kframe_writer.channel_id().Write(channel_id);
    EXPECT_TRUE(subsequent_kframe_writer.Ok());
    PW_CHECK(TryToCopyToEmbossStruct(
        /*emboss_dest=*/subsequent_kframe_writer.payload(),
        /*src=*/payload.subspan(payload_offset,
                                segment_pdu_length - sdu_length_field_offset)));
  }

  return kframe;
}

// Send an LE_Read_Buffer_Size (V2) CommandComplete event to `proxy` to request
// the reservation of a number of LE ACL send credits.
Status SendLeReadBufferResponseFromController(
    ProxyHost& proxy,
    uint8_t num_credits_to_reserve,
    uint16_t le_acl_data_packet_length) {
  std::array<
      uint8_t,
      emboss::LEReadBufferSizeV2CommandCompleteEventWriter::SizeInBytes()>
      hci_arr{};
  H4PacketWithHci h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
  PW_TRY_ASSIGN(auto view,
                CreateAndPopulateToHostEventWriter<
                    emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(
                    h4_packet, emboss::EventCode::COMMAND_COMPLETE));
  view.command_complete().command_opcode().Write(
      emboss::OpCode::LE_READ_BUFFER_SIZE_V2);
  view.total_num_le_acl_data_packets().Write(num_credits_to_reserve);
  view.le_acl_data_packet_length().Write(le_acl_data_packet_length);

  proxy.HandleH4HciFromController(std::move(h4_packet));
  return OkStatus();
}

Status SendReadBufferResponseFromController(ProxyHost& proxy,
                                            uint8_t num_credits_to_reserve) {
  std::array<uint8_t,
             emboss::ReadBufferSizeCommandCompleteEventWriter::SizeInBytes()>
      hci_arr{};
  H4PacketWithHci h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
  PW_TRY_ASSIGN(auto view,
                CreateAndPopulateToHostEventWriter<
                    emboss::ReadBufferSizeCommandCompleteEventWriter>(
                    h4_packet, emboss::EventCode::COMMAND_COMPLETE));
  view.command_complete().command_opcode().Write(
      emboss::OpCode::READ_BUFFER_SIZE);
  view.total_num_acl_data_packets().Write(num_credits_to_reserve);
  view.acl_data_packet_length().Write(0xFFFF);
  view.synchronous_data_packet_length().Write(0xFF);
  EXPECT_TRUE(view.Ok());

  proxy.HandleH4HciFromController(std::move(h4_packet));
  return OkStatus();
}

// Send a Connection_Complete event to `proxy` indicating the provided
// `handle` has disconnected.
Status SendConnectionCompleteEvent(ProxyHost& proxy,
                                   uint16_t handle,
                                   emboss::StatusCode status) {
  std::array<uint8_t, emboss::ConnectionCompleteEvent::IntrinsicSizeInBytes()>
      hci_arr{};
  H4PacketWithHci h4_packet{emboss::H4PacketType::EVENT, hci_arr};
  PW_TRY_ASSIGN(
      auto view,
      CreateAndPopulateToHostEventWriter<emboss::ConnectionCompleteEventWriter>(
          h4_packet, emboss::EventCode::CONNECTION_COMPLETE));
  view.status().Write(status);
  view.connection_handle().Write(handle);
  proxy.HandleH4HciFromController(std::move(h4_packet));
  return OkStatus();
}

// Send a LE_Connection_Complete event to `proxy` indicating the provided
// `handle` has disconnected.
Status SendLeConnectionCompleteEvent(ProxyHost& proxy,
                                     uint16_t handle,
                                     emboss::StatusCode status) {
  std::array<uint8_t,
             emboss::LEConnectionCompleteSubevent::IntrinsicSizeInBytes()>
      hci_arr_dc{};
  H4PacketWithHci dc_event{emboss::H4PacketType::EVENT, hci_arr_dc};
  PW_TRY_ASSIGN(auto view,
                MakeEmbossWriter<emboss::LEConnectionCompleteSubeventWriter>(
                    dc_event.GetHciSpan()));
  view.le_meta_event().header().event_code().Write(
      emboss::EventCode::LE_META_EVENT);
  view.le_meta_event().subevent_code_enum().Write(
      emboss::LeSubEventCode::CONNECTION_COMPLETE);
  view.status().Write(status);
  view.connection_handle().Write(handle);
  proxy.HandleH4HciFromController(std::move(dc_event));
  return OkStatus();
}

// Send a Disconnection_Complete event to `proxy` indicating the provided
// `handle` has disconnected.
Status SendDisconnectionCompleteEvent(ProxyHost& proxy,
                                      uint16_t handle,
                                      Direction direction,
                                      bool successful) {
  std::array<uint8_t,
             sizeof(emboss::H4PacketType) +
                 emboss::DisconnectionCompleteEvent::IntrinsicSizeInBytes()>
      h4_arr_dc;
  h4_arr_dc.fill(0);
  H4PacketWithHci dc_event_from_controller{emboss::H4PacketType::EVENT,
                                           pw::span(h4_arr_dc).subspan(1)};
  H4PacketWithH4 dc_event_from_host{emboss::H4PacketType::EVENT, h4_arr_dc};
  PW_TRY_ASSIGN(auto view,
                MakeEmbossWriter<emboss::DisconnectionCompleteEventWriter>(
                    dc_event_from_controller.GetHciSpan()));
  view.header().event_code().Write(emboss::EventCode::DISCONNECTION_COMPLETE);
  view.header().parameter_total_size().Write(
      emboss::DisconnectionCompleteEvent::IntrinsicSizeInBytes() -
      emboss::EventHeader::IntrinsicSizeInBytes());
  view.status().Write(successful ? emboss::StatusCode::SUCCESS
                                 : emboss::StatusCode::HARDWARE_FAILURE);
  view.connection_handle().Write(handle);
  if (direction == Direction::kFromController) {
    proxy.HandleH4HciFromController(std::move(dc_event_from_controller));
  } else if (direction == Direction::kFromHost) {
    proxy.HandleH4HciFromHost(std::move(dc_event_from_host));
  } else {
    return Status::InvalidArgument();
  }
  return OkStatus();
}

Status SendL2capConnectionReq(ProxyHost& proxy,
                              Direction direction,
                              uint16_t handle,
                              uint16_t source_cid,
                              uint16_t psm) {
  // First send CONNECTION_REQ to setup partial connection.
  constexpr size_t kConnectionReqLen =
      emboss::L2capConnectionReq::IntrinsicSizeInBytes();
  PW_TRY_ASSIGN(
      CFrameWithStorage cframe,
      SetupCFrame(handle,
                  cpp23::to_underlying(emboss::L2capFixedCid::ACL_U_SIGNALING),
                  kConnectionReqLen));

  emboss::L2capConnectionReqWriter conn_req_writer =
      emboss::MakeL2capConnectionReqView(
          cframe.writer.payload().BackingStorage().data(),
          cframe.writer.payload().SizeInBytes());
  conn_req_writer.command_header().code().Write(
      emboss::L2capSignalingPacketCode::CONNECTION_REQ);
  // Note data_length doesn't include command header.
  conn_req_writer.command_header().data_length().Write(
      kConnectionReqLen -
      emboss::L2capSignalingCommandHeader::IntrinsicSizeInBytes());
  conn_req_writer.psm().Write(psm);
  conn_req_writer.source_cid().Write(source_cid);

  if (direction == Direction::kFromController) {
    H4PacketWithHci packet{emboss::H4PacketType::ACL_DATA,
                           cframe.acl.hci_span()};
    proxy.HandleH4HciFromController(std::move(packet));
  } else if (direction == Direction::kFromHost) {
    H4PacketWithH4 packet{emboss::H4PacketType::ACL_DATA, cframe.acl.h4_span()};
    proxy.HandleH4HciFromHost(std::move(packet));
  } else {
    return Status::InvalidArgument();
  }

  return OkStatus();
}

Status SendL2capConfigureReq(ProxyHost& proxy,
                             Direction direction,
                             uint16_t handle,
                             uint16_t destination_cid,
                             L2capOptions& l2cap_options) {
  size_t kOptionsSize = 0;
  if (l2cap_options.mtu.has_value()) {
    kOptionsSize += emboss::L2capMtuConfigurationOption::IntrinsicSizeInBytes();
  }
  const size_t kConfigureReqLen =
      emboss::L2capConfigureReq::MinSizeInBytes() + kOptionsSize;

  PW_TRY_ASSIGN(
      CFrameWithStorage cframe,
      SetupCFrame(handle,
                  cpp23::to_underlying(emboss::L2capFixedCid::ACL_U_SIGNALING),
                  kConfigureReqLen));

  auto configure_req_writer = MakeEmbossWriter<emboss::L2capConfigureReqWriter>(
      cframe.writer.payload().SizeInBytes(),
      cframe.writer.payload().BackingStorage().data(),
      cframe.writer.payload().SizeInBytes());
  configure_req_writer->command_header().code().Write(
      emboss::L2capSignalingPacketCode::CONFIGURATION_REQ);
  configure_req_writer->command_header().data_length().Write(
      kConfigureReqLen -
      emboss::L2capSignalingCommandHeader::IntrinsicSizeInBytes());
  configure_req_writer->destination_cid().Write(destination_cid);
  configure_req_writer->continuation_flag().Write(false);

  if (l2cap_options.mtu.has_value()) {
    constexpr size_t buffer_size =
        emboss::L2capMtuConfigurationOption::IntrinsicSizeInBytes();
    std::array<uint8_t, buffer_size> l2cap_option_buffer{};

    auto l2cap_option =
        MakeEmbossWriter<emboss::L2capMtuConfigurationOptionWriter>(
            &l2cap_option_buffer);
    l2cap_option->header().option_type().Write(
        emboss::L2capConfigurationOptionType::MTU);
    l2cap_option->header().option_length().Write(2);
    l2cap_option->mtu().Write(l2cap_options.mtu.value().mtu);

    memcpy(configure_req_writer->options().BackingStorage().data(),
           l2cap_option_buffer.data(),
           l2cap_option_buffer.size());
  }

  if (direction == Direction::kFromController) {
    H4PacketWithHci configuration_req_packet{emboss::H4PacketType::ACL_DATA,
                                             cframe.acl.hci_span()};
    proxy.HandleH4HciFromController(std::move(configuration_req_packet));
  } else {
    H4PacketWithH4 configure_req_packet{emboss::H4PacketType::ACL_DATA,
                                        cframe.acl.h4_span()};
    proxy.HandleH4HciFromHost(std::move(configure_req_packet));
  }

  return OkStatus();
}

Status SendL2capConfigureRsp(ProxyHost& proxy,
                             Direction direction,
                             uint16_t handle,
                             uint16_t local_cid,
                             emboss::L2capConfigurationResult result) {
  constexpr size_t kConfigureRspLen =
      emboss::L2capConfigureRsp::MinSizeInBytes();
  PW_TRY_ASSIGN(
      CFrameWithStorage cframe,
      SetupCFrame(handle,
                  cpp23::to_underlying(emboss::L2capFixedCid::ACL_U_SIGNALING),
                  kConfigureRspLen));

  auto configure_rsp_writer = MakeEmbossWriter<emboss::L2capConfigureRspWriter>(
      cframe.writer.payload().SizeInBytes(),
      cframe.writer.payload().BackingStorage().data(),
      cframe.writer.payload().SizeInBytes());
  configure_rsp_writer->command_header().code().Write(
      emboss::L2capSignalingPacketCode::CONFIGURATION_RSP);
  configure_rsp_writer->command_header().data_length().Write(
      kConfigureRspLen -
      emboss::L2capSignalingCommandHeader::IntrinsicSizeInBytes());
  configure_rsp_writer->source_cid().Write(local_cid);
  configure_rsp_writer->continuation_flag().Write(false);
  configure_rsp_writer->result().Write(result);

  if (direction == Direction::kFromController) {
    H4PacketWithHci configure_rsp_packet{emboss::H4PacketType::ACL_DATA,
                                         cframe.acl.hci_span()};
    proxy.HandleH4HciFromController(std::move(configure_rsp_packet));
  } else {
    H4PacketWithH4 configure_rsp_packet{emboss::H4PacketType::ACL_DATA,
                                        cframe.acl.h4_span()};
    proxy.HandleH4HciFromHost(std::move(configure_rsp_packet));
  }

  return OkStatus();
}

Status SendL2capConnectionRsp(
    ProxyHost& proxy,
    Direction direction,
    uint16_t handle,
    uint16_t source_cid,
    uint16_t destination_cid,
    emboss::L2capConnectionRspResultCode result_code) {
  constexpr size_t kConnectionRspLen =
      emboss::L2capConnectionRsp::MinSizeInBytes();
  PW_TRY_ASSIGN(
      auto cframe,
      SetupCFrame(handle,
                  cpp23::to_underlying(emboss::L2capFixedCid::ACL_U_SIGNALING),
                  kConnectionRspLen));

  emboss::L2capConnectionRspWriter conn_rsp_writer =
      emboss::MakeL2capConnectionRspView(
          cframe.writer.payload().BackingStorage().data(),
          cframe.writer.payload().SizeInBytes());
  conn_rsp_writer.command_header().code().Write(
      emboss::L2capSignalingPacketCode::CONNECTION_RSP);

  conn_rsp_writer.command_header().data_length().Write(
      kConnectionRspLen -
      emboss::L2capSignalingCommandHeader::IntrinsicSizeInBytes());
  conn_rsp_writer.source_cid().Write(source_cid);
  conn_rsp_writer.destination_cid().Write(destination_cid);
  conn_rsp_writer.result().Write(result_code);

  if (direction == Direction::kFromController) {
    H4PacketWithHci packet{emboss::H4PacketType::ACL_DATA,
                           cframe.acl.hci_span()};
    proxy.HandleH4HciFromController(std::move(packet));
  } else if (direction == Direction::kFromHost) {
    H4PacketWithH4 packet{emboss::H4PacketType::ACL_DATA, cframe.acl.h4_span()};
    proxy.HandleH4HciFromHost(std::move(packet));
  } else {
    return Status::InvalidArgument();
  }

  return OkStatus();
}

Status SendL2capDisconnectRsp(ProxyHost& proxy,
                              Direction direction,
                              AclTransportType transport,
                              uint16_t handle,
                              uint16_t source_cid,
                              uint16_t destination_cid) {
  constexpr size_t kDisconnectionRspLen =
      emboss::L2capDisconnectionRsp::MinSizeInBytes();
  PW_TRY_ASSIGN(
      auto cframe,
      SetupCFrame(
          handle,
          transport == AclTransportType::kBrEdr
              ? cpp23::to_underlying(emboss::L2capFixedCid::ACL_U_SIGNALING)
              : cpp23::to_underlying(emboss::L2capFixedCid::LE_U_SIGNALING),
          kDisconnectionRspLen));

  emboss::L2capDisconnectionRspWriter disconn_rsp_writer =
      emboss::MakeL2capDisconnectionRspView(
          cframe.writer.payload().BackingStorage().data(),
          cframe.writer.payload().SizeInBytes());
  disconn_rsp_writer.command_header().code().Write(
      emboss::L2capSignalingPacketCode::DISCONNECTION_RSP);

  disconn_rsp_writer.command_header().data_length().Write(
      kDisconnectionRspLen -
      emboss::L2capSignalingCommandHeader::IntrinsicSizeInBytes());
  disconn_rsp_writer.source_cid().Write(source_cid);
  disconn_rsp_writer.destination_cid().Write(destination_cid);

  if (direction == Direction::kFromHost) {
    H4PacketWithH4 packet{emboss::H4PacketType::ACL_DATA, cframe.acl.h4_span()};
    proxy.HandleH4HciFromHost(std::move(packet));
  } else if (direction == Direction::kFromController) {
    H4PacketWithHci packet{emboss::H4PacketType::ACL_DATA,
                           cframe.acl.hci_span()};
    proxy.HandleH4HciFromController(std::move(packet));
  } else {
    return Status::InvalidArgument();
  }
  return OkStatus();
}

void SendL2capBFrame(ProxyHost& proxy,
                     uint16_t handle,
                     pw::span<const uint8_t> payload,
                     size_t pdu_length,
                     uint16_t channel_id) {
  constexpr size_t kHeadersSize =
      emboss::AclDataFrameHeader::IntrinsicSizeInBytes() +
      emboss::BasicL2capHeader::IntrinsicSizeInBytes();

  const size_t acl_data_size =
      emboss::BasicL2capHeader::IntrinsicSizeInBytes() + payload.size();

  std::vector<uint8_t> hci_buf(kHeadersSize + payload.size());
  H4PacketWithHci h4_packet{emboss::H4PacketType::ACL_DATA, hci_buf};

  // ACL header
  Result<emboss::AclDataFrameWriter> acl =
      MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan());
  acl->header().handle().Write(handle);
  acl->data_total_length().Write(acl_data_size);

  // L2CAP B-Frame header
  emboss::BFrameWriter bframe = emboss::MakeBFrameView(
      acl->payload().BackingStorage().data(), acl->payload().SizeInBytes());
  bframe.pdu_length().Write(pdu_length);
  bframe.channel_id().Write(channel_id);

  // Payload
  std::copy(payload.begin(),
            payload.end(),
            h4_packet.GetHciSpan().begin() + kHeadersSize);

  proxy.HandleH4HciFromController(std::move(h4_packet));
}

void SendAclContinuingFrag(ProxyHost& proxy,
                           uint16_t handle,
                           pw::span<const uint8_t> payload) {
  constexpr size_t kHeadersSize =
      emboss::AclDataFrameHeader::IntrinsicSizeInBytes();
  // No BasicL2capHeader.

  const size_t acl_data_size =
      // No BasicL2capHeader.
      payload.size();

  std::vector<uint8_t> hci_buf(kHeadersSize + payload.size());
  H4PacketWithHci h4_packet{emboss::H4PacketType::ACL_DATA, hci_buf};

  // ACL header
  Result<emboss::AclDataFrameWriter> acl =
      MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan());
  acl->header().handle().Write(handle);
  acl->header().packet_boundary_flag().Write(
      emboss::AclDataPacketBoundaryFlag::CONTINUING_FRAGMENT);
  acl->header().broadcast_flag().Write(
      emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
  acl->data_total_length().Write(acl_data_size);

  // Payload
  std::copy(payload.begin(),
            payload.end(),
            h4_packet.GetHciSpan().begin() + kHeadersSize);

  proxy.HandleH4HciFromController(std::move(h4_packet));
}

pw::Result<L2capCoc> ProxyHostTest::BuildCocWithResult(ProxyHost& proxy,
                                                       CocParameters params) {
  return proxy.AcquireL2capCoc(
      /*rx_multibuf_allocator=*/sut_multibuf_allocator_,
      params.handle,
      {.cid = params.local_cid,
       .mtu = params.rx_mtu,
       .mps = params.rx_mps,
       .credits = params.rx_credits},
      {.cid = params.remote_cid,
       .mtu = params.tx_mtu,
       .mps = params.tx_mps,
       .credits = params.tx_credits},
      std::move(params.receive_fn),
      std::move(params.event_fn));
}

L2capCoc ProxyHostTest::BuildCoc(ProxyHost& proxy, CocParameters params) {
  pw::Result<L2capCoc> channel = BuildCocWithResult(proxy, std::move(params));
  PW_TEST_EXPECT_OK(channel);
  return std::move(channel.value());
}

Result<BasicL2capChannel> ProxyHostTest::BuildBasicL2capChannelWithResult(
    ProxyHost& proxy, BasicL2capParameters params) {
  multibuf::MultiBufAllocator* rx_multibuf_allocator =
      params.rx_multibuf_allocator ? params.rx_multibuf_allocator
                                   : &sut_multibuf_allocator_;
  return proxy.AcquireBasicL2capChannel(
      *rx_multibuf_allocator,
      params.handle,
      params.local_cid,
      params.remote_cid,
      params.transport,
      std::move(params.payload_from_controller_fn),
      std::move(params.payload_from_host_fn),
      std::move(params.event_fn));
}

BasicL2capChannel ProxyHostTest::BuildBasicL2capChannel(
    ProxyHost& proxy, BasicL2capParameters params) {
  pw::Result<BasicL2capChannel> channel =
      BuildBasicL2capChannelWithResult(proxy, std::move(params));
  PW_TEST_EXPECT_OK(channel);
  return std::move(channel.value());
}

Result<GattNotifyChannel> ProxyHostTest::BuildGattNotifyChannelWithResult(
    ProxyHost& proxy, GattNotifyChannelParameters params) {
  return proxy.AcquireGattNotifyChannel(
      params.handle, params.attribute_handle, std::move(params.event_fn));
}

GattNotifyChannel ProxyHostTest::BuildGattNotifyChannel(
    ProxyHost& proxy, GattNotifyChannelParameters params) {
  pw::Result<GattNotifyChannel> channel =
      BuildGattNotifyChannelWithResult(proxy, std::move(params));
  PW_TEST_EXPECT_OK(channel);
  return std::move(channel.value());
}

RfcommChannel ProxyHostTest::BuildRfcomm(
    ProxyHost& proxy,
    RfcommParameters params,
    Function<void(multibuf::MultiBuf&& payload)>&& receive_fn,
    ChannelEventCallback&& event_fn) {
  pw::Result<RfcommChannel> channel = proxy.AcquireRfcommChannel(
      sut_multibuf_allocator_,
      params.handle,
      RfcommChannel::Config{
          .cid = params.rx_config.cid,
          .max_information_length = params.rx_config.max_information_length,
          .credits = params.rx_config.credits},
      RfcommChannel::Config{
          .cid = params.tx_config.cid,
          .max_information_length = params.tx_config.max_information_length,
          .credits = params.tx_config.credits},
      params.rfcomm_channel,
      std::move(receive_fn),
      std::move(event_fn));
  PW_TEST_EXPECT_OK(channel);
  return std::move((channel.value()));
}

}  // namespace pw::bluetooth::proxy
