Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5388137 authored by Henri Chataing's avatar Henri Chataing
Browse files

RootCanal: Properly handle concurrent connection attempts

Previously, if two devices attempted to initiate a connection
simultaneously, two situations could occur:

- none of the attemps succeeds, as both have a local pending
  to the peer and the state does not allow for another pending
  connection

- one connection succeeds, but the other initiated connection
  ends with the HCI event Connect Complete (ERR_PAGE_TIMEOUT).
  This causes Android to disconnect the link afterwards.

This change adds an additional Page state to save the active
paging information. The pending connection state is reserved
for accepting remote connection requests.

Bug: 285597323
Test: atest --host rootcanal_ll_test
Change-Id: I24b469e73fbc02ed0525d712fbe4c1ac7390fb31
parent c74eee81
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ class HFPProxy(ProfileProxy):
        th.start()

    def test_started(self, test: str, pts_addr: bytes, **kwargs):
        if test not in ("HFP/AG/SLC/BV-02-C", "HFP/AG/SLC/BV-04-C"):
            self.asyncWaitConnection(pts_addr)

        return "OK"
+1 −0
Original line number Diff line number Diff line
@@ -316,6 +316,7 @@ python_test_host {
        "test/LL/DDI/ADV/*.py",
        "test/LL/DDI/SCN/*.py",
        "test/LMP/LIH/*.py",
        "test/LMP/*.py",
        "test/main.py",
    ],
    data: [
+15 −4
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ uint16_t AclConnectionHandler::GetUnusedHandle() {
bool AclConnectionHandler::CreatePendingConnection(Address addr,
                                                   bool authenticate_on_connect,
                                                   bool allow_role_switch) {
  if (classic_connection_pending_) {
  if (classic_connection_pending_ || GetAclConnectionHandle(addr).has_value()) {
    return false;
  }
  classic_connection_pending_ = true;
@@ -132,9 +132,9 @@ bool AclConnectionHandler::CancelPendingLeConnection(AddressWithType addr) {
  return true;
}

uint16_t AclConnectionHandler::CreateConnection(Address addr,
                                                Address own_addr) {
  if (CancelPendingConnection(addr)) {
uint16_t AclConnectionHandler::CreateConnection(Address addr, Address own_addr,
                                                bool pending) {
  if (!pending || CancelPendingConnection(addr)) {
    uint16_t handle = GetUnusedHandle();
    acl_connections_.emplace(
        handle,
@@ -199,6 +199,17 @@ uint16_t AclConnectionHandler::GetHandleOnlyAddress(
  return kReservedHandle;
}

std::optional<uint16_t> AclConnectionHandler::GetAclConnectionHandle(
    bluetooth::hci::Address bd_addr) const {
  for (auto const& [handle, connection] : acl_connections_) {
    if (connection.GetAddress().GetAddress() == bd_addr &&
        connection.GetPhyType() == Phy::Type::BR_EDR) {
      return handle;
    }
  }
  return {};
}

AclConnection& AclConnectionHandler::GetAclConnection(uint16_t handle) {
  ASSERT_LOG(HasHandle(handle), "Unknown handle %d", handle);
  return acl_connections_.at(handle);
+9 −1
Original line number Diff line number Diff line
@@ -75,8 +75,11 @@ class AclConnectionHandler {
  bool HasPendingLeConnection(bluetooth::hci::AddressWithType addr) const;
  bool CancelPendingLeConnection(bluetooth::hci::AddressWithType addr);

  // \p pending is true if the connection is expected to be
  // in pending state.
  uint16_t CreateConnection(bluetooth::hci::Address addr,
                            bluetooth::hci::Address own_addr);
                            bluetooth::hci::Address own_addr,
                            bool pending = true);
  uint16_t CreateLeConnection(bluetooth::hci::AddressWithType addr,
                              bluetooth::hci::AddressWithType own_addr,
                              bluetooth::hci::Role role);
@@ -84,6 +87,11 @@ class AclConnectionHandler {
  bool HasHandle(uint16_t handle) const;
  bool HasScoHandle(uint16_t handle) const;

  // Return the connection handle for a classic ACL connection only.
  // \p bd_addr is the peer address.
  std::optional<uint16_t> GetAclConnectionHandle(
      bluetooth::hci::Address bd_addr) const;

  uint16_t GetHandle(bluetooth::hci::AddressWithType addr) const;
  uint16_t GetHandleOnlyAddress(bluetooth::hci::Address addr) const;
  bluetooth::hci::AddressWithType GetAddress(uint16_t handle) const;
+155 −73
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ using TaskId = rootcanal::LinkLayerController::TaskId;
namespace rootcanal {

constexpr milliseconds kNoDelayMs(0);
constexpr milliseconds kPageInterval(1000);

const Address& LinkLayerController::GetAddress() const { return address_; }

@@ -5021,75 +5022,87 @@ void LinkLayerController::LeSynchronization() {

void LinkLayerController::IncomingPagePacket(
    model::packets::LinkLayerPacketView incoming) {
  auto bd_addr = incoming.GetSourceAddress();
  auto page = model::packets::PageView::Create(incoming);
  ASSERT(page.IsValid());
  INFO(id_, "from {}", incoming.GetSourceAddress());

  // Cannot establish two BR-EDR connections with the same peer.
  if (connections_.GetAclConnectionHandle(bd_addr).has_value()) {
    return;
  }

  bool allow_role_switch = page.GetAllowRoleSwitch();
  if (!connections_.CreatePendingConnection(
          incoming.GetSourceAddress(),
          authentication_enable_ == AuthenticationEnable::REQUIRED,
          bd_addr, authentication_enable_ == AuthenticationEnable::REQUIRED,
          allow_role_switch)) {
    // Send a response to indicate that we're busy, or drop the packet?
    WARNING(id_, "Failed to create a pending connection for {}",
            incoming.GetSourceAddress());
    // Will be triggered when multiple hosts are paging simultaneously;
    // only one connection will be accepted.
    WARNING(id_, "Failed to create a pending connection for {}", bd_addr);
    return;
  }

  bluetooth::hci::Address source_address{};
  bluetooth::hci::Address::FromString(page.GetSourceAddress().ToString(),
                                      source_address);

  if (IsEventUnmasked(EventCode::CONNECTION_REQUEST)) {
    send_event_(bluetooth::hci::ConnectionRequestBuilder::Create(
        source_address, page.GetClassOfDevice(),
        bd_addr, page.GetClassOfDevice(),
        bluetooth::hci::ConnectionRequestLinkType::ACL));
  }
}

void LinkLayerController::IncomingPageRejectPacket(
    model::packets::LinkLayerPacketView incoming) {
  INFO(id_, "{}", incoming.GetSourceAddress());
  auto bd_addr = incoming.GetSourceAddress();
  auto reject = model::packets::PageRejectView::Create(incoming);
  ASSERT(reject.IsValid());
  INFO(id_, "Sending CreateConnectionComplete");

  if (!page_.has_value() || page_->bd_addr != bd_addr) {
    INFO(id_,
         "ignoring Page Reject packet received when not in Page state,"
         " or paging to a different address");
    return;
  }

  INFO(id_, "Received Page Reject packet from {}", bd_addr);
  page_ = {};

  if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
    send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
        static_cast<ErrorCode>(reject.GetReason()), 0x0eff,
        incoming.GetSourceAddress(), bluetooth::hci::LinkType::ACL,
        bluetooth::hci::Enable::DISABLED));
        static_cast<ErrorCode>(reject.GetReason()), 0, bd_addr,
        bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));
  }
}

void LinkLayerController::IncomingPageResponsePacket(
    model::packets::LinkLayerPacketView incoming) {
  Address peer = incoming.GetSourceAddress();
  INFO(id_, "{}", peer);
  uint16_t handle =
      connections_.CreateConnection(peer, incoming.GetDestinationAddress());
  if (handle == kReservedHandle) {
    WARNING(id_, "No free handles");
  auto bd_addr = incoming.GetSourceAddress();
  auto response = model::packets::PageResponseView::Create(incoming);
  ASSERT(response.IsValid());

  if (!page_.has_value() || page_->bd_addr != bd_addr) {
    INFO(id_,
         "ignoring Page Response packet received when not in Page state,"
         " or paging to a different address");
    return;
  }

  CancelScheduledTask(page_timeout_task_id_);
  ASSERT(link_manager_add_link(
      lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(peer.data())));

  CheckExpiringConnection(handle);
  INFO(id_, "Received Page Response packet from {}", bd_addr);

  AclConnection& connection = connections_.GetAclConnection(handle);
  auto bd_addr = incoming.GetSourceAddress();
  auto response = model::packets::PageResponseView::Create(incoming);
  ASSERT(response.IsValid());
  uint16_t connection_handle =
      connections_.CreateConnection(bd_addr, GetAddress(), false);
  ASSERT(connection_handle != kReservedHandle);

  bluetooth::hci::Role role =
      connections_.IsRoleSwitchAllowedForPendingConnection() &&
              response.GetTryRoleSwitch()
      page_->allow_role_switch && response.GetTryRoleSwitch()
          ? bluetooth::hci::Role::PERIPHERAL
          : bluetooth::hci::Role::CENTRAL;

  AclConnection& connection = connections_.GetAclConnection(connection_handle);
  CheckExpiringConnection(connection_handle);
  connection.SetLinkPolicySettings(default_link_policy_settings_);
  connection.SetRole(role);
  page_ = {};

  ASSERT(link_manager_add_link(
      lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(bd_addr.data())));

  // Role change event before connection complete generates an HCI Role Change
  // event on the initiator side if accepted; the event is sent before the
@@ -5102,13 +5115,15 @@ void LinkLayerController::IncomingPageResponsePacket(

  if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
    send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
        ErrorCode::SUCCESS, handle, bd_addr, bluetooth::hci::LinkType::ACL,
        bluetooth::hci::Enable::DISABLED));
        ErrorCode::SUCCESS, connection_handle, bd_addr,
        bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));
  }
}

void LinkLayerController::Tick() {
  RunPendingTasks();
  Paging();

  if (inquiry_timer_task_id_ != kInvalidTaskId) {
    Inquiry();
  }
@@ -5222,11 +5237,13 @@ ErrorCode LinkLayerController::AcceptConnectionRequest(const Address& bd_addr,
        link_parameters.extended));

    // Schedule HCI Connection Complete event.
    if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
      ScheduleTask(kNoDelayMs, [this, status, sco_handle, bd_addr]() {
        send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
          ErrorCode(status), sco_handle, bd_addr, bluetooth::hci::LinkType::SCO,
          bluetooth::hci::Enable::DISABLED));
            ErrorCode(status), sco_handle, bd_addr,
            bluetooth::hci::LinkType::SCO, bluetooth::hci::Enable::DISABLED));
      });
    }

    return ErrorCode::SUCCESS;
  }
