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

Commit caec024c authored by Rahul Arya's avatar Rahul Arya
Browse files

Add Remote Name Request scheduling to ACL scheduler

Remote name requests need to be queued along with ACL Create Connection
requests to avoid the commands being rejected by the controller. This
adds the relevant hooks into ACL scheduler to do so, analogous to those
that exist for connection creation.

Test: gd unit tests
Bug: 246640776
Tag: #refactor
BYPASS_LONG_LINES_REASON: Bluetooth

Change-Id: Ia6ce5d598eddb771110df44375f9f8256e411132
parent 798ed5e0
Loading
Loading
Loading
Loading
+137 −33
Original line number Original line Diff line number Diff line
@@ -16,8 +16,10 @@


#include "acl_scheduler.h"
#include "acl_scheduler.h"


#include <optional>
#include <queue>
#include <queue>
#include <unordered_set>
#include <unordered_set>
#include <variant>


namespace bluetooth {
namespace bluetooth {
namespace hci {
namespace hci {
@@ -29,10 +31,18 @@ struct AclCreateConnectionQueueEntry {
  common::ContextualOnceCallback<void()> callback;
  common::ContextualOnceCallback<void()> callback;
};
};


struct RemoteNameRequestQueueEntry {
  Address address;
  common::ContextualOnceCallback<void()> callback;
  common::ContextualOnceCallback<void()> callback_when_cancelled;
};

using QueueEntry = std::variant<AclCreateConnectionQueueEntry, RemoteNameRequestQueueEntry>;

struct AclScheduler::impl {
struct AclScheduler::impl {
  void EnqueueOutgoingAclConnection(Address address, common::ContextualOnceCallback<void()> start_connection) {
  void EnqueueOutgoingAclConnection(Address address, common::ContextualOnceCallback<void()> start_connection) {
    pending_outgoing_connections_.push_back({address, std::move(start_connection)});
    pending_outgoing_operations_.push_back(AclCreateConnectionQueueEntry{address, std::move(start_connection)});
    try_dequeue_next_connection();
    try_dequeue_next_operation();
  }
  }


