Snap for 5339364 from 6e948fca8ec9d90e15b8ab488c6244c45aafd811 to qt-release
Change-Id: If4dbb0046aaa201120b16fced0fa6cc13056fb86
diff --git a/.clang-format b/.clang-format
index fc5f5fe..1f1f586 100644
--- a/.clang-format
+++ b/.clang-format
@@ -23,3 +23,6 @@
CommentPragmas: NOLINT:.*
DerivePointerAlignment: false
ColumnLimit: 120
+AllowShortFunctionsOnASingleLine: Empty
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+BreakConstructorInitializers: BeforeColon
diff --git a/bta/Android.bp b/bta/Android.bp
index 9dfda3c..0e7bfe8 100644
--- a/bta/Android.bp
+++ b/bta/Android.bp
@@ -129,6 +129,7 @@
"test/gatt/database_test.cc",
],
shared_libs: [
+ "libcrypto",
"liblog",
"libprotobuf-cpp-lite",
],
diff --git a/btif/include/btif_keystore.h b/btif/include/btif_keystore.h
index 01ccd66..4762350 100644
--- a/btif/include/btif_keystore.h
+++ b/btif/include/btif_keystore.h
@@ -42,22 +42,20 @@
BtifKeystore(keystore::KeystoreClient* keystore_client);
/**
- * Stores encrypted data to disk.
+ * Encrypts given data
*
- * <p>Returns true on success.
+ * <p>Returns a string representation of the encrypted data
*
* @param data to be encrypted
- * @param output_filename location to write the file
- * @param flags
+ * @param flags for keystore
*/
- bool Encrypt(const std::string& data, const std::string& output_filename,
- int32_t flags);
+ std::string Encrypt(const std::string& data, int32_t flags);
/**
* Returns a decrypted string representation of the encrypted data or empty
* string on error.
*
- * @param input_filename location of file to read and decrypt
+ * @param input encrypted data
*/
std::string Decrypt(const std::string& input_filename);
diff --git a/btif/src/btif_config.cc b/btif/src/btif_config.cc
index 567fce6..7d23e34 100644
--- a/btif/src/btif_config.cc
+++ b/btif/src/btif_config.cc
@@ -24,15 +24,15 @@
#include <ctype.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
+#include <private/android_filesystem_config.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <mutex>
#include <sstream>
#include <string>
-#include <mutex>
-
#include "bt_types.h"
#include "btcore/include/module.h"
#include "btif_api.h"
@@ -55,10 +55,13 @@
#define FILE_TIMESTAMP "TimeCreated"
#define FILE_SOURCE "FileSource"
#define TIME_STRING_LENGTH sizeof("YYYY-MM-DD HH:MM:SS")
+#define DISABLED "disabled"
static const char* TIME_STRING_FORMAT = "%Y-%m-%d %H:%M:%S";
constexpr int kBufferSize = 400 * 10; // initial file is ~400B
+static bool use_key_attestation() { return getuid() == AID_BLUETOOTH; }
+
#define BT_CONFIG_METRICS_SECTION "Metrics"
#define BT_CONFIG_METRICS_SALT_256BIT "Salt256Bit"
using bluetooth::BtifKeystore;
@@ -636,6 +639,10 @@
}
static std::string hash_file(const char* filename) {
+ if (!use_key_attestation()) {
+ LOG(INFO) << __func__ << ": Disabled for multi-user";
+ return DISABLED;
+ }
FILE* fp = fopen(filename, "rb");
if (!fp) {
LOG(ERROR) << __func__ << ": unable to open config file: '" << filename
@@ -660,15 +667,31 @@
}
static std::string read_checksum_file(const char* checksum_filename) {
- // Ensure file exists
- if (access(checksum_filename, R_OK) != 0) {
+ if (!use_key_attestation()) {
+ LOG(INFO) << __func__ << ": Disabled for multi-user";
+ return DISABLED;
+ }
+ std::string encrypted_hash = checksum_read(checksum_filename);
+ if (encrypted_hash.empty()) {
+ LOG(INFO) << __func__ << ": read empty hash.";
return "";
}
- return btif_keystore.Decrypt(checksum_filename);
+ return btif_keystore.Decrypt(encrypted_hash);
}
static void write_checksum_file(const char* checksum_filename,
const std::string& hash) {
- bool result = btif_keystore.Encrypt(hash, checksum_filename, 0);
- CHECK(result) << "Failed writing checksum";
+ if (!use_key_attestation()) {
+ LOG(INFO) << __func__
+ << ": Disabled for multi-user, since config changed removing "
+ "checksums.";
+ remove(CONFIG_FILE_CHECKSUM_PATH);
+ remove(CONFIG_BACKUP_CHECKSUM_PATH);
+ return;
+ }
+ std::string encrypted_checksum = btif_keystore.Encrypt(hash, 0);
+ CHECK(!encrypted_checksum.empty())
+ << __func__ << ": Failed encrypting checksum";
+ CHECK(checksum_save(encrypted_checksum, checksum_filename))
+ << __func__ << ": Failed to save checksum!";
}
diff --git a/btif/src/btif_keystore.cc b/btif/src/btif_keystore.cc
index ac3a803..fe9d3dd 100644
--- a/btif/src/btif_keystore.cc
+++ b/btif/src/btif_keystore.cc
@@ -31,78 +31,41 @@
constexpr char kKeyStore[] = "AndroidKeystore";
-static std::string ReadFile(const std::string& filename) {
- CHECK(!filename.empty()) << __func__ << ": filename should not be empty";
-
- std::string content;
- base::FilePath path(filename);
- if (!base::PathExists(path)) {
- // Config file checksum file doesn't exist on first run after OTA.
- LOG(ERROR) << "file '" << filename.c_str() << "'doesn't exists yet";
- }
- if (!base::ReadFileToString(path, &content)) {
- LOG(ERROR) << "ReadFile failed: " << filename.c_str();
- }
- return content;
-}
-
-static void WriteFile(const std::string& filename, const std::string& content) {
- CHECK(!filename.empty()) << __func__ << ": filename should not be empty";
- CHECK(!content.empty()) << __func__ << ": content should not be empty";
-
- base::FilePath path(filename);
- int size = content.size();
- if (base::WriteFile(path, content.data(), size) != size) {
- LOG(FATAL) << "WriteFile failed.\n" << filename.c_str();
- }
-}
-
namespace bluetooth {
BtifKeystore::BtifKeystore(keystore::KeystoreClient* keystore_client)
: keystore_client_(keystore_client) {}
-bool BtifKeystore::Encrypt(const std::string& data,
- const std::string& output_filename, int32_t flags) {
+std::string BtifKeystore::Encrypt(const std::string& data, int32_t flags) {
std::lock_guard<std::mutex> lock(api_mutex_);
+ std::string output;
if (data.empty()) {
LOG(ERROR) << __func__ << ": empty data";
- return false;
+ return output;
}
- if (output_filename.empty()) {
- LOG(ERROR) << __func__ << ": empty output filename";
- return false;
- }
- std::string output;
if (!keystore_client_->doesKeyExist(kKeyStore)) {
auto gen_result = GenerateKey(kKeyStore, 0, false);
if (!gen_result.isOk()) {
LOG(FATAL) << "EncryptWithAuthentication Failed: generateKey response="
<< gen_result;
- return false;
+ return output;
}
}
if (!keystore_client_->encryptWithAuthentication(kKeyStore, data, flags,
&output)) {
LOG(FATAL) << "EncryptWithAuthentication failed.";
- return false;
+ return output;
}
- WriteFile(output_filename, output);
- return true;
+ return output;
}
-std::string BtifKeystore::Decrypt(const std::string& input_filename) {
+std::string BtifKeystore::Decrypt(const std::string& input) {
std::lock_guard<std::mutex> lock(api_mutex_);
- std::string output;
- if (input_filename.empty()) {
- LOG(ERROR) << __func__ << ": empty input filename";
- return output;
- }
- std::string input = ReadFile(input_filename);
if (input.empty()) {
LOG(ERROR) << __func__ << ": empty input data";
- return output;
+ return "";
}
+ std::string output;
if (!keystore_client_->decryptWithAuthentication(kKeyStore, input, &output)) {
LOG(FATAL) << "DecryptWithAuthentication failed.\n";
}
diff --git a/btif/test/btif_keystore_test.cc b/btif/test/btif_keystore_test.cc
index e57c64f..4cabbad 100644
--- a/btif/test/btif_keystore_test.cc
+++ b/btif/test/btif_keystore_test.cc
@@ -16,7 +16,6 @@
*
******************************************************************************/
-#include <base/files/file_util.h>
#include <base/logging.h>
#include <binder/ProcessState.h>
#include <gtest/gtest.h>
@@ -26,73 +25,40 @@
using namespace bluetooth;
-constexpr char kFilename[] = "/data/misc/bluedroid/testfile.txt";
-
class BtifKeystoreTest : public ::testing::Test {
protected:
std::unique_ptr<BtifKeystore> btif_keystore_;
- base::FilePath file_path_;
- BtifKeystoreTest() : file_path_(kFilename) {}
void SetUp() override {
android::ProcessState::self()->startThreadPool();
btif_keystore_ =
std::make_unique<BtifKeystore>(static_cast<keystore::KeystoreClient*>(
new keystore::KeystoreClientImpl));
- base::DeleteFile(file_path_, true);
};
void TearDown() override { btif_keystore_ = nullptr; };
};
-// Encrypt
TEST_F(BtifKeystoreTest, test_encrypt_decrypt) {
std::string hash = "test";
- EXPECT_TRUE(btif_keystore_->Encrypt(hash, kFilename, 0));
- std::string decrypted_hash = btif_keystore_->Decrypt(kFilename);
+ std::string encrypted_hash = btif_keystore_->Encrypt(hash, 0);
+ std::string decrypted_hash = btif_keystore_->Decrypt(encrypted_hash);
- EXPECT_TRUE(base::PathExists(file_path_));
+ EXPECT_FALSE(encrypted_hash.empty());
EXPECT_EQ(hash, decrypted_hash);
}
TEST_F(BtifKeystoreTest, test_encrypt_empty_hash) {
std::string hash = "";
- EXPECT_FALSE(btif_keystore_->Encrypt(hash, kFilename, 0));
+ std::string encrypted_hash = btif_keystore_->Encrypt(hash, 0);
- EXPECT_FALSE(base::PathExists(file_path_));
+ EXPECT_TRUE(encrypted_hash.empty());
}
-TEST_F(BtifKeystoreTest, test_encrypt_empty_filename) {
- std::string hash = "test";
-
- EXPECT_FALSE(btif_keystore_->Encrypt(hash, "", 0));
-
- EXPECT_FALSE(base::PathExists(file_path_));
-}
-
-// Decrypt
TEST_F(BtifKeystoreTest, test_decrypt_empty_hash) {
- // Only way to get the hash to decrypt is to read it from the file
- // So make empty file manually
- std::ofstream outfile(kFilename);
- outfile.close();
+ std::string hash = "";
- std::string decrypted_hash = btif_keystore_->Decrypt(kFilename);
-
- EXPECT_TRUE(decrypted_hash.empty());
-}
-
-TEST_F(BtifKeystoreTest, test_decrypt_file_not_exist) {
- // Ensure file doesn't exist, then decrypt
- EXPECT_FALSE(base::PathExists(file_path_));
-
- std::string decrypted_hash = btif_keystore_->Decrypt(kFilename);
-
- EXPECT_TRUE(decrypted_hash.empty());
-}
-
-TEST_F(BtifKeystoreTest, test_decrypt_empty_filename) {
- std::string decrypted_hash = btif_keystore_->Decrypt("");
+ std::string decrypted_hash = btif_keystore_->Decrypt(hash);
EXPECT_TRUE(decrypted_hash.empty());
}
diff --git a/common/Android.bp b/common/Android.bp
index 8e077cd..0dd1fa2 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -1,6 +1,9 @@
cc_library_static {
name: "libbt-common",
- defaults: ["fluoride_defaults"],
+ defaults: [
+ "fluoride_defaults",
+ "clang_file_coverage",
+ ],
host_supported: true,
include_dirs: [
"system/bt",
@@ -8,10 +11,15 @@
],
srcs: [
"address_obfuscator.cc",
+ "alarm.cc",
+ "handler.cc",
"message_loop_thread.cc",
"metrics.cc",
"once_timer.cc",
+ "reactor.cc",
+ "repeating_alarm.cc",
"repeating_timer.cc",
+ "thread.cc",
"time_util.cc",
],
shared_libs: [
@@ -25,7 +33,10 @@
cc_test {
name: "bluetooth_test_common",
test_suites: ["device-tests"],
- defaults: ["fluoride_defaults"],
+ defaults: [
+ "fluoride_defaults",
+ "clang_coverage_bin",
+ ],
host_supported: true,
include_dirs: [
"system/bt",
@@ -33,12 +44,17 @@
],
srcs : [
"address_obfuscator_unittest.cc",
+ "alarm_unittest.cc",
+ "handler_unittest.cc",
"leaky_bonded_queue_unittest.cc",
"message_loop_thread_unittest.cc",
"metrics_unittest.cc",
"once_timer_unittest.cc",
+ "reactor_unittest.cc",
+ "repeating_alarm_unittest.cc",
"repeating_timer_unittest.cc",
"state_machine_unittest.cc",
+ "thread_unittest.cc",
"time_util_unittest.cc",
"id_generator_unittest.cc",
],
@@ -77,12 +93,16 @@
cc_benchmark {
name: "bluetooth_benchmark_thread_performance",
- defaults: ["fluoride_defaults"],
+ defaults: [
+ "fluoride_defaults",
+ ],
+ host_supported: true,
include_dirs: ["system/bt"],
srcs: [
"benchmark/thread_performance_benchmark.cc",
],
shared_libs: [
+ "libcrypto",
"liblog",
],
static_libs: [
@@ -93,14 +113,19 @@
cc_benchmark {
name: "bluetooth_benchmark_timer_performance",
- defaults: ["fluoride_defaults"],
+ defaults: [
+ "fluoride_defaults",
+ ],
+ host_supported: false,
include_dirs: ["system/bt"],
srcs: [
"benchmark/timer_performance_benchmark.cc",
],
shared_libs: [
"liblog",
+ "libcrypto",
"libprotobuf-cpp-lite",
+ "libcrypto",
"libcutils",
],
static_libs: [
diff --git a/common/alarm.cc b/common/alarm.cc
new file mode 100644
index 0000000..247acb2
--- /dev/null
+++ b/common/alarm.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "alarm.h"
+
+#include <sys/timerfd.h>
+#include <cstring>
+
+#include "base/logging.h"
+#include "utils.h"
+
+namespace bluetooth {
+namespace common {
+
+Alarm::Alarm(Thread* thread)
+ : thread_(thread),
+ fd_(timerfd_create(CLOCK_BOOTTIME_ALARM, 0)) {
+ CHECK_NE(fd_, -1) << __func__ << ": cannot create timerfd: " << strerror(errno);
+
+ token_ = thread_->GetReactor()->Register(fd_, [this] { on_fire(); }, nullptr);
+}
+
+Alarm::~Alarm() {
+ thread_->GetReactor()->Unregister(token_);
+
+ int close_status;
+ RUN_NO_INTR(close_status = close(fd_));
+ CHECK_NE(close_status, -1) << __func__ << ": cannot close timerfd: " << strerror(errno);
+}
+
+void Alarm::Schedule(Closure task, std::chrono::milliseconds delay) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ long delay_ms = delay.count();
+ itimerspec timer_itimerspec{
+ {/* interval for periodic timer */},
+ {delay_ms / 1000, delay_ms % 1000 * 1000000}
+ };
+ int result = timerfd_settime(fd_, 0, &timer_itimerspec, nullptr);
+ CHECK_EQ(result, 0) << __func__ << ": failed, error=" << strerror(errno);
+
+ task_ = std::move(task);
+}
+
+void Alarm::Cancel() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ itimerspec disarm_itimerspec{/* disarm timer */};
+ int result = timerfd_settime(fd_, 0, &disarm_itimerspec, nullptr);
+ CHECK_EQ(result, 0) << __func__ << ": failed, error=" << strerror(errno);
+}
+
+void Alarm::on_fire() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ auto task = std::move(task_);
+ uint64_t times_invoked;
+ auto bytes_read = read(fd_, ×_invoked, sizeof(uint64_t));
+ lock.unlock();
+ task();
+ CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(uint64_t))) << __func__ << ": failed, error=" << strerror(errno);
+ CHECK_EQ(times_invoked, static_cast<uint64_t>(1));
+}
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/alarm.h b/common/alarm.h
new file mode 100644
index 0000000..1b35b8f
--- /dev/null
+++ b/common/alarm.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 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 <functional>
+#include <memory>
+#include <mutex>
+
+#include "common/thread.h"
+#include "common/utils.h"
+
+namespace bluetooth {
+namespace common {
+
+// A single-shot alarm for reactor-based thread, implemented by Linux timerfd.
+// When it's constructed, it will register a reactable on the specified thread; when it's destroyed, it will unregister
+// itself from the thread.
+class Alarm {
+ public:
+ // Create and register a single-shot alarm on given thread
+ explicit Alarm(Thread* thread);
+
+ // Unregister this alarm from the thread and release resource
+ ~Alarm();
+
+ DISALLOW_COPY_AND_ASSIGN(Alarm);
+
+ // Schedule the alarm with given delay
+ void Schedule(Closure task, std::chrono::milliseconds delay);
+
+ // Cancel the alarm. No-op if it's not armed.
+ void Cancel();
+
+ private:
+ Closure task_;
+ Thread* thread_;
+ int fd_ = 0;
+ Reactor::Reactable* token_;
+ mutable std::mutex mutex_;
+ void on_fire();
+};
+
+} // namespace common
+
+} // namespace bluetooth
diff --git a/common/alarm_unittest.cc b/common/alarm_unittest.cc
new file mode 100644
index 0000000..25c20ad
--- /dev/null
+++ b/common/alarm_unittest.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "alarm.h"
+
+#include <future>
+
+#include "base/logging.h"
+#include "gtest/gtest.h"
+
+namespace bluetooth {
+namespace common {
+namespace {
+
+class AlarmTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ thread_ = new Thread("test_thread", Thread::Priority::NORMAL);
+ alarm_ = new Alarm(thread_);
+ }
+
+ void TearDown() override {
+ delete alarm_;
+ delete thread_;
+ }
+ Alarm* alarm_;
+
+ private:
+ Thread* thread_;
+};
+
+TEST_F(AlarmTest, cancel_while_not_armed) {
+ alarm_->Cancel();
+}
+
+TEST_F(AlarmTest, schedule) {
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ auto before = std::chrono::steady_clock::now();
+ int delay_ms = 10;
+ int delay_error_ms = 3;
+ alarm_->Schedule([&promise]() { promise.set_value(); }, std::chrono::milliseconds(delay_ms));
+ future.get();
+ auto after = std::chrono::steady_clock::now();
+ auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
+ ASSERT_NEAR(duration_ms.count(), delay_ms, delay_error_ms);
+}
+
+TEST_F(AlarmTest, cancel_alarm) {
+ alarm_->Schedule([]() { ASSERT_TRUE(false) << "Should not happen"; }, std::chrono::milliseconds(3));
+ alarm_->Cancel();
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+}
+
+TEST_F(AlarmTest, cancel_alarm_from_callback) {
+ alarm_->Schedule([this]() { this->alarm_->Cancel(); }, std::chrono::milliseconds(1));
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+}
+
+TEST_F(AlarmTest, schedule_while_alarm_armed) {
+ alarm_->Schedule([]() { ASSERT_TRUE(false) << "Should not happen"; }, std::chrono::milliseconds(1));
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ alarm_->Schedule([&promise]() { promise.set_value(); }, std::chrono::milliseconds(10));
+ future.get();
+}
+
+TEST_F(AlarmTest, delete_while_alarm_armed) {
+ alarm_->Schedule([]() { ASSERT_TRUE(false) << "Should not happen"; }, std::chrono::milliseconds(1));
+ delete alarm_;
+ alarm_ = nullptr;
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+}
+
+} // namespace
+} // namespace common
+} // namespace bluetooth
diff --git a/common/benchmark/thread_performance_benchmark.cc b/common/benchmark/thread_performance_benchmark.cc
index 74f157f..01f332f 100644
--- a/common/benchmark/thread_performance_benchmark.cc
+++ b/common/benchmark/thread_performance_benchmark.cc
@@ -23,12 +23,16 @@
#include <memory>
#include <thread>
+#include "common/handler.h"
#include "common/message_loop_thread.h"
+#include "common/thread.h"
#include "osi/include/fixed_queue.h"
#include "osi/include/thread.h"
using ::benchmark::State;
+using bluetooth::common::Handler;
using bluetooth::common::MessageLoopThread;
+using bluetooth::common::Thread;
#define NUM_MESSAGES_TO_SEND 100000
@@ -419,6 +423,54 @@
}
};
+class BM_ReactorThread : public BM_ThreadPerformance {
+ protected:
+ void SetUp(State& st) override {
+ BM_ThreadPerformance::SetUp(st);
+ std::future<void> set_up_future = set_up_promise_->get_future();
+ thread_ = new Thread("BM_ReactorThread thread", Thread::Priority::NORMAL);
+ handler_ = new Handler(thread_);
+ handler_->Post([this]() { set_up_promise_->set_value(); });
+ set_up_future.wait();
+ }
+
+ void TearDown(State& st) override {
+ delete handler_;
+ handler_ = nullptr;
+ thread_->Stop();
+ delete thread_;
+ thread_ = nullptr;
+ BM_ThreadPerformance::TearDown(st);
+ }
+
+ Thread* thread_ = nullptr;
+ Handler* handler_ = nullptr;
+};
+
+BENCHMARK_F(BM_ReactorThread, batch_enque_dequeue)(State& state) {
+ for (auto _ : state) {
+ g_counter = 0;
+ g_counter_promise = std::make_unique<std::promise<void>>();
+ std::future<void> counter_future = g_counter_promise->get_future();
+ for (int i = 0; i < NUM_MESSAGES_TO_SEND; i++) {
+ fixed_queue_enqueue(bt_msg_queue_, (void*)&g_counter);
+ handler_->Post([this]() { callback_batch(bt_msg_queue_, nullptr); });
+ }
+ counter_future.wait();
+ }
+};
+
+BENCHMARK_F(BM_ReactorThread, sequential_execution)(State& state) {
+ for (auto _ : state) {
+ for (int i = 0; i < NUM_MESSAGES_TO_SEND; i++) {
+ g_counter_promise = std::make_unique<std::promise<void>>();
+ std::future<void> counter_future = g_counter_promise->get_future();
+ handler_->Post([]() { callback_sequential(nullptr); });
+ counter_future.wait();
+ }
+ }
+};
+
int main(int argc, char** argv) {
// Disable LOG() output from libchrome
logging::LoggingSettings log_settings;
diff --git a/common/benchmark/timer_performance_benchmark.cc b/common/benchmark/timer_performance_benchmark.cc
index deaaad8..2f7826b 100644
--- a/common/benchmark/timer_performance_benchmark.cc
+++ b/common/benchmark/timer_performance_benchmark.cc
@@ -20,16 +20,22 @@
#include <benchmark/benchmark.h>
#include <future>
+#include "common/alarm.h"
#include "common/message_loop_thread.h"
#include "common/once_timer.h"
+#include "common/repeating_alarm.h"
#include "common/repeating_timer.h"
+#include "common/thread.h"
#include "common/time_util.h"
#include "osi/include/alarm.h"
using ::benchmark::State;
+using bluetooth::common::Alarm;
using bluetooth::common::MessageLoopThread;
using bluetooth::common::OnceTimer;
+using bluetooth::common::RepeatingAlarm;
using bluetooth::common::RepeatingTimer;
+using bluetooth::common::Thread;
using bluetooth::common::time_get_os_boottime_us;
// fake get_main_message_loop implementation for alarm
@@ -268,6 +274,88 @@
->Iterations(1)
->UseRealTime();
+class BM_ReactableAlarm : public ::benchmark::Fixture {
+ protected:
+ void SetUp(State& st) override {
+ ::benchmark::Fixture::SetUp(st);
+ thread_ = std::make_unique<Thread>("timer_benchmark", Thread::Priority::REAL_TIME);
+ alarm_ = std::make_unique<Alarm>(thread_.get());
+ repeating_alarm_ = std::make_unique<RepeatingAlarm>(thread_.get());
+ g_map.clear();
+ g_promise = std::make_shared<std::promise<void>>();
+ g_scheduled_tasks = 0;
+ g_task_length = 0;
+ g_task_interval = 0;
+ g_task_counter = 0;
+ }
+
+ void TearDown(State& st) override {
+ g_promise = nullptr;
+ alarm_ = nullptr;
+ repeating_alarm_ = nullptr;
+ thread_->Stop();
+ thread_ = nullptr;
+ ::benchmark::Fixture::TearDown(st);
+ }
+
+ std::unique_ptr<Thread> thread_;
+ std::unique_ptr<Alarm> alarm_;
+ std::unique_ptr<RepeatingAlarm> repeating_alarm_;
+};
+
+BENCHMARK_DEFINE_F(BM_ReactableAlarm, timer_performance_ms)(State& state) {
+ auto milliseconds = static_cast<int>(state.range(0));
+ for (auto _ : state) {
+ auto start_time_point = time_get_os_boottime_us();
+ alarm_->Schedule(std::bind(TimerFire, nullptr), std::chrono::milliseconds(milliseconds));
+ g_promise->get_future().get();
+ auto end_time_point = time_get_os_boottime_us();
+ auto duration = end_time_point - start_time_point;
+ state.SetIterationTime(duration * 1e-6);
+ alarm_->Cancel();
+ }
+};
+
+BENCHMARK_REGISTER_F(BM_ReactableAlarm, timer_performance_ms)
+ ->Arg(1)
+ ->Arg(5)
+ ->Arg(10)
+ ->Arg(20)
+ ->Arg(100)
+ ->Arg(1000)
+ ->Arg(2000)
+ ->Iterations(1)
+ ->UseRealTime();
+
+BENCHMARK_DEFINE_F(BM_ReactableAlarm, periodic_accuracy)
+(State& state) {
+ for (auto _ : state) {
+ g_scheduled_tasks = state.range(0);
+ g_task_length = state.range(1);
+ g_task_interval = state.range(2);
+ g_start_time = time_get_os_boottime_us();
+ repeating_alarm_->Schedule([] { AlarmSleepAndCountDelayedTime(nullptr); },
+ std::chrono::milliseconds(g_task_interval));
+ g_promise->get_future().get();
+ repeating_alarm_->Cancel();
+ }
+ for (const auto& delay : g_map) {
+ state.counters[std::to_string(delay.first)] = delay.second;
+ }
+};
+
+BENCHMARK_REGISTER_F(BM_ReactableAlarm, periodic_accuracy)
+ ->Args({2000, 1, 5})
+ ->Args({2000, 3, 5})
+ ->Args({2000, 1, 7})
+ ->Args({2000, 3, 7})
+ ->Args({2000, 1, 20})
+ ->Args({2000, 5, 20})
+ ->Args({2000, 10, 20})
+ ->Args({2000, 15, 20})
+ ->Iterations(1)
+ ->UseRealTime();
+
int main(int argc, char** argv) {
// Disable LOG() output from libchrome
logging::LoggingSettings log_settings;
diff --git a/common/handler.cc b/common/handler.cc
new file mode 100644
index 0000000..219dd82
--- /dev/null
+++ b/common/handler.cc
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "handler.h"
+
+#include <sys/eventfd.h>
+#include <cstring>
+
+#include "base/logging.h"
+
+#include "reactor.h"
+#include "utils.h"
+
+#ifndef EFD_SEMAPHORE
+#define EFD_SEMAPHORE 1
+#endif
+
+namespace bluetooth {
+namespace common {
+
+Handler::Handler(Thread* thread)
+ : thread_(thread),
+ fd_(eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) {
+ CHECK_NE(fd_, -1) << __func__ << ": cannot create eventfd: " << strerror(errno);
+
+ reactable_ = thread_->GetReactor()->Register(fd_, [this] { this->handle_next_event(); }, nullptr);
+}
+
+Handler::~Handler() {
+ thread_->GetReactor()->Unregister(reactable_);
+ reactable_ = nullptr;
+
+ int close_status;
+ RUN_NO_INTR(close_status = close(fd_));
+ CHECK_NE(close_status, -1) << __func__ << ": cannot close eventfd: " << strerror(errno);
+}
+
+void Handler::Post(Closure closure) {
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ tasks_.emplace(std::move(closure));
+ }
+ uint64_t val = 1;
+ auto write_result = eventfd_write(fd_, val);
+ CHECK_NE(write_result, -1) << __func__ << ": failed to write: " << strerror(errno);
+}
+
+void Handler::Clear() {
+ std::lock_guard<std::mutex> lock(mutex_);
+
+ std::queue<Closure> empty;
+ std::swap(tasks_, empty);
+
+ uint64_t val;
+ while (eventfd_read(fd_, &val) == 0) {
+ }
+}
+
+void Handler::handle_next_event() {
+ Closure closure;
+ uint64_t val = 0;
+ auto read_result = eventfd_read(fd_, &val);
+ CHECK_NE(read_result, -1) << __func__ << ": failed to read fd: " << strerror(errno);
+
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ closure = std::move(tasks_.front());
+ tasks_.pop();
+ }
+ closure();
+}
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/handler.h b/common/handler.h
new file mode 100644
index 0000000..71b4dba
--- /dev/null
+++ b/common/handler.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 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 <functional>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#include "common/thread.h"
+#include "common/utils.h"
+
+namespace bluetooth {
+namespace common {
+
+// A message-queue style handler for reactor-based thread to handle incoming events from different threads. When it's
+// constructed, it will register a reactable on the specified thread; when it's destroyed, it will unregister itself
+// from the thread.
+class Handler {
+ public:
+ // Create and register a handler on given thread
+ explicit Handler(Thread* thread);
+
+ // Unregister this handler from the thread and release resource. Unhandled events will be discarded and not executed.
+ ~Handler();
+
+ DISALLOW_COPY_AND_ASSIGN(Handler);
+
+ // Enqueue a closure to the queue of this handler
+ void Post(Closure closure);
+
+ // Remove all pending events from the queue of this handler
+ void Clear();
+
+ private:
+ std::queue<Closure> tasks_;
+ Thread* thread_;
+ int fd_;
+ Reactor::Reactable* reactable_;
+ mutable std::mutex mutex_;
+ void handle_next_event();
+};
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/handler_unittest.cc b/common/handler_unittest.cc
new file mode 100644
index 0000000..c51e51b
--- /dev/null
+++ b/common/handler_unittest.cc
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "handler.h"
+
+#include <sys/eventfd.h>
+#include <thread>
+
+#include <gtest/gtest.h>
+#include "base/logging.h"
+
+namespace bluetooth {
+namespace common {
+namespace {
+
+class HandlerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ thread_ = new Thread("test_thread", Thread::Priority::NORMAL);
+ handler_ = new Handler(thread_);
+ }
+ void TearDown() override {
+ delete handler_;
+ delete thread_;
+ }
+
+ Handler* handler_;
+ Thread* thread_;
+};
+
+TEST_F(HandlerTest, empty) {}
+
+TEST_F(HandlerTest, post_task_invoked) {
+ int val = 0;
+ Closure closure = [&val]() { val++; };
+ handler_->Post(closure);
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ EXPECT_EQ(val, 1);
+}
+
+TEST_F(HandlerTest, post_task_cleared) {
+ int val = 0;
+ Closure closure = [&val]() {
+ val++;
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+ };
+ handler_->Post(std::move(closure));
+ closure = []() { LOG(FATAL) << "Should not happen"; };
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+ handler_->Post(std::move(closure));
+ handler_->Clear();
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ EXPECT_EQ(val, 1);
+}
+
+} // namespace
+} // namespace common
+} // namespace bluetooth
diff --git a/common/reactor.cc b/common/reactor.cc
new file mode 100644
index 0000000..61cc7d6
--- /dev/null
+++ b/common/reactor.cc
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "reactor.h"
+
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+#include <algorithm>
+#include <cerrno>
+#include <cstring>
+
+#include "base/logging.h"
+
+namespace {
+
+// Use at most sizeof(epoll_event) * kEpollMaxEvents kernel memory
+constexpr int kEpollMaxEvents = 64;
+
+} // namespace
+
+namespace bluetooth {
+namespace common {
+
+class Reactor::Reactable {
+ public:
+ Reactable(int fd, Closure on_read_ready, Closure on_write_ready)
+ : fd_(fd),
+ on_read_ready_(std::move(on_read_ready)),
+ on_write_ready_(std::move(on_write_ready)),
+ is_executing_(false) {}
+ const int fd_;
+ Closure on_read_ready_;
+ Closure on_write_ready_;
+ bool is_executing_;
+ std::recursive_mutex lock_;
+};
+
+Reactor::Reactor()
+ : epoll_fd_(0),
+ control_fd_(0),
+ is_running_(false),
+ reactable_removed_(false) {
+ RUN_NO_INTR(epoll_fd_ = epoll_create1(EPOLL_CLOEXEC));
+ CHECK_NE(epoll_fd_, -1) << __func__ << ": cannot create epoll_fd: " << strerror(errno);
+
+ control_fd_ = eventfd(0, EFD_NONBLOCK);
+ CHECK_NE(control_fd_, -1) << __func__ << ": cannot create control_fd: " << strerror(errno);
+
+ epoll_event control_epoll_event = {EPOLLIN, {.ptr = nullptr}};
+ int result;
+ RUN_NO_INTR(result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, control_fd_, &control_epoll_event));
+ CHECK_NE(result, -1) << __func__ << ": cannot register control_fd: " << strerror(errno);
+}
+
+Reactor::~Reactor() {
+ int result;
+ RUN_NO_INTR(result = epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, control_fd_, nullptr));
+ CHECK_NE(result, -1) << __func__ << ": cannot unregister control_fd: " << strerror(errno);
+
+ RUN_NO_INTR(result = close(control_fd_));
+ CHECK_NE(result, -1) << __func__ << ": cannot close control_fd: " << strerror(errno);
+
+ RUN_NO_INTR(result = close(epoll_fd_));
+ CHECK_NE(result, -1) << __func__ << ": cannot close epoll_fd: " << strerror(errno);
+}
+
+void Reactor::Run() {
+ bool previously_running = is_running_.exchange(true);
+ CHECK_EQ(previously_running, false) << __func__ << ": already running";
+ LOG(INFO) << __func__ << ": started";
+
+ for (;;) {
+ invalidation_list_.clear();
+ epoll_event events[kEpollMaxEvents];
+ int count;
+ RUN_NO_INTR(count = epoll_wait(epoll_fd_, events, kEpollMaxEvents, -1));
+ CHECK_NE(count, -1) << __func__ << ": Error polling for fds: " << strerror(errno);
+
+ for (int i = 0; i < count; ++i) {
+ auto event = events[i];
+ CHECK_NE(event.events, 0u) << __func__ << ": no result in epoll result";
+
+ // If the ptr stored in epoll_event.data is nullptr, it means the control fd triggered
+ if (event.data.ptr == nullptr) {
+ uint64_t value;
+ eventfd_read(control_fd_, &value);
+ LOG(INFO) << __func__ << ": stopped";
+ is_running_ = false;
+ return;
+ }
+ auto* reactable = static_cast<Reactor::Reactable*>(event.data.ptr);
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ // See if this reactable has been removed in the meantime.
+ if (std::find(invalidation_list_.begin(), invalidation_list_.end(), reactable) != invalidation_list_.end()) {
+ continue;
+ }
+
+ std::lock_guard<std::recursive_mutex> reactable_lock(reactable->lock_);
+ lock.unlock();
+ reactable_removed_ = false;
+ reactable->is_executing_ = true;
+ if (event.events & (EPOLLIN | EPOLLHUP | EPOLLRDHUP | EPOLLERR) && reactable->on_read_ready_ != nullptr) {
+ reactable->on_read_ready_();
+ }
+ if (!reactable_removed_ && event.events & EPOLLOUT && reactable->on_write_ready_ != nullptr) {
+ reactable->on_write_ready_();
+ }
+ reactable->is_executing_ = false;
+ }
+ if (reactable_removed_) {
+ delete reactable;
+ }
+ }
+ }
+}
+
+void Reactor::Stop() {
+ if (!is_running_) {
+ LOG(WARNING) << __func__ << ": not running, will stop once it's started";
+ }
+ auto control = eventfd_write(control_fd_, 1);
+ CHECK_NE(control, -1) << __func__ << ": failed: " << strerror(errno);
+}
+
+Reactor::Reactable* Reactor::Register(int fd, Closure on_read_ready, Closure on_write_ready) {
+ uint32_t poll_event_type = 0;
+ if (on_read_ready != nullptr) {
+ poll_event_type |= (EPOLLIN | EPOLLRDHUP);
+ }
+ if (on_write_ready != nullptr) {
+ poll_event_type |= EPOLLOUT;
+ }
+ auto* reactable = new Reactable(fd, on_read_ready, on_write_ready);
+ epoll_event event = {
+ .events = poll_event_type,
+ {.ptr = reactable}
+ };
+ int register_fd;
+ RUN_NO_INTR(register_fd = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event));
+ CHECK_NE(register_fd, -1) << __func__ << ": failed: " << strerror(errno);
+ return reactable;
+}
+
+void Reactor::Unregister(Reactor::Reactable* reactable) {
+ CHECK_NE(reactable, nullptr);
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ invalidation_list_.push_back(reactable);
+ }
+ {
+ int result;
+ std::lock_guard<std::recursive_mutex> reactable_lock(reactable->lock_);
+ RUN_NO_INTR(result = epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, reactable->fd_, nullptr));
+ if (result == -1 && errno == ENOENT) {
+ LOG(INFO) << __func__ << ": reactable is invalid or unregistered";
+ } else if (result == -1) {
+ LOG(FATAL) << __func__ << ": failed: " << strerror(errno);
+ }
+ // If we are unregistering during the callback event from this reactable, we delete it after the callback is executed.
+ // reactable->is_executing_ is protected by reactable->lock_, so it's thread safe.
+ if (reactable->is_executing_) {
+ reactable_removed_ = true;
+ }
+ }
+ // If we are unregistering outside of the callback event from this reactable, we delete it now
+ if (!reactable_removed_) {
+ delete reactable;
+ }
+}
+
+void Reactor::ModifyRegistration(Reactor::Reactable* reactable, Closure on_read_ready, Closure on_write_ready) {
+ CHECK_NE(reactable, nullptr);
+
+ uint32_t poll_event_type = 0;
+ if (on_read_ready != nullptr) {
+ poll_event_type |= (EPOLLIN | EPOLLRDHUP);
+ }
+ if (on_write_ready != nullptr) {
+ poll_event_type |= EPOLLOUT;
+ }
+ {
+ std::lock_guard<std::recursive_mutex> reactable_lock(reactable->lock_);
+ reactable->on_read_ready_ = std::move(on_read_ready);
+ reactable->on_write_ready_ = std::move(on_write_ready);
+ }
+ epoll_event event = {
+ .events = poll_event_type,
+ {.ptr = reactable}
+ };
+ int modify_fd;
+ RUN_NO_INTR(modify_fd = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, reactable->fd_, &event));
+ CHECK_NE(modify_fd, -1) << __func__ << ": failed: " << strerror(errno);
+}
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/reactor.h b/common/reactor.h
new file mode 100644
index 0000000..27528ce
--- /dev/null
+++ b/common/reactor.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 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 <sys/epoll.h>
+#include <atomic>
+#include <functional>
+#include <list>
+#include <mutex>
+#include <thread>
+
+#include "common/utils.h"
+
+namespace bluetooth {
+namespace common {
+
+// Format of closure to be used in the entire stack
+using Closure = std::function<void()>;
+
+// A simple implementation of reactor-style looper.
+// When a reactor is running, the main loop is polling and blocked until at least one registered reactable is ready to
+// read or write. It will invoke on_read_ready() or on_write_ready(), which is registered with the reactor. Then, it
+// blocks again until ready event.
+class Reactor {
+ public:
+ // An object used for Unregister() and ModifyRegistration()
+ class Reactable;
+
+ // Construct a reactor on the current thread
+ Reactor();
+
+ // Destruct this reactor and release its resources
+ ~Reactor();
+
+ DISALLOW_COPY_AND_ASSIGN(Reactor);
+
+ // Start the reactor. The current thread will be blocked until Stop() is invoked and handled.
+ void Run();
+
+ // Stop the reactor. Must be invoked from a different thread. Note: all registered reactables will not be unregistered
+ // by Stop(). If the reactor is not running, it will be stopped once it's started.
+ void Stop();
+
+ // Register a reactable fd to this reactor. Returns a pointer to a Reactable. Caller must use this object to
+ // unregister or modify registration. Ownership of the memory space is NOT transferred to user.
+ Reactable* Register(int fd, Closure on_read_ready, Closure on_write_ready);
+
+ // Unregister a reactable from this reactor
+ void Unregister(Reactable* reactable);
+
+ // Modify the registration for a reactable with given reactable
+ void ModifyRegistration(Reactable* reactable, Closure on_read_ready, Closure on_write_ready);
+
+ private:
+ mutable std::mutex mutex_;
+ int epoll_fd_;
+ int control_fd_;
+ std::atomic<bool> is_running_;
+ std::list<Reactable*> invalidation_list_;
+ bool reactable_removed_;
+};
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/reactor_unittest.cc b/common/reactor_unittest.cc
new file mode 100644
index 0000000..5c7e90e
--- /dev/null
+++ b/common/reactor_unittest.cc
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "reactor.h"
+
+#include <sys/eventfd.h>
+#include <chrono>
+#include <future>
+#include <thread>
+
+#include "base/logging.h"
+#include "gtest/gtest.h"
+
+namespace bluetooth {
+namespace common {
+namespace {
+
+constexpr int kReadReadyValue = 100;
+
+std::promise<int>* g_promise;
+
+class ReactorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ g_promise = new std::promise<int>;
+ reactor_ = new Reactor;
+ }
+
+ void TearDown() override {
+ delete g_promise;
+ g_promise = nullptr;
+ delete reactor_;
+ reactor_ = nullptr;
+ }
+
+ Reactor* reactor_;
+};
+
+class SampleReactable {
+ public:
+ SampleReactable() : fd_(eventfd(0, EFD_NONBLOCK)) {
+ EXPECT_NE(fd_, 0);
+ }
+
+ ~SampleReactable() {
+ close(fd_);
+ }
+
+ void OnReadReady() {}
+
+ void OnWriteReady() {}
+
+ int fd_;
+};
+
+class FakeReactable {
+ public:
+ enum EventFdValue {
+ kSetPromise = 1,
+ kRegisterSampleReactable,
+ kUnregisterSampleReactable,
+ kSampleOutputValue,
+ };
+ FakeReactable() : fd_(eventfd(0, 0)), reactor_(nullptr) {
+ EXPECT_NE(fd_, 0);
+ }
+
+ FakeReactable(Reactor* reactor) : fd_(eventfd(0, 0)), reactor_(reactor) {
+ EXPECT_NE(fd_, 0);
+ }
+
+ ~FakeReactable() {
+ close(fd_);
+ }
+
+ void OnReadReady() {
+ uint64_t value = 0;
+ auto read_result = eventfd_read(fd_, &value);
+ EXPECT_EQ(read_result, 0);
+ if (value == kSetPromise && g_promise != nullptr) {
+ g_promise->set_value(kReadReadyValue);
+ }
+ if (value == kRegisterSampleReactable) {
+ reactable_ = reactor_->Register(sample_reactable_.fd_, [this] { this->sample_reactable_.OnReadReady(); },
+ [this] { this->sample_reactable_.OnWriteReady(); });
+ g_promise->set_value(kReadReadyValue);
+ }
+ if (value == kUnregisterSampleReactable) {
+ reactor_->Unregister(reactable_);
+ g_promise->set_value(kReadReadyValue);
+ }
+ }
+
+ void OnWriteReady() {
+ auto write_result = eventfd_write(fd_, output_data_);
+ output_data_ = 0;
+ EXPECT_EQ(write_result, 0);
+ }
+
+ SampleReactable sample_reactable_;
+ Reactor::Reactable* reactable_ = nullptr;
+ int fd_;
+
+ private:
+ Reactor* reactor_;
+ uint64_t output_data_ = kSampleOutputValue;
+};
+
+TEST_F(ReactorTest, start_and_stop) {
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ reactor_->Stop();
+ reactor_thread.join();
+}
+
+TEST_F(ReactorTest, stop_and_start) {
+ auto reactor_thread = std::thread(&Reactor::Stop, reactor_);
+ auto another_thread = std::thread(&Reactor::Run, reactor_);
+ reactor_thread.join();
+ another_thread.join();
+}
+
+TEST_F(ReactorTest, stop_multi_times) {
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ for (int i = 0; i < 5; i++) {
+ reactor_->Stop();
+ }
+ reactor_thread.join();
+}
+
+TEST_F(ReactorTest, cold_register_only) {
+ FakeReactable fake_reactable;
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+
+ reactor_->Unregister(reactable);
+}
+
+TEST_F(ReactorTest, cold_register) {
+ FakeReactable fake_reactable;
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ auto future = g_promise->get_future();
+
+ auto write_result = eventfd_write(fake_reactable.fd_, FakeReactable::kSetPromise);
+ EXPECT_EQ(write_result, 0);
+ EXPECT_EQ(future.get(), kReadReadyValue);
+ reactor_->Stop();
+ reactor_thread.join();
+ reactor_->Unregister(reactable);
+}
+
+TEST_F(ReactorTest, hot_register_from_different_thread) {
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ auto future = g_promise->get_future();
+
+ FakeReactable fake_reactable;
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+ auto write_result = eventfd_write(fake_reactable.fd_, FakeReactable::kSetPromise);
+ EXPECT_EQ(write_result, 0);
+ EXPECT_EQ(future.get(), kReadReadyValue);
+ reactor_->Stop();
+ reactor_thread.join();
+
+ reactor_->Unregister(reactable);
+}
+
+TEST_F(ReactorTest, hot_unregister_from_different_thread) {
+ FakeReactable fake_reactable;
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ reactor_->Unregister(reactable);
+ auto future = g_promise->get_future();
+
+ auto write_result = eventfd_write(fake_reactable.fd_, FakeReactable::kSetPromise);
+ EXPECT_EQ(write_result, 0);
+ future.wait_for(std::chrono::milliseconds(10));
+ g_promise->set_value(2);
+ EXPECT_EQ(future.get(), 2);
+ reactor_->Stop();
+ reactor_thread.join();
+}
+
+TEST_F(ReactorTest, hot_register_from_same_thread) {
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ auto future = g_promise->get_future();
+
+ FakeReactable fake_reactable(reactor_);
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+ auto write_result = eventfd_write(fake_reactable.fd_, FakeReactable::kRegisterSampleReactable);
+ EXPECT_EQ(write_result, 0);
+ EXPECT_EQ(future.get(), kReadReadyValue);
+ reactor_->Stop();
+ reactor_thread.join();
+
+ reactor_->Unregister(reactable);
+}
+
+TEST_F(ReactorTest, hot_unregister_from_same_thread) {
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ auto future = g_promise->get_future();
+
+ FakeReactable fake_reactable(reactor_);
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+ auto write_result = eventfd_write(fake_reactable.fd_, FakeReactable::kRegisterSampleReactable);
+ EXPECT_EQ(write_result, 0);
+ EXPECT_EQ(future.get(), kReadReadyValue);
+ delete g_promise;
+ g_promise = new std::promise<int>;
+ future = g_promise->get_future();
+ write_result = eventfd_write(fake_reactable.fd_, FakeReactable::kUnregisterSampleReactable);
+ EXPECT_EQ(write_result, 0);
+ EXPECT_EQ(future.get(), kReadReadyValue);
+ reactor_->Stop();
+ reactor_thread.join();
+ LOG(INFO);
+
+ reactor_->Unregister(reactable);
+ LOG(INFO);
+}
+
+TEST_F(ReactorTest, start_and_stop_multi_times) {
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ reactor_->Stop();
+ reactor_thread.join();
+ for (int i = 0; i < 5; i++) {
+ reactor_thread = std::thread(&Reactor::Run, reactor_);
+ reactor_->Stop();
+ reactor_thread.join();
+ }
+}
+
+TEST_F(ReactorTest, on_write_ready) {
+ FakeReactable fake_reactable;
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, nullptr, std::bind(&FakeReactable::OnWriteReady, &fake_reactable));
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ uint64_t value = 0;
+ auto read_result = eventfd_read(fake_reactable.fd_, &value);
+ EXPECT_EQ(read_result, 0);
+ EXPECT_EQ(value, FakeReactable::kSampleOutputValue);
+
+ reactor_->Stop();
+ reactor_thread.join();
+
+ reactor_->Unregister(reactable);
+}
+
+TEST_F(ReactorTest, modify_registration) {
+ FakeReactable fake_reactable;
+ auto* reactable =
+ reactor_->Register(fake_reactable.fd_, std::bind(&FakeReactable::OnReadReady, &fake_reactable), nullptr);
+ reactor_->ModifyRegistration(reactable, nullptr, std::bind(&FakeReactable::OnWriteReady, &fake_reactable));
+ auto reactor_thread = std::thread(&Reactor::Run, reactor_);
+ uint64_t value = 0;
+ auto read_result = eventfd_read(fake_reactable.fd_, &value);
+ EXPECT_EQ(read_result, 0);
+ EXPECT_EQ(value, FakeReactable::kSampleOutputValue);
+
+ reactor_->Stop();
+ reactor_thread.join();
+}
+
+} // namespace
+} // namespace common
+} // namespace bluetooth
diff --git a/common/repeating_alarm.cc b/common/repeating_alarm.cc
new file mode 100644
index 0000000..65c9b3a
--- /dev/null
+++ b/common/repeating_alarm.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "repeating_alarm.h"
+
+#include <sys/timerfd.h>
+#include <cstring>
+
+#include "base/logging.h"
+#include "utils.h"
+
+namespace bluetooth {
+namespace common {
+
+RepeatingAlarm::RepeatingAlarm(Thread* thread)
+ : thread_(thread),
+ fd_(timerfd_create(CLOCK_BOOTTIME_ALARM, 0)) {
+ CHECK_NE(fd_, -1) << __func__ << ": cannot create timerfd: " << strerror(errno);
+
+ token_ = thread_->GetReactor()->Register(fd_, [this] { on_fire(); }, nullptr);
+}
+
+RepeatingAlarm::~RepeatingAlarm() {
+ thread_->GetReactor()->Unregister(token_);
+
+ int close_status;
+ RUN_NO_INTR(close_status = close(fd_));
+ CHECK_NE(close_status, -1) << __func__ << ": cannot close timerfd: " << strerror(errno);
+}
+
+void RepeatingAlarm::Schedule(Closure task, std::chrono::milliseconds period) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ long period_ms = period.count();
+ itimerspec timer_itimerspec{
+ {period_ms / 1000, period_ms % 1000 * 1000000},
+ {period_ms / 1000, period_ms % 1000 * 1000000}
+ };
+ int result = timerfd_settime(fd_, 0, &timer_itimerspec, nullptr);
+ CHECK_EQ(result, 0) << __func__ << ": failed, error=" << strerror(errno);
+
+ task_ = std::move(task);
+}
+
+void RepeatingAlarm::Cancel() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ itimerspec disarm_itimerspec{/* disarm timer */};
+ int result = timerfd_settime(fd_, 0, &disarm_itimerspec, nullptr);
+ CHECK_EQ(result, 0) << __func__ << ": failed, error=" << strerror(errno);
+}
+
+void RepeatingAlarm::on_fire() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ auto task = task_;
+ uint64_t times_invoked;
+ auto bytes_read = read(fd_, ×_invoked, sizeof(uint64_t));
+ lock.unlock();
+ task();
+ CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(uint64_t))) << __func__ << ": failed, error=" << strerror(errno);
+}
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/repeating_alarm.h b/common/repeating_alarm.h
new file mode 100644
index 0000000..f1f6aa1
--- /dev/null
+++ b/common/repeating_alarm.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 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 <functional>
+#include <memory>
+#include <mutex>
+
+#include "common/thread.h"
+#include "common/utils.h"
+
+namespace bluetooth {
+namespace common {
+
+// A repeating alarm for reactor-based thread, implemented by Linux timerfd.
+// When it's constructed, it will register a reactable on the specified thread; when it's destroyed, it will unregister
+// itself from the thread.
+class RepeatingAlarm {
+ public:
+ // Create and register a repeating alarm on given thread
+ explicit RepeatingAlarm(Thread* thread);
+
+ // Unregister this alarm from the thread and release resource
+ ~RepeatingAlarm();
+
+ DISALLOW_COPY_AND_ASSIGN(RepeatingAlarm);
+
+ // Schedule a repeating alarm with given period
+ void Schedule(Closure task, std::chrono::milliseconds period);
+
+ // Cancel the alarm. No-op if it's not armed.
+ void Cancel();
+
+ private:
+ Closure task_;
+ Thread* thread_;
+ int fd_ = 0;
+ Reactor::Reactable* token_;
+ mutable std::mutex mutex_;
+ void on_fire();
+};
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/repeating_alarm_unittest.cc b/common/repeating_alarm_unittest.cc
new file mode 100644
index 0000000..3c0f255
--- /dev/null
+++ b/common/repeating_alarm_unittest.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "repeating_alarm.h"
+
+#include <future>
+
+#include "base/logging.h"
+#include "gtest/gtest.h"
+
+namespace bluetooth {
+namespace common {
+namespace {
+
+constexpr int error_ms = 20;
+
+class RepeatingAlarmTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ thread_ = new Thread("test_thread", Thread::Priority::NORMAL);
+ alarm_ = new RepeatingAlarm(thread_);
+ }
+
+ void TearDown() override {
+ delete alarm_;
+ delete thread_;
+ }
+
+ void VerifyMultipleDelayedTasks(int scheduled_tasks, int task_length_ms, int interval_between_tasks_ms) {
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ auto start_time = std::chrono::steady_clock::now();
+ int counter = 0;
+ alarm_->Schedule(
+ [&counter, &promise, start_time, scheduled_tasks, task_length_ms, interval_between_tasks_ms]() {
+ counter++;
+ auto time_now = std::chrono::steady_clock::now();
+ auto time_delta = time_now - start_time;
+ if (counter == scheduled_tasks) {
+ promise.set_value();
+ }
+ ASSERT_NEAR(time_delta.count(), interval_between_tasks_ms * 1000000 * counter, error_ms * 1000000);
+ std::this_thread::sleep_for(std::chrono::milliseconds(task_length_ms));
+ },
+ std::chrono::milliseconds(interval_between_tasks_ms));
+ future.get();
+ alarm_->Cancel();
+ }
+
+ RepeatingAlarm* alarm_;
+
+ private:
+ Thread* thread_;
+};
+
+TEST_F(RepeatingAlarmTest, cancel_while_not_armed) {
+ alarm_->Cancel();
+}
+
+TEST_F(RepeatingAlarmTest, schedule) {
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ auto before = std::chrono::steady_clock::now();
+ int period_ms = 10;
+ alarm_->Schedule([&promise]() { promise.set_value(); }, std::chrono::milliseconds(period_ms));
+ future.get();
+ alarm_->Cancel();
+ auto after = std::chrono::steady_clock::now();
+ auto duration = after - before;
+ ASSERT_NEAR(duration.count(), period_ms * 1000000, error_ms * 1000000);
+}
+
+TEST_F(RepeatingAlarmTest, cancel_alarm) {
+ alarm_->Schedule([]() { LOG(FATAL) << "Should not happen"; }, std::chrono::milliseconds(1));
+ alarm_->Cancel();
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+}
+
+TEST_F(RepeatingAlarmTest, cancel_alarm_from_callback) {
+ alarm_->Schedule([this]() { this->alarm_->Cancel(); }, std::chrono::milliseconds(1));
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+}
+
+TEST_F(RepeatingAlarmTest, schedule_while_alarm_armed) {
+ alarm_->Schedule([]() { LOG(FATAL) << "Should not happen"; }, std::chrono::milliseconds(1));
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ alarm_->Schedule([&promise]() { promise.set_value(); }, std::chrono::milliseconds(10));
+ future.get();
+ alarm_->Cancel();
+}
+
+TEST_F(RepeatingAlarmTest, delete_while_alarm_armed) {
+ alarm_->Schedule([]() { LOG(FATAL) << "Should not happen"; }, std::chrono::milliseconds(1));
+ delete alarm_;
+ alarm_ = nullptr;
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+}
+
+TEST_F(RepeatingAlarmTest, verify_small) {
+ VerifyMultipleDelayedTasks(100, 1, 10);
+}
+
+TEST_F(RepeatingAlarmTest, verify_large) {
+ VerifyMultipleDelayedTasks(100, 3, 10);
+}
+
+} // namespace
+} // namespace common
+} // namespace bluetooth
diff --git a/common/thread.cc b/common/thread.cc
new file mode 100644
index 0000000..2cb2205
--- /dev/null
+++ b/common/thread.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "common/thread.h"
+
+#include <fcntl.h>
+#include <sys/syscall.h>
+#include <cerrno>
+#include <cstring>
+
+#include "base/logging.h"
+
+namespace bluetooth {
+namespace common {
+
+namespace {
+constexpr int kRealTimeFifoSchedulingPriority = 1;
+}
+
+Thread::Thread(const std::string& name, const Priority priority)
+ : name_(name),
+ reactor_(),
+ running_thread_(&Thread::run, this, priority) {}
+
+void Thread::run(Priority priority) {
+ if (priority == Priority::REAL_TIME) {
+ struct sched_param rt_params = {.sched_priority = kRealTimeFifoSchedulingPriority};
+ auto linux_tid = static_cast<pid_t>(syscall(SYS_gettid));
+ int rc;
+ RUN_NO_INTR(rc = sched_setscheduler(linux_tid, SCHED_FIFO, &rt_params));
+ if (rc != 0) {
+ LOG(ERROR) << __func__ << ": unable to set SCHED_FIFO priority: " << strerror(errno);
+ }
+ }
+ reactor_.Run();
+}
+
+Thread::~Thread() {
+ Stop();
+}
+
+bool Thread::Stop() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ CHECK_NE(std::this_thread::get_id(), running_thread_.get_id());
+
+ if (!running_thread_.joinable()) {
+ return false;
+ }
+ reactor_.Stop();
+ running_thread_.join();
+ return true;
+}
+
+bool Thread::IsSameThread() const {
+ return std::this_thread::get_id() == running_thread_.get_id();
+}
+
+Reactor* Thread::GetReactor() const {
+ return &reactor_;
+}
+
+std::string Thread::GetThreadName() const {
+ return name_;
+}
+
+std::string Thread::ToString() const {
+ return "Thread " + name_;
+}
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/thread.h b/common/thread.h
new file mode 100644
index 0000000..09e6b48
--- /dev/null
+++ b/common/thread.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 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 <mutex>
+#include <string>
+#include <thread>
+
+#include "common/reactor.h"
+#include "common/utils.h"
+
+namespace bluetooth {
+namespace common {
+
+// Reactor-based looper thread implementation. The thread runs immediately after it is constructed, and stops after
+// Stop() is invoked. To assign task to this thread, user needs to register a reactable object to the underlying
+// reactor.
+class Thread {
+ public:
+ // Used by thread constructor. Suggest the priority to the kernel scheduler. Use REAL_TIME if we need (soft) real-time
+ // scheduling guarantee for this thread; use NORMAL if no real-time guarantee is needed to save CPU time slice for
+ // other threads
+ enum class Priority {
+ REAL_TIME,
+ NORMAL,
+ };
+
+ // name: thread name for POSIX systems
+ // priority: priority for kernel scheduler
+ Thread(const std::string& name, Priority priority);
+
+ // Stop and destroy this thread
+ ~Thread();
+
+ DISALLOW_COPY_AND_ASSIGN(Thread);
+
+ // Stop this thread. Must be invoked from another thread. After this thread is stopped, it cannot be started again.
+ bool Stop();
+
+ // Return true if this function is invoked from this thread
+ bool IsSameThread() const;
+
+ // Return the POSIX thread name
+ std::string GetThreadName() const;
+
+ // Return a user-friendly string representation of this thread object
+ std::string ToString() const;
+
+ // Return the pointer of underlying reactor. The ownership is NOT transferred.
+ Reactor* GetReactor() const;
+
+ private:
+ void run(Priority priority);
+ mutable std::mutex mutex_;
+ const std::string name_;
+ mutable Reactor reactor_;
+ std::thread running_thread_;
+};
+
+} // namespace common
+} // namespace bluetooth
diff --git a/common/thread_unittest.cc b/common/thread_unittest.cc
new file mode 100644
index 0000000..678f34d
--- /dev/null
+++ b/common/thread_unittest.cc
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "thread.h"
+
+#include <sys/eventfd.h>
+
+#include "base/logging.h"
+#include "gtest/gtest.h"
+#include "reactor.h"
+
+namespace bluetooth {
+namespace common {
+namespace {
+
+constexpr int kCheckIsSameThread = 1;
+
+class SampleReactable {
+ public:
+ explicit SampleReactable(Thread* thread) : thread_(thread), fd_(eventfd(0, 0)), is_same_thread_checked_(false) {
+ EXPECT_NE(fd_, 0);
+ }
+
+ ~SampleReactable() {
+ close(fd_);
+ }
+
+ void OnReadReady() {
+ EXPECT_TRUE(thread_->IsSameThread());
+ is_same_thread_checked_ = true;
+ uint64_t val;
+ eventfd_read(fd_, &val);
+ }
+
+ bool IsSameThreadCheckDone() {
+ return is_same_thread_checked_;
+ }
+
+ Thread* thread_;
+ int fd_;
+ bool is_same_thread_checked_;
+};
+
+class ThreadTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ thread = new Thread("test", Thread::Priority::NORMAL);
+ }
+
+ void TearDown() override {
+ delete thread;
+ }
+ Thread* thread = nullptr;
+};
+
+TEST_F(ThreadTest, just_stop_no_op) {
+ thread->Stop();
+}
+
+TEST_F(ThreadTest, thread_name) {
+ EXPECT_EQ(thread->GetThreadName(), "test");
+}
+
+TEST_F(ThreadTest, thread_to_string) {
+ EXPECT_NE(thread->ToString().find("test"), std::string::npos);
+}
+
+TEST_F(ThreadTest, not_same_thread) {
+ EXPECT_FALSE(thread->IsSameThread());
+}
+
+TEST_F(ThreadTest, same_thread) {
+ Reactor* reactor = thread->GetReactor();
+ SampleReactable sample_reactable(thread);
+ auto* reactable =
+ reactor->Register(sample_reactable.fd_, std::bind(&SampleReactable::OnReadReady, &sample_reactable), nullptr);
+ int fd = sample_reactable.fd_;
+ int write_result = eventfd_write(fd, kCheckIsSameThread);
+ EXPECT_EQ(write_result, 0);
+ while (!sample_reactable.IsSameThreadCheckDone()) std::this_thread::yield();
+ reactor->Unregister(reactable);
+}
+
+} // namespace
+} // namespace common
+} // namespace bluetooth
diff --git a/common/utils.h b/common/utils.h
new file mode 100644
index 0000000..26e8f23
--- /dev/null
+++ b/common/utils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 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
+
+// A macro to re-try a syscall when it receives EINTR
+#ifndef RUN_NO_INTR
+#define RUN_NO_INTR(fn) \
+ do { \
+ } while ((fn) == -1 && errno == EINTR)
+#endif
+
+// A macro to disallow the copy constructor and operator= functions
+#ifndef DISALLOW_COPY_AND_ASSIGN
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&) = delete; \
+ void operator=(const TypeName&) = delete
+#endif
diff --git a/osi/include/config.h b/osi/include/config.h
index 55adecb..47c3a3e 100644
--- a/osi/include/config.h
+++ b/osi/include/config.h
@@ -49,6 +49,9 @@
// file on the filesystem.
std::unique_ptr<config_t> config_new(const char* filename);
+// Read the checksum from the |filename|
+std::string checksum_read(const char* filename);
+
// Clones |src|, including all of it's sections, keys, and values.
// Returns a new config which is a copy and separated from the original;
// changes to the new config are not reflected in any way in the original.
@@ -133,3 +136,8 @@
// |config_save|, all comments and special formatting in the original file will
// be lost. Neither |config| nor |filename| may be NULL.
bool config_save(const config_t& config, const std::string& filename);
+
+// Saves the encrypted |checksum| of config file to a given |filename| Note
+// that this could be a destructive operation: if |filename| already exists,
+// it will be overwritten.
+bool checksum_save(const std::string& checksum, const std::string& filename);
diff --git a/osi/src/config.cc b/osi/src/config.cc
index e937959..de7e6e6 100644
--- a/osi/src/config.cc
+++ b/osi/src/config.cc
@@ -18,7 +18,7 @@
#include "osi/include/config.h"
-#include <base/files/file_path.h>
+#include <base/files/file_util.h>
#include <base/logging.h>
#include <ctype.h>
#include <errno.h>
@@ -84,6 +84,19 @@
return config;
}
+std::string checksum_read(const char* filename) {
+ base::FilePath path(filename);
+ if (!base::PathExists(path)) {
+ LOG(ERROR) << __func__ << ": unable to locate file '" << filename << "'";
+ return "";
+ }
+ std::string encrypted_hash;
+ if (!base::ReadFileToString(path, &encrypted_hash)) {
+ LOG(ERROR) << __func__ << ": unable to read file '" << filename << "'";
+ }
+ return encrypted_hash;
+}
+
std::unique_ptr<config_t> config_new_clone(const config_t& src) {
std::unique_ptr<config_t> ret = config_new_empty();
@@ -332,6 +345,107 @@
return false;
}
+bool checksum_save(const std::string& checksum, const std::string& filename) {
+ CHECK(!checksum.empty()) << __func__ << ": checksum cannot be empty";
+ CHECK(!filename.empty()) << __func__ << ": filename cannot be empty";
+
+ // Steps to ensure content of config checksum file gets to disk:
+ //
+ // 1) Open and write to temp file (e.g.
+ // bt_config.conf.encrypted-checksum.new). 2) Sync the temp file to disk with
+ // fsync(). 3) Rename temp file to actual config checksum file (e.g.
+ // bt_config.conf.encrypted-checksum).
+ // This ensures atomic update.
+ // 4) Sync directory that has the conf file with fsync().
+ // This ensures directory entries are up-to-date.
+ FILE* fp = nullptr;
+ int dir_fd = -1;
+
+ // Build temp config checksum file based on config checksum file (e.g.
+ // bt_config.conf.encrypted-checksum.new).
+ const std::string temp_filename = filename + ".new";
+ base::FilePath path(temp_filename);
+
+ // Extract directory from file path (e.g. /data/misc/bluedroid).
+ const std::string directoryname = base::FilePath(filename).DirName().value();
+ if (directoryname.empty()) {
+ LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
+ << "': " << strerror(errno);
+ goto error2;
+ }
+
+ dir_fd = open(directoryname.c_str(), O_RDONLY);
+ if (dir_fd < 0) {
+ LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
+ << "': " << strerror(errno);
+ goto error2;
+ }
+
+ if (base::WriteFile(path, checksum.data(), checksum.size()) !=
+ (int)checksum.size()) {
+ LOG(ERROR) << __func__ << ": unable to write file '" << filename.c_str();
+ goto error2;
+ }
+
+ fp = fopen(temp_filename.c_str(), "rb");
+ if (!fp) {
+ LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
+ << "': " << strerror(errno);
+ goto error2;
+ }
+
+ // Sync written temp file out to disk. fsync() is blocking until data makes it
+ // to disk.
+ if (fsync(fileno(fp)) < 0) {
+ LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
+ << "': " << strerror(errno);
+ }
+
+ if (fclose(fp) == EOF) {
+ LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
+ << "': " << strerror(errno);
+ goto error2;
+ }
+ fp = nullptr;
+
+ // Change the file's permissions to Read/Write by User and Group
+ if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
+ -1) {
+ LOG(ERROR) << __func__ << ": unable to change file permissions '"
+ << filename << "': " << strerror(errno);
+ goto error2;
+ }
+
+ // Rename written temp file to the actual config file.
+ if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
+ LOG(ERROR) << __func__ << ": unable to commit file '" << filename
+ << "': " << strerror(errno);
+ goto error2;
+ }
+
+ // This should ensure the directory is updated as well.
+ if (fsync(dir_fd) < 0) {
+ LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
+ << "': " << strerror(errno);
+ }
+
+ if (close(dir_fd) < 0) {
+ LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
+ << "': " << strerror(errno);
+ goto error2;
+ }
+
+ return true;
+
+error2:
+ // This indicates there is a write issue. Unlink as partial data is not
+ // acceptable.
+ unlink(temp_filename.c_str());
+ if (fp) fclose(fp);
+ if (dir_fd != -1) close(dir_fd);
+ return false;
+}
+
static char* trim(char* str) {
while (isspace(*str)) ++str;
diff --git a/osi/test/config_test.cc b/osi/test/config_test.cc
index 58ce818..7cf4db8 100644
--- a/osi/test/config_test.cc
+++ b/osi/test/config_test.cc
@@ -1,3 +1,4 @@
+#include <base/files/file_util.h>
#include <gtest/gtest.h>
#include "AllocationTestHarness.h"
@@ -173,3 +174,24 @@
std::unique_ptr<config_t> config = config_new(CONFIG_FILE);
EXPECT_TRUE(config_save(*config, CONFIG_FILE));
}
+
+TEST_F(ConfigTest, checksum_read) {
+ std::string filename = "/data/misc/bluedroid/test.checksum";
+ std::string checksum = "0x1234";
+ base::FilePath file_path(filename);
+
+ EXPECT_EQ(base::WriteFile(file_path, checksum.data(), checksum.size()),
+ (int)checksum.size());
+
+ EXPECT_EQ(checksum_read(filename.c_str()), checksum.c_str());
+}
+
+TEST_F(ConfigTest, checksum_save) {
+ std::string filename = "/data/misc/bluedroid/test.checksum";
+ std::string checksum = "0x1234";
+ base::FilePath file_path(filename);
+
+ EXPECT_TRUE(checksum_save(checksum, filename));
+
+ EXPECT_TRUE(base::PathExists(file_path));
+}
diff --git a/stack/Android.bp b/stack/Android.bp
index 9f03b39..53076bb 100644
--- a/stack/Android.bp
+++ b/stack/Android.bp
@@ -202,6 +202,7 @@
"test/stack_a2dp_test.cc",
],
shared_libs: [
+ "libcrypto",
"libhidlbase",
"liblog",
"libprotobuf-cpp-lite",
diff --git a/test/gen_coverage.py b/test/gen_coverage.py
index b1c9a2f..3697c37 100755
--- a/test/gen_coverage.py
+++ b/test/gen_coverage.py
@@ -79,6 +79,11 @@
"covered_files": [
"system/bt/vendor_libs/test_vendor_lib/packets",
],
+ }, {
+ "test_name": "bluetooth_test_common",
+ "covered_files": [
+ "system/bt/common",
+ ],
},
]