@@ -5237,10 +5254,6 @@ ErrorCode LinkLayerController::AcceptConnectionRequest(const Address& bd_addr,

void LinkLayerController::MakePeripheralConnection(const Address& bd_addr,
                                                   bool try_role_switch) {
  INFO(id_, "Sending page response to {}", bd_addr);
  SendLinkLayerPacket(model::packets::PageResponseBuilder::Create(
      GetAddress(), bd_addr, try_role_switch));

  uint16_t connection_handle =
      connections_.CreateConnection(bd_addr, GetAddress());
  if (connection_handle == kReservedHandle) {
@@ -5248,21 +5261,19 @@ void LinkLayerController::MakePeripheralConnection(const Address& bd_addr,
    return;
  }

  ASSERT(link_manager_add_link(
      lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(bd_addr.data())));

  CheckExpiringConnection(connection_handle);

  bluetooth::hci::Role role =
      try_role_switch && connections_.IsRoleSwitchAllowedForPendingConnection()
          ? bluetooth::hci::Role::CENTRAL
          : bluetooth::hci::Role::PERIPHERAL;

  AclConnection& connection = connections_.GetAclConnection(connection_handle);

  CheckExpiringConnection(connection_handle);
  connection.SetLinkPolicySettings(default_link_policy_settings_);
  connection.SetRole(role);

  ASSERT(link_manager_add_link(
      lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(bd_addr.data())));

  // Role change event before connection complete generates an HCI Role Change
  // event on the acceptor side if accepted; the event is sent before the
  // HCI Connection Complete event.
@@ -5273,12 +5284,33 @@ void LinkLayerController::MakePeripheralConnection(const Address& bd_addr,
                                                          bd_addr, role));
  }

  INFO(id_, "CreateConnection returned handle 0x{:x}", connection_handle);
  if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
    send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
        ErrorCode::SUCCESS, connection_handle, bd_addr,
        bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));
  }

  // If the current Host was initiating a connection to the same bd_addr,
  // send a connection complete event for the pending Create Connection
  // command and cancel the paging.
  if (page_.has_value() && page_->bd_addr == bd_addr) {
    // TODO: the core specification is very unclear as to what behavior
    // is expected when two connections are established simultaneously.
    // This implementation considers that an HCI Connection Complete
    // event is expected for both the HCI Create Connection and HCI Accept
    // Connection Request commands. Both events are sent with the status
    // for success.
    if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
      send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
          ErrorCode::SUCCESS, connection_handle, bd_addr,
          bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));
    }
    page_ = {};
  }

  INFO(id_, "Sending page response to {}", bd_addr.ToString());
  SendLinkLayerPacket(model::packets::PageResponseBuilder::Create(
      GetAddress(), bd_addr, try_role_switch));
}