  void RegisterPendingIncomingConnection(Address address) {
  void RegisterPendingIncomingConnection(Address address) {
@@ -44,68 +54,141 @@ struct AclScheduler::impl {
      common::ContextualOnceCallback<void()> handle_outgoing_connection,
      common::ContextualOnceCallback<void()> handle_outgoing_connection,
      common::ContextualOnceCallback<void()> handle_incoming_connection,
      common::ContextualOnceCallback<void()> handle_incoming_connection,
      common::ContextualOnceCallback<void(std::string)> handle_unknown_connection) {
      common::ContextualOnceCallback<void(std::string)> handle_unknown_connection) {
    if (outgoing_connecting_address_ == address) {
    // Check if an outgoing request (a) exists, (b) is a Create Connection, (c) matches the received address
      outgoing_connecting_address_ = Address::kEmpty;
    if (outgoing_entry_.has_value()) {
      auto entry = std::get_if<AclCreateConnectionQueueEntry>(&outgoing_entry_.value());
      if (entry != nullptr && entry->address == address) {
        // If so, clear the current entry and advance the queue
        outgoing_entry_.reset();
        handle_outgoing_connection.InvokeIfNotEmpty();
        handle_outgoing_connection.InvokeIfNotEmpty();
    } else if (incoming_connecting_address_set_.find(address) != incoming_connecting_address_set_.end()) {
        try_dequeue_next_operation();
        return;
      }
    }

    // Otherwise check if it's an incoming request and advance the queue if so
    if (incoming_connecting_address_set_.find(address) != incoming_connecting_address_set_.end()) {
      incoming_connecting_address_set_.erase(address);
      incoming_connecting_address_set_.erase(address);
      handle_incoming_connection.InvokeIfNotEmpty();
      handle_incoming_connection.InvokeIfNotEmpty();
    } else {
    } else {
      handle_unknown_connection.InvokeIfNotEmpty(set_of_incoming_connecting_addresses());
      handle_unknown_connection.InvokeIfNotEmpty(set_of_incoming_connecting_addresses());
    }
    }
    try_dequeue_next_connection();
    try_dequeue_next_operation();
  }
  }


  void ReportOutgoingAclConnectionFailure() {
  void ReportOutgoingAclConnectionFailure() {
    if (outgoing_connecting_address_ == Address::kEmpty) {
    if (!outgoing_entry_.has_value()) {
      LOG_ERROR("Outgoing connection failure reported, but none present!");
      LOG_ERROR("Outgoing connection failure reported, but none present!");
      return;
      return;
    }
    }
    outgoing_connecting_address_ = Address::kEmpty;
    auto entry = std::get_if<AclCreateConnectionQueueEntry>(&outgoing_entry_.value());
    try_dequeue_next_connection();
    if (entry == nullptr) {
      LOG_ERROR("Outgoing connection failure reported, but we're currently doing an RNR!");
      return;
    }
    outgoing_entry_.reset();
    try_dequeue_next_operation();
  }
  }


  void CancelAclConnection(
  void CancelAclConnection(
      Address address,
      Address address,
      common::ContextualOnceCallback<void()> cancel_connection,
      common::ContextualOnceCallback<void()> cancel_connection,
      common::ContextualOnceCallback<void()> cancel_connection_completed) {
      common::ContextualOnceCallback<void()> cancel_connection_completed) {
    // Check if relevant connection is currently outgoing
    auto ok = cancel_outgoing_or_queued_connection(
    if (outgoing_connecting_address_ == address) {
        [&](auto& entry) {
      cancel_connection.Invoke();
          auto entry_ptr = std::get_if<AclCreateConnectionQueueEntry>(&entry);
      // as per method contract, we *don't* clear it from the queue
          return entry_ptr != nullptr && entry_ptr->address == address;
      return;
        },
        [&]() { cancel_connection.Invoke(); },
        [&](auto entry) { cancel_connection_completed.Invoke(); });
    if (!ok) {
      LOG_ERROR("Attempted to cancel connection to %s that does not exist", address.ToString().c_str());
    }
  }
  }


    // Otherwise, clear from the queue
  void EnqueueRemoteNameRequest(
    auto it =
      Address address,
        std::find_if(pending_outgoing_connections_.begin(), pending_outgoing_connections_.end(), [&](auto& entry) {
      common::ContextualOnceCallback<void()> start_request,
          return entry.address == address;
      common::ContextualOnceCallback<void()> cancel_request_completed) {
        });
    pending_outgoing_operations_.push_back(
    if (it == pending_outgoing_connections_.end()) {
        RemoteNameRequestQueueEntry{address, std::move(start_request), std::move(cancel_request_completed)});
      LOG_ERROR("Attempted to cancel connection to %s that does not exist", address.ToString().c_str());
    try_dequeue_next_operation();
  }

  void ReportRemoteNameRequestCompletion(Address address) {
    if (!outgoing_entry_.has_value()) {
      LOG_ERROR("Remote name request completion reported, but none taking place!");
      return;
      return;
    }
    }
    pending_outgoing_connections_.erase(it);

    cancel_connection_completed.Invoke();
    std::visit(
        [](auto&& entry) {
          using T = std::decay_t<decltype(entry)>;
          if constexpr (std::is_same_v<T, RemoteNameRequestQueueEntry>) {
            LOG_INFO("Remote name request completed");
          } else if constexpr (std::is_same_v<T, AclCreateConnectionQueueEntry>) {
            LOG_ERROR(
                "Received RNR completion when ACL connection is outstanding - assuming the connection has failed and "
                "continuing");
          } else {
            static_assert(!sizeof(T*), "non-exhaustive visitor!");
          }
          }
        },
        outgoing_entry_.value());

    outgoing_entry_.reset();
    try_dequeue_next_operation();
  }

  void CancelRemoteNameRequest(Address address, common::ContextualOnceCallback<void()> cancel_request) {
    auto ok = cancel_outgoing_or_queued_connection(
        [&](auto& entry) {
          auto entry_ptr = std::get_if<RemoteNameRequestQueueEntry>(&entry);
          return entry_ptr != nullptr && entry_ptr->address == address;
        },
        [&]() { cancel_request.Invoke(); },
        [](auto entry) { std::get<RemoteNameRequestQueueEntry>(entry).callback_when_cancelled.Invoke(); });
    if (!ok) {
      LOG_ERROR("Attempted to cancel remote name request to %s that does not exist", address.ToString().c_str());
    }
  };


  void Stop() {
  void Stop() {
    stopped_ = true;
    stopped_ = true;
  }
  }


 private:
 private:
  void try_dequeue_next_connection() {
  void try_dequeue_next_operation() {
    if (stopped_) {
    if (stopped_) {
      return;
      return;
    }
    }
    if (incoming_connecting_address_set_.empty() && outgoing_connecting_address_.IsEmpty() &&
    if (incoming_connecting_address_set_.empty() && !outgoing_entry_.has_value() &&
        !pending_outgoing_connections_.empty()) {
        !pending_outgoing_operations_.empty()) {
      LOG_INFO("Pending connections is not empty; so sending next connection");
      LOG_INFO("Pending connections is not empty; so sending next connection");
      auto entry = std::move(pending_outgoing_connections_.front());
      auto entry = std::move(pending_outgoing_operations_.front());
      pending_outgoing_connections_.pop_front();
      pending_outgoing_operations_.pop_front();
      outgoing_connecting_address_ = entry.address;
      std::visit([](auto&& variant) { variant.callback.Invoke(); }, entry);
      entry.callback.Invoke();
      outgoing_entry_ = std::move(entry);
    }
  }

  template <typename T, typename U, typename V>
  bool cancel_outgoing_or_queued_connection(T matcher, U cancel_outgoing, V cancelled_queued) {
    // Check if relevant connection is currently outgoing
    if (outgoing_entry_.has_value()) {
      if (matcher(outgoing_entry_.value())) {
        cancel_outgoing();
        return true;
      }
    }
    // Otherwise, clear from the queue
    auto it = std::find_if(pending_outgoing_operations_.begin(), pending_outgoing_operations_.end(), matcher);
    if (it == pending_outgoing_operations_.end()) {
      return false;
    }
    }
    cancelled_queued(std::move(*it));
    pending_outgoing_operations_.erase(it);
    return true;
  }
  }


  const std::string set_of_incoming_connecting_addresses() const {
  const std::string set_of_incoming_connecting_addresses() const {
@@ -114,8 +197,8 @@ struct AclScheduler::impl {
    return buffer.str();
    return buffer.str();
  }
  }


  Address outgoing_connecting_address_;
  std::optional<QueueEntry> outgoing_entry_;
  std::deque<AclCreateConnectionQueueEntry> pending_outgoing_connections_;
  std::deque<QueueEntry> pending_outgoing_operations_;
  std::unordered_set<Address> incoming_connecting_address_set_;
  std::unordered_set<Address> incoming_connecting_address_set_;
  bool stopped_ = false;
  bool stopped_ = false;
};
};
@@ -165,6 +248,27 @@ void AclScheduler::CancelAclConnection(
      std::move(cancel_connection_completed));
      std::move(cancel_connection_completed));
}
}


void AclScheduler::EnqueueRemoteNameRequest(
    Address address,
    common::ContextualOnceCallback<void()> start_request,
    common::ContextualOnceCallback<void()> cancel_request_completed) {
  GetHandler()->Call(
      &impl::EnqueueRemoteNameRequest,
      common::Unretained(pimpl_.get()),
      address,
      std::move(start_request),
      std::move(cancel_request_completed));
}

void AclScheduler::ReportRemoteNameRequestCompletion(Address address) {
  GetHandler()->Call(&impl::ReportRemoteNameRequestCompletion, common::Unretained(pimpl_.get()), address);
}

void AclScheduler::CancelRemoteNameRequest(Address address, common::ContextualOnceCallback<void()> cancel_request) {
  GetHandler()->Call(
      &impl::CancelRemoteNameRequest, common::Unretained(pimpl_.get()), address, std::move(cancel_request));
}

void AclScheduler::ListDependencies(ModuleList* list) const {}
void AclScheduler::ListDependencies(ModuleList* list) const {}


void AclScheduler::Start() {}
void AclScheduler::Start() {}
+15 −0
Original line number Original line Diff line number Diff line
@@ -63,6 +63,21 @@ class AclScheduler : public bluetooth::Module {
      common::ContextualOnceCallback<void()> cancel_connection,
      common::ContextualOnceCallback<void()> cancel_connection,
      common::ContextualOnceCallback<void()> cancel_connection_completed);
      common::ContextualOnceCallback<void()> cancel_connection_completed);


