Refactor of headless test to allow additional tests

Bug: 147316415

Test: bt_headless --device=40:4e:36:56:f3:9b --loop=1 pairing
Test: bt_headless --device=40:4e:36:56:f3:9b --loop=1 --uuid=0x110e sdp

Change-Id: I202c7af885d86fd77fffeec5a87b56ffe18882da
diff --git a/system/test/headless/Android.bp b/system/test/headless/Android.bp
index bc91652..4e70366 100644
--- a/system/test/headless/Android.bp
+++ b/system/test/headless/Android.bp
@@ -5,7 +5,13 @@
     srcs: [
         "get_options.cc",
         "headless.cc",
-        "sdp/main.cc",
+        "main.cc",
+        "pairing/pairing.cc",
+        "sdp/sdp.cc",
+        "sdp/sdp_db.cc",
+        "nop/nop.cc",
+        "read/read.cc",
+        "read/name.cc",
     ],
     include_dirs: [
         "packages/modules/Bluetooth/system",
diff --git a/system/test/headless/get_options.cc b/system/test/headless/get_options.cc
index c28e30e..f8d8576 100644
--- a/system/test/headless/get_options.cc
+++ b/system/test/headless/get_options.cc
@@ -24,19 +24,34 @@
 #include <string>
 
 namespace {
-constexpr struct option long_options[] = {{"device", required_argument, 0, 0},
-                                          {"loop", required_argument, 0, 0},
-                                          {"uuid", required_argument, 0, 0},
-                                          {0, 0, 0, 0}};
+constexpr struct option long_options[] = {
+    {"device", required_argument, 0, 0}, {"loop", required_argument, 0, 0},
+    {"uuid", required_argument, 0, 0},   {"msleep", required_argument, 0, 0},
+    {"stderr", no_argument, 0, 0},       {0, 0, 0, 0}};
 
 enum OptionType {
   kOptionDevice = 0,
   kOptionLoop = 1,
   kOptionUuid = 2,
+  kOptionMsleep = 3,
+  kOptionStdErr = 4,
 };
 
 }  // namespace
 
+void bluetooth::test::headless::GetOpt::Usage() const {
+  fprintf(stdout, "%s: Usage:\n", name_);
+  fprintf(stdout,
+          "%s  --device=<device,>  Comma separated list of remote devices\n",
+          name_);
+  fprintf(stdout, "%s  --uuid=<uuid,>      Comma separated list of uuids\n",
+          name_);
+  fprintf(stdout, "%s  --loop=<loop>       Number of loops\n", name_);
+  fprintf(stdout, "%s  --msleep=<msecs>    Sleep msec between loops\n", name_);
+  fprintf(stdout, "%s  --stderr            Dump stderr to stdout\n", name_);
+  fflush(nullptr);
+}
+
 void bluetooth::test::headless::GetOpt::ParseValue(
     char* optarg, std::list<std::string>& string_list) {
   CHECK(optarg != nullptr);
@@ -58,9 +73,9 @@
   std::list<std::string> string_list;
   OptionType option_type = static_cast<OptionType>(option_index);
 
-  if (!optarg) return;
   switch (option_type) {
     case kOptionDevice:
+      if (!optarg) return;
       ParseValue(optarg, string_list);
       for (auto& entry : string_list) {
         if (RawAddress::IsValidAddress(entry)) {
@@ -74,12 +89,20 @@
       loop_ = std::stoul(optarg, nullptr, 0);
       break;
     case kOptionUuid:
+      if (!optarg) return;
       ParseValue(optarg, string_list);
       for (auto& entry : string_list) {
         uuid_.push_back(
             bluetooth::Uuid::From16Bit(std::stoul(entry.c_str(), nullptr, 0)));
       }
       break;
+    case kOptionMsleep:
+      if (!optarg) return;
+      msec_ = std::stoul(optarg, nullptr, 0);
+      break;
+    case kOptionStdErr:
+      close_stderr_ = false;
+      break;
     default:
       fflush(nullptr);
       valid_ = false;
@@ -108,20 +131,9 @@
     }
   }
 
-  if (optind < argc) {
-    printf("non-option ARGV-elements: ");
-    while (optind < argc) printf("%s ", argv[optind++]);
-    printf("\n");
-    valid_ = false;
+  while (optind < argc) {
+    non_options_.push_back(argv[optind++]);
   }
   fflush(nullptr);
 }
 
-void bluetooth::test::headless::GetOpt::Usage() const {
-  printf("%s: Usage:\n", name_);
-  printf("%s  --device=<device,>  Comma separated list of remote devices\n",
-         name_);
-  printf("%s  --uuid=<uuid,>      Comma separated list of uuids\n", name_);
-  printf("%s  --loop=<loop>       Number of loops\n", name_);
-  fflush(nullptr);
-}
diff --git a/system/test/headless/get_options.h b/system/test/headless/get_options.h
index 65ab920..04a5025 100644
--- a/system/test/headless/get_options.h
+++ b/system/test/headless/get_options.h
@@ -33,9 +33,20 @@
   virtual void Usage() const;
   virtual bool IsValid() const { return valid_; };
 
+  std::string GetNextSubTest() const {
+    std::string test = non_options_.front();
+    non_options_.pop_front();
+    return test;
+  }
+
   std::list<RawAddress> device_;
   std::list<bluetooth::Uuid> uuid_;
-  int loop_;
+  unsigned long loop_{1};
+  unsigned long msec_{0};
+
+  bool close_stderr_{true};
+
+  mutable std::list<std::string> non_options_;
 
  private:
   void ParseValue(char* optarg, std::list<std::string>& my_list);
diff --git a/system/test/headless/headless.cc b/system/test/headless/headless.cc
index 2cf6334..384159d 100644
--- a/system/test/headless/headless.cc
+++ b/system/test/headless/headless.cc
@@ -21,6 +21,7 @@
 #include "base/logging.h"  // LOG() stdout and android log
 #include "include/hardware/bluetooth.h"
 #include "osi/include/log.h"  // android log only
+#include "test/headless/get_options.h"
 #include "test/headless/headless.h"
 
 extern bt_interface_t bluetoothInterface;
@@ -137,7 +138,7 @@
 };
 }  // namespace
 
-void Headless::SetUp() {
+void HeadlessStack::SetUp() {
   LOG(INFO) << __func__ << " Entry";
 
   int status = bluetoothInterface.init(&bt_callbacks, false, false);
@@ -151,14 +152,14 @@
       : LOG(ERROR) << "Failed to set up Bluetooth OS callouts";
 
   bluetoothInterface.enable();
-  LOG_INFO(LOG_TAG, "%s Headless stack has enabled", __func__);
+  LOG_INFO(LOG_TAG, "%s HeadlessStack stack has enabled", __func__);
 
   std::unique_lock<std::mutex> lck(adapter_state_mutex_);
   while (bt_state_ != BT_STATE_ON) adapter_state_cv_.wait(lck);
-  LOG_INFO(LOG_TAG, "%s Headless stack is operational", __func__);
+  LOG_INFO(LOG_TAG, "%s HeadlessStack stack is operational", __func__);
 }
 
-void Headless::TearDown() {
+void HeadlessStack::TearDown() {
   LOG_INFO(LOG_TAG, "Stack has disabled");
   int status = bluetoothInterface.disable();
 
@@ -169,5 +170,5 @@
 
   std::unique_lock<std::mutex> lck(adapter_state_mutex_);
   while (bt_state_ != BT_STATE_OFF) adapter_state_cv_.wait(lck);
-  LOG_INFO(LOG_TAG, "%s Headless stack has exited", __func__);
+  LOG_INFO(LOG_TAG, "%s HeadlessStack stack has exited", __func__);
 }
diff --git a/system/test/headless/headless.h b/system/test/headless/headless.h
index c51d640..551f60f 100644
--- a/system/test/headless/headless.h
+++ b/system/test/headless/headless.h
@@ -14,32 +14,104 @@
  * limitations under the License.
  */
 
+#pragma once
+
+#include <unordered_map>
+
+#include <unistd.h>
+
+#include "base/logging.h"  // LOG() stdout and android log
+#include "test/headless/get_options.h"
+
 namespace bluetooth {
 namespace test {
 namespace headless {
 
+namespace {
+
 template <typename T>
 using ExecutionUnit = std::function<T()>;
 
-class Headless {
- public:
-  Headless() = default;
-  virtual ~Headless() = default;
+constexpr char kHeadlessStartSentinel[] =
+    " START HEADLESS HEADLESS HEADLESS HEADLESS HEADLESS HEADLESS HEADLESS "
+    "HEADLESS";
+constexpr char kHeadlessStopSentinel[] =
+    " STOP HEADLESS HEADLESS HEADLESS HEADLESS HEADLESS HEADLESS HEADLESS "
+    "HEADLESS";
 
+}  // namespace
+
+class HeadlessStack {
  protected:
-  virtual void SetUp();
-  virtual void TearDown();
+  HeadlessStack() = default;
+  virtual ~HeadlessStack() = default;
+
+  void SetUp();
+  void TearDown();
 };
 
-class Test : public Headless {
- public:
+class HeadlessRun : public HeadlessStack {
+ protected:
+  const bluetooth::test::headless::GetOpt& options_;
+  unsigned long loop_{0};
+
+  HeadlessRun(const bluetooth::test::headless::GetOpt& options)
+      : options_(options) {}
+
   template <typename T>
-  T Run(ExecutionUnit<T> func) {
+  T RunOnHeadlessStack(ExecutionUnit<T> func) {
     SetUp();
-    T rc = func();
+    LOG(INFO) << kHeadlessStartSentinel;
+
+    T rc;
+    for (loop_ = 0; loop_ < options_.loop_; loop_++) {
+      rc = func();
+      if (options_.msec_ != 0) {
+        usleep(options_.msec_ * 1000);
+      }
+      if (rc) {
+        break;
+      }
+    }
+    if (rc) {
+      LOG(ERROR) << "FAIL:" << rc << " loop/loops:" << loop_ << "/"
+                 << options_.loop_;
+    } else {
+      LOG(INFO) << "PASS:" << rc << " loop/loops:" << loop_ << "/"
+                << options_.loop_;
+    }
+
+    LOG(INFO) << kHeadlessStopSentinel;
     TearDown();
     return rc;
   }
+  virtual ~HeadlessRun() = default;
+};
+
+template <typename T>
+class HeadlessTest : public HeadlessRun {
+ public:
+  virtual T Run() {
+    if (options_.non_options_.size() == 0) {
+      fprintf(stdout, "Must supply at least one subtest name\n");
+      return -1;
+    }
+
+    std::string subtest = options_.GetNextSubTest();
+    if (test_nodes_.find(subtest) == test_nodes_.end()) {
+      fprintf(stdout, "Unknown subtest module:%s\n", subtest.c_str());
+      return -1;
+    }
+    return test_nodes_.at(subtest)->Run();
+  }
+
+  virtual ~HeadlessTest() = default;
+
+ protected:
+  HeadlessTest(const bluetooth::test::headless::GetOpt& options)
+      : HeadlessRun(options) {}
+
+  std::unordered_map<std::string, std::unique_ptr<HeadlessTest<T>>> test_nodes_;
 };
 
 }  // namespace headless
diff --git a/system/test/headless/main.cc b/system/test/headless/main.cc
new file mode 100644
index 0000000..487e96c
--- /dev/null
+++ b/system/test/headless/main.cc
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless"
+
+#include <unordered_map>
+
+#include "base/logging.h"     // LOG() stdout and android log
+#include "osi/include/log.h"  // android log only
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+#include "test/headless/nop/nop.h"
+#include "test/headless/pairing/pairing.h"
+#include "test/headless/read/read.h"
+#include "test/headless/sdp/sdp.h"
+
+using namespace bluetooth::test::headless;
+
+namespace {
+
+class Main : public HeadlessTest<int> {
+ public:
+  Main(const bluetooth::test::headless::GetOpt& options)
+      : HeadlessTest<int>(options) {
+    test_nodes_.emplace(
+        "nop", std::make_unique<bluetooth::test::headless::Nop>(options));
+    test_nodes_.emplace(
+        "pairing",
+        std::make_unique<bluetooth::test::headless::Pairing>(options));
+    test_nodes_.emplace(
+        "read", std::make_unique<bluetooth::test::headless::Read>(options));
+    test_nodes_.emplace(
+        "sdp", std::make_unique<bluetooth::test::headless::Sdp>(options));
+  }
+
+  int Run() override {
+    if (options_.close_stderr_) {
+      fclose(stderr);
+    }
+    return HeadlessTest<int>::Run();
+  }
+};
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  fflush(nullptr);
+  setvbuf(stdout, nullptr, _IOLBF, 0);
+
+  bluetooth::test::headless::GetOpt options(argc, argv);
+  if (!options.IsValid()) {
+    return -1;
+  }
+
+  Main main(options);
+  return main.Run();
+}
diff --git a/system/test/headless/nop/nop.cc b/system/test/headless/nop/nop.cc
new file mode 100644
index 0000000..f6ac42c
--- /dev/null
+++ b/system/test/headless/nop/nop.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless_sdp"
+
+#include <future>
+
+#include "base/logging.h"     // LOG() stdout and android log
+#include "osi/include/log.h"  // android log only
+#include "stack/include/sdp_api.h"
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+#include "test/headless/nop/nop.h"
+#include "types/raw_address.h"
+
+int bluetooth::test::headless::Nop::Run() {
+  return RunOnHeadlessStack<int>([this]() {
+    fprintf(stdout, "Nop loop:%lu\n", loop_);
+    return 0;
+  });
+}
diff --git a/system/test/headless/nop/nop.h b/system/test/headless/nop/nop.h
new file mode 100644
index 0000000..e65efa3
--- /dev/null
+++ b/system/test/headless/nop/nop.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+
+namespace bluetooth {
+namespace test {
+namespace headless {
+
+class Nop : public HeadlessTest<int> {
+ public:
+  Nop(const bluetooth::test::headless::GetOpt& options)
+      : HeadlessTest<int>(options) {}
+  int Run() override;
+};
+
+}  // namespace headless
+}  // namespace test
+}  // namespace bluetooth
diff --git a/system/test/headless/pairing/pairing.cc b/system/test/headless/pairing/pairing.cc
new file mode 100644
index 0000000..7785ea4
--- /dev/null
+++ b/system/test/headless/pairing/pairing.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless_sdp"
+
+#include <future>
+
+#include "base/logging.h"  // LOG() stdout and android log
+#include "btif/include/btif_api.h"
+#include "osi/include/log.h"  // android log only
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+#include "test/headless/pairing/pairing.h"
+#include "types/raw_address.h"
+
+int bluetooth::test::headless::Pairing::Run() {
+  if (options_.loop_ < 1) {
+    fprintf(stdout, "This test requires at least a single loop");
+    options_.Usage();
+    return -1;
+  }
+  if (options_.device_.size() != 1) {
+    fprintf(stdout, "This test requires a single device specified");
+    options_.Usage();
+    return -1;
+  }
+
+  RawAddress raw_address = options_.device_.front();
+
+  return RunOnHeadlessStack<int>([raw_address]() {
+    bt_status_t status = btif_dm_create_bond(&raw_address, BT_TRANSPORT_BR_EDR);
+    switch (status) {
+      case BT_STATUS_SUCCESS:
+        break;
+      default:
+        fprintf(stdout, "Failed to create bond status:%d", status);
+        break;
+    }
+    return status;
+  });
+}
diff --git a/system/test/headless/pairing/pairing.h b/system/test/headless/pairing/pairing.h
new file mode 100644
index 0000000..4125d48
--- /dev/null
+++ b/system/test/headless/pairing/pairing.h
@@ -0,0 +1,20 @@
+
+#pragma once
+
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+
+namespace bluetooth {
+namespace test {
+namespace headless {
+
+class Pairing : public HeadlessTest<int> {
+ public:
+  Pairing(const bluetooth::test::headless::GetOpt& options)
+      : HeadlessTest<int>(options) {}
+  int Run() override;
+};
+
+}  // namespace headless
+}  // namespace test
+}  // namespace bluetooth
diff --git a/system/test/headless/read/name.cc b/system/test/headless/read/name.cc
new file mode 100644
index 0000000..6a8ea9f
--- /dev/null
+++ b/system/test/headless/read/name.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless_sdp"
+
+#include <future>
+
+#include "base/logging.h"     // LOG() stdout and android log
+#include "osi/include/log.h"  // android log only
+#include "stack/include/btm_api.h"
+#include "stack/include/btm_api_types.h"
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+#include "test/headless/read/name.h"
+#include "types/raw_address.h"
+
+std::promise<tBTM_REMOTE_DEV_NAME> promise_;
+
+void RemoteNameCallback(void* data) {
+  promise_.set_value(*static_cast<tBTM_REMOTE_DEV_NAME*>(data));
+}
+
+int bluetooth::test::headless::Name::Run() {
+  if (options_.loop_ < 1) {
+    fprintf(stdout, "This test requires at least a single loop");
+    options_.Usage();
+    return -1;
+  }
+  if (options_.device_.size() != 1) {
+    fprintf(stdout, "This test requires a single device specified");
+    options_.Usage();
+    return -1;
+  }
+
+  const RawAddress& raw_address = options_.device_.front();
+
+  return RunOnHeadlessStack<int>([&raw_address]() {
+    promise_ = std::promise<tBTM_REMOTE_DEV_NAME>();
+
+    auto future = promise_.get_future();
+
+    tBTM_STATUS status = BTM_ReadRemoteDeviceName(
+        raw_address, &RemoteNameCallback, BT_TRANSPORT_BR_EDR);
+    if (status != BTM_CMD_STARTED) {
+      fprintf(stdout, "Failure to start read remote device\n");
+      return -1;
+    }
+
+    tBTM_REMOTE_DEV_NAME name_packet = future.get();
+    switch (name_packet.status) {
+      case BTM_SUCCESS: {
+        char buf[BD_NAME_LEN];
+        memcpy(buf, name_packet.remote_bd_name, BD_NAME_LEN);
+        std::string name(buf);
+        fprintf(stdout, "Name result mac:%s name:%s\n",
+                raw_address.ToString().c_str(), name.c_str());
+      } break;
+      case BTM_BAD_VALUE_RET:
+        fprintf(stdout, "Name Timeout or other failure");
+        return -2;
+      default:
+        fprintf(stdout, "Unexpected remote name request failure status:%hd",
+                name_packet.status);
+        return -2;
+    }
+    return 0;
+  });
+}
diff --git a/system/test/headless/read/name.h b/system/test/headless/read/name.h
new file mode 100644
index 0000000..587cee2
--- /dev/null
+++ b/system/test/headless/read/name.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+
+namespace bluetooth {
+namespace test {
+namespace headless {
+
+class Name : public HeadlessTest<int> {
+ public:
+  Name(const bluetooth::test::headless::GetOpt& options)
+      : HeadlessTest<int>(options) {}
+  int Run() override;
+};
+
+}  // namespace headless
+}  // namespace test
+}  // namespace bluetooth
diff --git a/system/test/headless/read/read.cc b/system/test/headless/read/read.cc
new file mode 100644
index 0000000..d1a3b8d
--- /dev/null
+++ b/system/test/headless/read/read.cc
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless"
+
+#include "test/headless/read/read.h"
+#include "base/logging.h"     // LOG() stdout and android log
+#include "osi/include/log.h"  // android log only
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+#include "test/headless/read/name.h"
+
+using namespace bluetooth::test::headless;
+
+Read::Read(const bluetooth::test::headless::GetOpt& options)
+    : HeadlessTest<int>(options) {
+  test_nodes_.emplace(
+      "name", std::make_unique<bluetooth::test::headless::Name>(options));
+}
diff --git a/system/test/headless/read/read.h b/system/test/headless/read/read.h
new file mode 100644
index 0000000..04ab0f2
--- /dev/null
+++ b/system/test/headless/read/read.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+
+namespace bluetooth {
+namespace test {
+namespace headless {
+
+class Read : public HeadlessTest<int> {
+ public:
+  Read(const bluetooth::test::headless::GetOpt& options);
+};
+
+}  // namespace headless
+}  // namespace test
+}  // namespace bluetooth
diff --git a/system/test/headless/sdp/main.cc b/system/test/headless/sdp/main.cc
deleted file mode 100644
index b031e28..0000000
--- a/system/test/headless/sdp/main.cc
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * 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
- *
- *      http://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.
- */
-
-#define LOG_TAG "bt_headless_sdp"
-
-#include <future>
-
-#include "base/logging.h"     // LOG() stdout and android log
-#include "osi/include/log.h"  // android log only
-#include "stack/include/sdp_api.h"
-#include "test/headless/get_options.h"
-#include "test/headless/headless.h"
-#include "types/raw_address.h"
-
-static void bta_jv_start_discovery_callback(uint16_t result, void* user_data) {
-  auto promise = static_cast<std::promise<uint16_t>*>(user_data);
-  promise->set_value(result);
-}
-
-constexpr size_t kMaxDiscoveryRecords = 16;
-
-int sdp_query_uuid(int num_loops, const RawAddress& raw_address,
-                   const bluetooth::Uuid& uuid) {
-  for (int i = 0; i < num_loops; i++) {
-    tSDP_DISCOVERY_DB* sdp_discovery_db = (tSDP_DISCOVERY_DB*)malloc(
-        sizeof(tSDP_DISCOVERY_DB) +
-        sizeof(tSDP_DISC_REC) * kMaxDiscoveryRecords);
-
-    if (!SDP_InitDiscoveryDb(sdp_discovery_db,
-                             sizeof(tSDP_DISCOVERY_DB) +
-                                 sizeof(tSDP_DISC_REC) * kMaxDiscoveryRecords,
-                             1,  // num_uuid,
-                             &uuid, 0, nullptr)) {
-      LOG(ERROR) << __func__ << " Unable to initialize sdp discovery";
-      return -1;
-    }
-
-    std::promise<uint16_t> promise;
-    auto future = promise.get_future();
-
-    if (!SDP_ServiceSearchAttributeRequest2(raw_address, sdp_discovery_db,
-                                            bta_jv_start_discovery_callback,
-                                            (void*)&promise)) {
-      LOG(ERROR) << __func__
-                 << " Failed to start search attribute request.. waiting";
-      return -2;
-    }
-    uint16_t result = future.get();
-    LOG(INFO) << __func__ << " connection result:" << result;
-
-    tSDP_DISC_REC* rec =
-        SDP_FindServiceInDb(sdp_discovery_db, uuid.As16Bit(), nullptr);
-    if (rec == nullptr) {
-      LOG(INFO) << __func__ << " iter:" << i << " discovery record is null"
-                << " from:" << raw_address.ToString() << " uuid:" << uuid;
-    } else {
-      printf("iter:%d result:%d attr_id:%x from:%s uuid:%s", i, result,
-             rec->p_first_attr->attr_id, rec->remote_bd_addr.ToString().c_str(),
-             uuid.ToString().c_str());
-
-      LOG(INFO) << __func__ << " iter:" << i << " result:" << result
-                << " discovery record found  attr_id:"
-                << rec->p_first_attr->attr_id
-                << " len_type:" << rec->p_first_attr->attr_len_type << " time"
-                << rec->time_read << " from:" << rec->remote_bd_addr.ToString()
-                << " uuid:" << uuid;
-      fflush(nullptr);
-    }
-    free(sdp_discovery_db);
-  }
-  return 0;
-}
-
-int main(int argc, char** argv) {
-  printf("Hello world\n");
-  fflush(nullptr);
-
-  LOG(INFO) << "bt_headless start up";
-
-  bluetooth::test::headless::GetOpt options(argc, argv);
-  if (!options.IsValid()) {
-    return -1;
-  }
-  if (options.loop_ < 1) {
-    LOG(INFO) << "This test requires at least a single loop";
-    options.Usage();
-    return -1;
-  }
-  if (options.device_.size() != 1) {
-    LOG(INFO) << "This test requires a single device specified";
-    options.Usage();
-    return -1;
-  }
-  if (options.uuid_.size() != 1) {
-    LOG(INFO) << "This test requires a single uuid specified";
-    options.Usage();
-    return -1;
-  }
-
-  bluetooth::test::headless::Test test;
-  int rc = test.Run<int>([options]() {
-    return sdp_query_uuid(options.loop_, options.device_.front(),
-                          options.uuid_.front());
-  });
-  LOG(INFO) << "bt_headless shut down";
-  return rc;
-}
diff --git a/system/test/headless/sdp/sdp.cc b/system/test/headless/sdp/sdp.cc
new file mode 100644
index 0000000..9e20340
--- /dev/null
+++ b/system/test/headless/sdp/sdp.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless_sdp"
+
+#include <future>
+
+#include "base/logging.h"     // LOG() stdout and android log
+#include "osi/include/log.h"  // android log only
+#include "stack/include/sdp_api.h"
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+#include "test/headless/sdp/sdp.h"
+#include "test/headless/sdp/sdp_db.h"
+#include "types/raw_address.h"
+
+using namespace bluetooth::test::headless;
+
+static void bta_jv_start_discovery_callback(uint16_t result, void* user_data) {
+  auto promise = static_cast<std::promise<uint16_t>*>(user_data);
+  promise->set_value(result);
+}
+
+namespace {
+
+struct sdp_error_code_s {
+  const char* name;
+  uint16_t error_code;
+} sdp_error_code[] = {
+    {"KsdpSuccess", 0},
+    {"KsdpInvalidVersion", 0x0001},
+    {"KsdpInvalidServRecHdl", 0x0002},
+    {"KsdpInvalidReqSyntax", 0x0003},
+    {"KsdpInvalidPduSize", 0x0004},
+    {"KsdpInvalidContState", 0x0005},
+    {"KsdpNoResources", 0x0006},
+    {"KsdpDiRegFailed", 0x0007},
+    {"KsdpDiDiscFailed", 0x0008},
+    {"KsdpNoDiRecordFound", 0x0009},
+    {"KsdpErrAttrNotPresent", 0x000a},
+    {"KsdpIllegalParameter", 0x000b},
+    {"KsdpNoRecsMatch", 0xFFF0},
+    {"KsdpConnFailed", 0xFFF1},
+    {"KsdpCfgFailed", 0xFFF2},
+    {"KsdpGenericError", 0xFFF3},
+    {"KsdpDbFull", 0xFFF4},
+    {"KsdpInvalidPdu", 0xFFF5},
+    {"KsdpSecurityErr", 0xFFF6},
+    {"KsdpConnRejected", 0xFFF7},
+    {"KsdpCancel", 0xFFF8},
+};
+
+const char* kUnknownText = "Unknown";
+
+const char* SdpErrorCodeToString(uint16_t code) {
+  for (size_t i = 0; i < sizeof(sdp_error_code) / sizeof(sdp_error_code_s);
+       ++i) {
+    if (sdp_error_code[i].error_code == code) {
+      return sdp_error_code[i].name;
+    }
+  }
+  return kUnknownText;
+}
+
+constexpr size_t kMaxDiscoveryRecords = 64;
+
+int sdp_query_uuid(unsigned int num_loops, const RawAddress& raw_address,
+                   const bluetooth::Uuid& uuid) {
+  SdpDb sdp_discovery_db(kMaxDiscoveryRecords);
+
+  if (!SDP_InitDiscoveryDb(sdp_discovery_db.RawPointer(),
+                           sdp_discovery_db.Length(),
+                           1,  // num_uuid,
+                           &uuid, 0, nullptr)) {
+    fprintf(stdout, "%s Unable to initialize sdp discovery\n", __func__);
+    return -1;
+  }
+
+  std::promise<uint16_t> promise;
+  auto future = promise.get_future();
+
+  sdp_discovery_db.Print(stdout);
+
+  if (!SDP_ServiceSearchAttributeRequest2(
+          raw_address, sdp_discovery_db.RawPointer(),
+          bta_jv_start_discovery_callback, (void*)&promise)) {
+    fprintf(stdout, "%s Failed to start search attribute request\n", __func__);
+    return -2;
+  }
+
+  uint16_t result = future.get();
+  if (result != 0) {
+    fprintf(stdout, "Failed search discovery result:%s\n",
+            SdpErrorCodeToString(result));
+    return result;
+  }
+
+  tSDP_DISC_REC* rec = SDP_FindServiceInDb(sdp_discovery_db.RawPointer(),
+                                           uuid.As16Bit(), nullptr);
+  if (rec == nullptr) {
+    fprintf(stdout, "discovery record is null from:%s uuid:%s\n",
+            raw_address.ToString().c_str(), uuid.ToString().c_str());
+  } else {
+    fprintf(stdout, "result:%d attr_id:%x from:%s uuid:%s\n", result,
+            rec->p_first_attr->attr_id, rec->remote_bd_addr.ToString().c_str(),
+            uuid.ToString().c_str());
+  }
+  return 0;
+}
+
+}  // namespace
+
+int bluetooth::test::headless::Sdp::Run() {
+  if (options_.loop_ < 1) {
+    printf("This test requires at least a single loop\n");
+    options_.Usage();
+    return -1;
+  }
+  if (options_.device_.size() != 1) {
+    printf("This test requires a single device specified\n");
+    options_.Usage();
+    return -1;
+  }
+  if (options_.uuid_.size() != 1) {
+    printf("This test requires a single uuid specified\n");
+    options_.Usage();
+    return -1;
+  }
+
+  return RunOnHeadlessStack<int>([this]() {
+    return sdp_query_uuid(options_.loop_, options_.device_.front(),
+                          options_.uuid_.front());
+  });
+}
diff --git a/system/test/headless/sdp/sdp.h b/system/test/headless/sdp/sdp.h
new file mode 100644
index 0000000..6542a1d
--- /dev/null
+++ b/system/test/headless/sdp/sdp.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include "test/headless/get_options.h"
+#include "test/headless/headless.h"
+
+namespace bluetooth {
+namespace test {
+namespace headless {
+
+class Sdp : public HeadlessTest<int> {
+ public:
+  Sdp(const bluetooth::test::headless::GetOpt& options)
+      : HeadlessTest<int>(options) {}
+  int Run() override;
+};
+
+}  // namespace headless
+}  // namespace test
+}  // namespace bluetooth
diff --git a/system/test/headless/sdp/sdp_db.cc b/system/test/headless/sdp/sdp_db.cc
new file mode 100644
index 0000000..023861b
--- /dev/null
+++ b/system/test/headless/sdp/sdp_db.cc
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "bt_headless"
+
+#include "test/headless/sdp/sdp_db.h"
+#include "base/logging.h"     // LOG() stdout and android log
+#include "osi/include/log.h"  // android log only
+#include "stack/include/sdp_api.h"
+#include "types/bluetooth/uuid.h"
+#include "types/raw_address.h"
+
+using namespace bluetooth::test::headless;
+
+SdpDb::SdpDb(unsigned int max_records) : max_records_(max_records) {
+  db_ = (tSDP_DISCOVERY_DB*)malloc(max_records_ * sizeof(tSDP_DISC_REC) +
+                                   sizeof(tSDP_DISCOVERY_DB));
+}
+
+SdpDb::~SdpDb() { free(db_); }
+
+tSDP_DISCOVERY_DB* SdpDb::RawPointer() { return db_; }
+
+uint32_t SdpDb::Length() const {
+  return max_records_ * sizeof(tSDP_DISC_REC) + sizeof(tSDP_DISCOVERY_DB);
+}
+
+void SdpDb::Print(FILE* filep) const {
+  fprintf(filep, "memory size:0x%x free:0x%x\n", db_->mem_size, db_->mem_free);
+  fprintf(filep, "number of filters:%hd\n", db_->num_uuid_filters);
+  for (int i = 0; i < db_->num_uuid_filters; i++) {
+    fprintf(filep, "  uuid:%s\n", db_->uuid_filters[i].ToString().c_str());
+  }
+  fprintf(filep, "raw data size:0x%x used:0x%x\n", db_->raw_size,
+          db_->raw_used);
+}
diff --git a/system/test/headless/sdp/sdp_db.h b/system/test/headless/sdp/sdp_db.h
new file mode 100644
index 0000000..63de0cc
--- /dev/null
+++ b/system/test/headless/sdp/sdp_db.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstdio>
+
+#include "stack/include/sdp_api.h"
+
+namespace bluetooth {
+namespace test {
+namespace headless {
+
+class SdpDb {
+ public:
+  SdpDb(unsigned int max_records);
+  ~SdpDb();
+
+  tSDP_DISCOVERY_DB* RawPointer();
+
+  uint32_t Length() const;
+
+  void Print(FILE* filep) const;
+
+ private:
+  unsigned int max_records_;
+  tSDP_DISCOVERY_DB* db_;
+};
+
+}  // namespace headless
+}  // namespace test
+}  // namespace bluetooth