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

Commit de19cd5c authored by Charlie Boutier's avatar Charlie Boutier Committed by Gerrit Code Review
Browse files

Merge "RootCanal: Properly handle concurrent connection attempts" into main

parents 701b96cd a5388137
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