  // Schedule a Remote Name Request. When the request is started, start_request will be invoked. If the request is
  // cancelled before it is dequeued, cancel_request_completed will be invoked.
  void EnqueueRemoteNameRequest(
      Address address,
      common::ContextualOnceCallback<void()> start_request,
      common::ContextualOnceCallback<void()> cancel_request_completed);

  // Report that a Remote Name Request connection has completed, so we can resume popping from the queue.
  void ReportRemoteNameRequestCompletion(Address address);

  // Cancel an Remote Name Request. If the request is already outgoing, we will invoke cancel_request, without
  // clearing the outgoing request. Otherwise, we will invoke the cancel_request_completed callback registered on
  // the initial enqueue.
  void CancelRemoteNameRequest(Address address, common::ContextualOnceCallback<void()> cancel_request);

 private:
 private:
  struct impl;
  struct impl;
  std::unique_ptr<impl> pimpl_;
  std::unique_ptr<impl> pimpl_;
+115 −0
Original line number Original line Diff line number Diff line
@@ -359,6 +359,121 @@ TEST_F(AclSchedulerTest, CancelQueuedConnectionCallback) {
  EXPECT_THAT(future, IsSet());
  EXPECT_THAT(future, IsSet());
}
}


TEST_F(AclSchedulerTest, RemoteNameRequestImmediatelyExecuted) {
  auto promise = std::promise<void>{};
  auto future = promise.get_future();

  // start an outgoing request
  acl_scheduler_->EnqueueRemoteNameRequest(address1, promiseCallback(std::move(promise)), emptyCallback());

  // we expect the start callback to be invoked immediately
  EXPECT_THAT(future, IsSet());
}