ErrorCode LinkLayerController::RejectConnectionRequest(const Address& addr,
@@ -5308,36 +5340,61 @@ void LinkLayerController::RejectPeripheralConnection(const Address& addr,
  }
}

ErrorCode LinkLayerController::CreateConnection(const Address& addr,
ErrorCode LinkLayerController::CreateConnection(const Address& bd_addr,
                                                uint16_t /* packet_type */,
                                                uint8_t /* page_scan_mode */,
                                                uint16_t /* clock_offset */,
                                                uint8_t allow_role_switch) {
  if (!connections_.CreatePendingConnection(
          addr, authentication_enable_ == AuthenticationEnable::REQUIRED,
          allow_role_switch)) {
    return ErrorCode::CONTROLLER_BUSY;
  // RootCanal only accepts one pending outgoing connection at any time.
  if (page_.has_value()) {
    INFO(id_, "Create Connection command is already pending");
    return ErrorCode::COMMAND_DISALLOWED;
  }

  page_timeout_task_id_ = ScheduleTask(
      duration_cast<milliseconds>(page_timeout_ * microseconds(625)),
      [this, addr] {
        send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
            ErrorCode::PAGE_TIMEOUT, 0xeff, addr, bluetooth::hci::LinkType::ACL,
            bluetooth::hci::Enable::DISABLED));
      });
  // Reject the command if a connection or pending connection already exists
  // for the selected peer address.
  if (connections_.HasPendingConnection(bd_addr) ||
      connections_.GetAclConnectionHandle(bd_addr).has_value()) {
    INFO(id_, "Connection with {} already exists", bd_addr.ToString());
    return ErrorCode::CONNECTION_ALREADY_EXISTS;
  }

  SendLinkLayerPacket(model::packets::PageBuilder::Create(
      GetAddress(), addr, class_of_device_, allow_role_switch));
  auto now = std::chrono::steady_clock::now();
  page_ = Page{
      .bd_addr = bd_addr,
      .allow_role_switch = allow_role_switch,
      .next_page_event = now + kPageInterval,
      .page_timeout = now + slots(page_timeout_),
  };

  return ErrorCode::SUCCESS;
}

