RootCanal: Refactor implementation of Inquiry and InquiryCancel

Test: atest rootcanal_ll_test
Bug: 286588829
Flag: EXEMPT, tool change
Change-Id: I546b90e15be1547044d62ceaf5f5991be912a47f
diff --git a/tools/rootcanal/model/controller/dual_mode_controller.cc b/tools/rootcanal/model/controller/dual_mode_controller.cc
index 20d8ccf..b5913d7 100644
--- a/tools/rootcanal/model/controller/dual_mode_controller.cc
+++ b/tools/rootcanal/model/controller/dual_mode_controller.cc
@@ -1717,24 +1717,19 @@
 void DualModeController::Inquiry(CommandView command) {
   auto command_view = bluetooth::hci::InquiryView::Create(command);
   ASSERT(command_view.IsValid());
-  auto max_responses = command_view.GetNumResponses();
-  auto length = command_view.GetInquiryLength();
+  auto lap = command_view.GetLap().lap_;
+  auto inquiry_length = command_view.GetInquiryLength();
+  auto num_responses = command_view.GetNumResponses();
 
   DEBUG(id_, "<< Inquiry");
-  DEBUG(id_, "   num_responses={}", max_responses);
-  DEBUG(id_, "   inquiry_length={}", length);
+  DEBUG(id_, "   lap={}", lap);
+  DEBUG(id_, "   inquiry_length={}", inquiry_length);
+  DEBUG(id_, "   num_responses={}", num_responses);
 
-  if (max_responses > 0xff || length < 1 || length > 0x30) {
-    send_event_(bluetooth::hci::InquiryStatusBuilder::Create(
-        ErrorCode::INVALID_HCI_COMMAND_PARAMETERS, kNumCommandPackets));
-    return;
-  }
-  link_layer_controller_.SetInquiryLAP(command_view.GetLap().lap_);
-  link_layer_controller_.SetInquiryMaxResponses(max_responses);
-  link_layer_controller_.StartInquiry(std::chrono::milliseconds(length * 1280));
-
-  send_event_(bluetooth::hci::InquiryStatusBuilder::Create(ErrorCode::SUCCESS,
-                                                           kNumCommandPackets));
+  auto status =
+      link_layer_controller_.Inquiry(lap, inquiry_length, num_responses);
+  send_event_(
+      bluetooth::hci::InquiryStatusBuilder::Create(status, kNumCommandPackets));
 }
 
 void DualModeController::InquiryCancel(CommandView command) {
@@ -1743,9 +1738,9 @@
 
   DEBUG(id_, "<< Inquiry Cancel");
 
-  link_layer_controller_.InquiryCancel();
+  auto status = link_layer_controller_.InquiryCancel();
   send_event_(bluetooth::hci::InquiryCancelCompleteBuilder::Create(
-      kNumCommandPackets, ErrorCode::SUCCESS));
+      kNumCommandPackets, status));
 }
 
 void DualModeController::AcceptConnectionRequest(CommandView command) {
diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc
index 845f108..15feb1a 100644
--- a/tools/rootcanal/model/controller/link_layer_controller.cc
+++ b/tools/rootcanal/model/controller/link_layer_controller.cc
@@ -60,6 +60,7 @@
 
 constexpr milliseconds kNoDelayMs(0);
 constexpr milliseconds kPageInterval(1000);
+constexpr milliseconds kInquiryInterval(500);
 
 const Address& LinkLayerController::GetAddress() const { return address_; }
 
@@ -267,6 +268,43 @@
 //  BR/EDR Commands
 // =============================================================================
 
+// HCI Inquiry command (Vol 4, Part E § 7.1.1).
+ErrorCode LinkLayerController::Inquiry(uint8_t lap, uint8_t inquiry_length,
+                                       uint8_t num_responses) {
+  if (inquiry_.has_value()) {
+    INFO(id_, "inquiry is already started");
+    return ErrorCode::COMMAND_DISALLOWED;
+  }
+
+  if (inquiry_length < 0x1 || inquiry_length > 0x30) {
+    INFO(id_, "invalid inquiry length ({})", inquiry_length);
+    return ErrorCode::INVALID_HCI_COMMAND_PARAMETERS;
+  }
+
+  // The Inquiry_Length parameter, added to Extended_Inquiry_Length
+  // (see Section 6.42), specifies the total duration of the Inquiry Mode and,
+  // when this time expires, Inquiry will be halted.
+  std::chrono::microseconds inquiry_timeout =
+      1280ms * inquiry_length +
+      std::chrono::duration_cast<milliseconds>(extended_inquiry_length_);
+
+  auto now = std::chrono::steady_clock::now();
+  inquiry_ = InquiryState{
+      .lap = lap,
+      .num_responses = num_responses,
+      .next_inquiry_event = now + kInquiryInterval,
+      .inquiry_timeout = now + inquiry_timeout,
+  };
+
+  return ErrorCode::SUCCESS;
+}
+
+// HCI Inquiry Cancel command (Vol 4, Part E § 7.1.2).
+ErrorCode LinkLayerController::InquiryCancel() {
+  inquiry_ = {};
+  return ErrorCode::SUCCESS;
+}
+
 // HCI Read Rssi command (Vol 4, Part E § 7.5.4).
 ErrorCode LinkLayerController::ReadRssi(uint16_t connection_handle,
                                         int8_t* rssi) {
@@ -5155,10 +5193,7 @@
 void LinkLayerController::Tick() {
   RunPendingTasks();
   Paging();
-
-  if (inquiry_timer_task_id_ != kInvalidTaskId) {
-    Inquiry();
-  }
+  Inquiring();
   LeAdvertising();
   LeScanning();
   link_manager_tick(lm_.get());
@@ -5386,7 +5421,7 @@
   }
 
   auto now = std::chrono::steady_clock::now();
-  page_ = Page{
+  page_ = PageState{
       .bd_addr = bd_addr,
       .allow_role_switch = allow_role_switch,
       .next_page_event = now + kPageInterval,
@@ -6043,10 +6078,7 @@
   initiator_ = Initiator{};
   synchronizing_ = {};
   synchronized_ = {};
-  last_inquiry_ = steady_clock::now();
   inquiry_mode_ = InquiryType::STANDARD;
-  inquiry_lap_ = 0;
-  inquiry_max_responses_ = 0;
   default_tx_phys_ = properties_.LeSupportedPhys();
   default_rx_phys_ = properties_.LeSupportedPhys();
 
@@ -6056,11 +6088,7 @@
   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;
-  }
+  inquiry_ = {};
 
   lm_.reset(link_manager_create(controller_ops_));
   ll_.reset(link_layer_create(controller_ops_));
@@ -6071,7 +6099,7 @@
   auto now = std::chrono::steady_clock::now();
 
   if (page_.has_value() && now >= page_->page_timeout) {
-    INFO("page timeout triggered for connection with {}",
+    INFO(id_, "page timeout triggered for connection with {}",
          page_->bd_addr.ToString());
 
     send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
@@ -6094,25 +6122,25 @@
   }
 }
 
-void LinkLayerController::StartInquiry(milliseconds timeout) {
-  inquiry_timer_task_id_ = ScheduleTask(milliseconds(timeout), [this]() {
-    LinkLayerController::InquiryTimeout();
-  });
-}
+/// Drive the logic for the Inquiry controller substate.
+void LinkLayerController::Inquiring() {
+  auto now = std::chrono::steady_clock::now();
 
-void LinkLayerController::InquiryCancel() {
-  ASSERT(inquiry_timer_task_id_ != kInvalidTaskId);
-  CancelScheduledTask(inquiry_timer_task_id_);
-  inquiry_timer_task_id_ = kInvalidTaskId;
-}
+  if (inquiry_.has_value() && now >= inquiry_->inquiry_timeout) {
+    INFO(id_, "inquiry timeout triggered");
 
-void LinkLayerController::InquiryTimeout() {
-  if (inquiry_timer_task_id_ != kInvalidTaskId) {
-    inquiry_timer_task_id_ = kInvalidTaskId;
-    if (IsEventUnmasked(EventCode::INQUIRY_COMPLETE)) {
-      send_event_(
-          bluetooth::hci::InquiryCompleteBuilder::Create(ErrorCode::SUCCESS));
-    }
+    send_event_(
+        bluetooth::hci::InquiryCompleteBuilder::Create(ErrorCode::SUCCESS));
+
+    inquiry_ = {};
+    return;
+  }
+
+  // Send a Inquiry packet when the inquiry interval has passed.
+  if (inquiry_.has_value() && now >= inquiry_->next_inquiry_event) {
+    SendLinkLayerPacket(model::packets::InquiryBuilder::Create(
+        GetAddress(), Address::kEmpty, inquiry_mode_, inquiry_->lap));
+    inquiry_->next_inquiry_event = now + kInquiryInterval;
   }
 }
 
@@ -6120,23 +6148,6 @@
   inquiry_mode_ = static_cast<model::packets::InquiryType>(mode);
 }
 
-void LinkLayerController::SetInquiryLAP(uint64_t lap) { inquiry_lap_ = lap; }
-
-void LinkLayerController::SetInquiryMaxResponses(uint8_t max) {
-  inquiry_max_responses_ = max;
-}
-
-void LinkLayerController::Inquiry() {
-  steady_clock::time_point now = steady_clock::now();
-  if (duration_cast<milliseconds>(now - last_inquiry_) < milliseconds(2000)) {
-    return;
-  }
-
-  SendLinkLayerPacket(model::packets::InquiryBuilder::Create(
-      GetAddress(), Address::kEmpty, inquiry_mode_, inquiry_lap_));
-  last_inquiry_ = now;
-}
-
 void LinkLayerController::SetInquiryScanEnable(bool enable) {
   inquiry_scan_enable_ = enable;
 }
diff --git a/tools/rootcanal/model/controller/link_layer_controller.h b/tools/rootcanal/model/controller/link_layer_controller.h
index ce50d91..35a72ef 100644
--- a/tools/rootcanal/model/controller/link_layer_controller.h
+++ b/tools/rootcanal/model/controller/link_layer_controller.h
@@ -185,6 +185,7 @@
   void Reset();
 
   void Paging();
+  void Inquiring();
   void LeAdvertising();
   void LeScanning();
   void LeSynchronization();
@@ -267,13 +268,7 @@
   uint8_t LeReadNumberOfSupportedAdvertisingSets();
 
   // Classic
-  void StartInquiry(std::chrono::milliseconds timeout);
-  void InquiryCancel();
-  void InquiryTimeout();
   void SetInquiryMode(uint8_t mode);
-  void SetInquiryLAP(uint64_t lap);
-  void SetInquiryMaxResponses(uint8_t max);
-  void Inquiry();
 
   bool GetInquiryScanEnable() const { return inquiry_scan_enable_; }
   void SetInquiryScanEnable(bool enable);
@@ -334,6 +329,12 @@
 
   // BR/EDR Commands
 
+  // HCI Inquiry command (Vol 4, Part E § 7.1.1).
+  ErrorCode Inquiry(uint8_t lap, uint8_t inquiry_length, uint8_t num_responses);
+
+  // HCI Inquiry Cancel command (Vol 4, Part E § 7.1.2).
+  ErrorCode InquiryCancel();
+
   // HCI Read Rssi command (Vol 4, Part E § 7.5.4).
   ErrorCode ReadRssi(uint16_t connection_handle, int8_t* rssi);
 
@@ -939,6 +940,9 @@
   // Class of Device (Vol 4, Part E § 6.26).
   uint32_t class_of_device_{0};
 
+  // Extended Inquiry Length (Vol 4, Part E § 6.42).
+  slots extended_inquiry_length_{0};
+
   // Other configuration parameters.
 
   // Current IAC LAP (Vol 4, Part E § 7.3.44).
@@ -1161,23 +1165,27 @@
   struct ControllerOps controller_ops_;
 
   // Classic state.
-  struct Page {
+  struct PageState {
     Address bd_addr;
     uint8_t allow_role_switch;
     std::chrono::steady_clock::time_point next_page_event{};
     std::chrono::steady_clock::time_point page_timeout{};
   };
 
-  // Page substate.
-  // RootCanal will allow only one page request running at the same time.
-  std::optional<Page> page_;
+  struct InquiryState {
+    uint64_t lap;
+    uint8_t num_responses;
+    std::chrono::steady_clock::time_point next_inquiry_event{};
+    std::chrono::steady_clock::time_point inquiry_timeout{};
+  };
 
-  std::chrono::steady_clock::time_point last_inquiry_;
+  // Page and inquiry substates.
+  // RootCanal will allow only one page request running at the same time.
+  std::optional<PageState> page_;
+  std::optional<InquiryState> inquiry_;
+
   model::packets::InquiryType inquiry_mode_{
       model::packets::InquiryType::STANDARD};
-  TaskId inquiry_timer_task_id_ = kInvalidTaskId;
-  uint64_t inquiry_lap_{};
-  uint8_t inquiry_max_responses_{};
 
  public:
   // Type of scheduled tasks.