TEST_F(AclSchedulerTest, RemoteNameRequestQueuing) {
  auto promise = std::promise<void>{};
  auto future = promise.get_future();

  // start an outgoing request
  acl_scheduler_->EnqueueRemoteNameRequest(address1, emptyCallback(), impossibleCallback());
  // enqueue a second one
  acl_scheduler_->EnqueueRemoteNameRequest(address2, promiseCallback(std::move(promise)), impossibleCallback());

  // we should still be queued
  EXPECT_THAT(future.wait_for(timeout), std::future_status::timeout);

  // the first request completes
  acl_scheduler_->ReportRemoteNameRequestCompletion(address1);

  // so the second request should now have started
  EXPECT_THAT(future, IsSet());
}

TEST_F(AclSchedulerTest, RemoteNameRequestCancellationCallback) {
  auto promise = std::promise<void>{};
  auto future = promise.get_future();

  // start an outgoing request
  acl_scheduler_->EnqueueRemoteNameRequest(address1, emptyCallback(), impossibleCallback());

  // cancel it
  acl_scheduler_->CancelRemoteNameRequest(address1, promiseCallback(std::move(promise)));

  // the cancel callback should be invoked
  EXPECT_THAT(future, IsSet());
}

TEST_F(AclSchedulerTest, RemoteNameRequestCancellationWhileQueuedCallback) {
  auto promise = std::promise<void>{};
  auto future = promise.get_future();

  // start an outgoing request
  acl_scheduler_->EnqueueRemoteNameRequest(address1, emptyCallback(), impossibleCallback());
  // enqueue a second one
  acl_scheduler_->EnqueueRemoteNameRequest(address2, impossibleCallback(), promiseCallback(std::move(promise)));

  // cancel the second one
  acl_scheduler_->CancelRemoteNameRequest(address2, impossibleCallback());

  // the cancel_request_completed calback should be invoked
  EXPECT_THAT(future, IsSet());

  // the first request completes
  acl_scheduler_->ReportRemoteNameRequestCompletion(address1);

  // we don't dequeue the second one, since it was cancelled
  // implicitly assert that its callback was never invoked
}