ErrorCode LinkLayerController::CreateConnectionCancel(const Address& addr) {
  if (!connections_.CancelPendingConnection(addr)) {
ErrorCode LinkLayerController::CreateConnectionCancel(const Address& bd_addr) {
  // If the HCI_Create_Connection_Cancel command is sent to the Controller
  // without a preceding HCI_Create_Connection command to the same device,
  // the BR/EDR Controller shall return an HCI_Command_Complete event with
  // the error code Unknown Connection Identifier (0x02)
  if (!page_.has_value() || page_->bd_addr != bd_addr) {
    INFO(id_, "no pending connection to {}", bd_addr.ToString());
    return ErrorCode::UNKNOWN_CONNECTION;
  }
  CancelScheduledTask(page_timeout_task_id_);

  // The HCI_Connection_Complete event for the corresponding HCI_Create_-
  // Connection command shall always be sent. The HCI_Connection_Complete
  // event shall be sent after the HCI_Command_Complete event for the
  // HCI_Create_Connection_Cancel command. If the cancellation was successful,
  // the HCI_Connection_Complete event will be generated with the error code
  // Unknown Connection Identifier (0x02).
  if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
    ScheduleTask(kNoDelayMs, [this, bd_addr]() {
      send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
          ErrorCode::UNKNOWN_CONNECTION, 0, bd_addr,
          bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));
    });
  }

  page_ = {};
  return ErrorCode::SUCCESS;
}

@@ -5972,20 +6029,45 @@ void LinkLayerController::Reset() {
  current_iac_lap_list_.clear();
  current_iac_lap_list_.emplace_back(general_iac);

  page_ = {};

  if (inquiry_timer_task_id_ != kInvalidTaskId) {
    CancelScheduledTask(inquiry_timer_task_id_);
    inquiry_timer_task_id_ = kInvalidTaskId;
  }

  if (page_timeout_task_id_ != kInvalidTaskId) {
    CancelScheduledTask(page_timeout_task_id_);
    page_timeout_task_id_ = kInvalidTaskId;
  }

  lm_.reset(link_manager_create(controller_ops_));
  ll_.reset(link_layer_create(controller_ops_));
}

/// Drive the logic for the Page controller substate.
void LinkLayerController::Paging() {
  auto now = std::chrono::steady_clock::now();

  if (page_.has_value() && now >= page_->page_timeout) {
    INFO("page timeout triggered for connection with {}",
         page_->bd_addr.ToString());

    send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
        ErrorCode::PAGE_TIMEOUT, 0, page_->bd_addr,
        bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));

    page_ = {};
    return;
  }

  // Send a Page packet to the peer when a paging interval has passed.
  // Paging is suppressed while a pending connection with the same peer is
  // being established (i.e. two hosts initiated a connection simultaneously).
  if (page_.has_value() && now >= page_->next_page_event &&
      !connections_.HasPendingConnection(page_->bd_addr)) {
    SendLinkLayerPacket(model::packets::PageBuilder::Create(
        GetAddress(), page_->bd_addr, class_of_device_,
        page_->allow_role_switch));
    page_->next_page_event = now + kPageInterval;
  }
}

void LinkLayerController::StartInquiry(milliseconds timeout) {
  inquiry_timer_task_id_ = ScheduleTask(milliseconds(timeout), [this]() {
    LinkLayerController::InquiryTimeout();
Loading