TEST_F(AclSchedulerTest, CancelQueuedRemoteNameRequestRemoveFromQueue) {
  auto promise = std::promise<void>{};
  auto future = promise.get_future();

  // start an outgoing connection
  acl_scheduler_->EnqueueOutgoingAclConnection(address1, emptyCallback());
  // start another connection that will queue
  acl_scheduler_->EnqueueRemoteNameRequest(address2, impossibleCallback(), emptyCallback());
  // start a third connection that will queue
  acl_scheduler_->EnqueueRemoteNameRequest(address3, promiseCallback(std::move(promise)), impossibleCallback());

  // cancel the first queued connection
  acl_scheduler_->CancelRemoteNameRequest(address2, impossibleCallback());

  // the second queued connection should remain enqueued, since another connection is in progress
  EXPECT_THAT(future.wait_for(timeout), std::future_status::timeout);

  // complete the outgoing connection
  acl_scheduler_->ReportOutgoingAclConnectionFailure();

  // only now can we dequeue the second queued connection
  EXPECT_THAT(future, IsSet());
}

TEST_F(AclSchedulerTest, RemoteNameRequestCancellationShouldDequeueNext) {
  auto promise = std::promise<void>{};
  auto future = promise.get_future();

  // start an outgoing request
  acl_scheduler_->EnqueueRemoteNameRequest(address1, emptyCallback(), impossibleCallback());
  // enqueue a second one
  acl_scheduler_->EnqueueRemoteNameRequest(address2, promiseCallback(std::move(promise)), impossibleCallback());

  // we should still be queued
  EXPECT_THAT(future.wait_for(timeout), std::future_status::timeout);

  // the first request is cancelled
  acl_scheduler_->CancelRemoteNameRequest(address1, emptyCallback());

  // we should still remain queued while we wait for the cancel to complete
  EXPECT_THAT(future.wait_for(timeout), std::future_status::timeout);

  // the cancel completes
  acl_scheduler_->ReportRemoteNameRequestCompletion(address1);

  // so the second request should now have started
  EXPECT_THAT(future, IsSet());
}

}  // namespace
}  // namespace
}  // namespace acl_manager
}  // namespace acl_manager
}  // namespace hci
}  // namespace hci
+1 −0
Original line number Original line Diff line number Diff line
@@ -177,6 +177,7 @@ init_flags!(
        gd_core,
        gd_core,
        gd_l2cap,
        gd_l2cap,
        gd_link_policy,
        gd_link_policy,
        gd_remote_name_request,
        gd_rust,
        gd_rust,
        gd_security,
        gd_security,
        hci_adapter: i32,
        hci_adapter: i32,
+1 −0
Original line number Original line Diff line number Diff line
@@ -10,6 +10,7 @@ mod ffi {
        fn gd_core_is_enabled() -> bool;
        fn gd_core_is_enabled() -> bool;
        fn gd_l2cap_is_enabled() -> bool;
        fn gd_l2cap_is_enabled() -> bool;
        fn gd_link_policy_is_enabled() -> bool;
        fn gd_link_policy_is_enabled() -> bool;
        fn gd_remote_name_request_is_enabled() -> bool;
        fn gd_rust_is_enabled() -> bool;
        fn gd_rust_is_enabled() -> bool;
        fn gd_security_is_enabled() -> bool;
        fn gd_security_is_enabled() -> bool;
        fn get_hci_adapter() -> i32;
        fn get_hci_adapter() -> i32;