From bbfc2991c5a482cee06fcfba9d781c204a361991 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Fri, 19 May 2023 19:19:15 -0500 Subject: [PATCH] Started implementing Linux specific platform This is the start of the implementing for the Linux platform. I aim to make this as similar to the Windows platform as possible, although due to the fundamental difference in the two OS's, there will be things that need to be different. As such, I will be using the Windows platform files as a base, and reimplementing the functions with Linux equivalents. I am sure this will have bugs, which by my best attempt will be fixed when found. --- .../platform/implementation/device_info.h | 4 +- internal/platform/implementation/linux/BUILD | 290 ++++++++++++++ .../implementation/linux/atomic_boolean.h | 43 +++ .../linux/atomic_boolean_test.cc | 32 ++ .../implementation/linux/atomic_reference.h | 43 +++ .../linux/atomic_reference_test.cc | 72 ++++ .../implementation/linux/condition_variable.h | 51 +++ .../linux/condition_variable_test.cc | 102 +++++ .../platform/implementation/linux/crypto.cc | 54 +++ .../implementation/linux/crypto_test.cc | 50 +++ .../implementation/linux/device_info.cc | 264 +++++++++++++ .../implementation/linux/device_info.h | 62 +++ .../platform/implementation/linux/executor.cc | 55 +++ .../platform/implementation/linux/executor.h | 50 +++ .../implementation/linux/executor_test.cc | 323 ++++++++++++++++ .../platform/implementation/linux/file.cc | 111 ++++++ internal/platform/implementation/linux/file.h | 60 +++ .../platform/implementation/linux/mutex.h | 69 ++++ .../implementation/linux/mutext_test.cc | 105 +++++ .../platform/implementation/linux/test_data.h | 34 ++ .../implementation/linux/thread_pool.cc | 128 +++++++ .../implementation/linux/thread_pool.h | 72 ++++ .../platform/implementation/linux/utils.cc | 362 ++++++++++++++++++ .../platform/implementation/linux/utils.h | 85 ++++ 24 files changed, 2519 insertions(+), 2 deletions(-) create mode 100644 internal/platform/implementation/linux/BUILD create mode 100644 internal/platform/implementation/linux/atomic_boolean.h create mode 100644 internal/platform/implementation/linux/atomic_boolean_test.cc create mode 100644 internal/platform/implementation/linux/atomic_reference.h create mode 100644 internal/platform/implementation/linux/atomic_reference_test.cc create mode 100644 internal/platform/implementation/linux/condition_variable.h create mode 100644 internal/platform/implementation/linux/condition_variable_test.cc create mode 100644 internal/platform/implementation/linux/crypto.cc create mode 100644 internal/platform/implementation/linux/crypto_test.cc create mode 100644 internal/platform/implementation/linux/device_info.cc create mode 100644 internal/platform/implementation/linux/device_info.h create mode 100644 internal/platform/implementation/linux/executor.cc create mode 100644 internal/platform/implementation/linux/executor.h create mode 100644 internal/platform/implementation/linux/executor_test.cc create mode 100644 internal/platform/implementation/linux/file.cc create mode 100644 internal/platform/implementation/linux/file.h create mode 100644 internal/platform/implementation/linux/mutex.h create mode 100644 internal/platform/implementation/linux/mutext_test.cc create mode 100644 internal/platform/implementation/linux/test_data.h create mode 100644 internal/platform/implementation/linux/thread_pool.cc create mode 100644 internal/platform/implementation/linux/thread_pool.h create mode 100644 internal/platform/implementation/linux/utils.cc create mode 100644 internal/platform/implementation/linux/utils.h diff --git a/internal/platform/implementation/device_info.h b/internal/platform/implementation/device_info.h index 7247fa85..da35de37 100644 --- a/internal/platform/implementation/device_info.h +++ b/internal/platform/implementation/device_info.h @@ -28,8 +28,8 @@ namespace api { class DeviceInfo { public: enum class ScreenStatus { kUndetermined = 0, kLocked, kUnlocked }; - enum class DeviceType { kUnknown = 0, kPhone, kTablet, kLaptop }; - enum class OsType { kUnknown = 0, kAndroid, kChromeOs, kIos, kWindows }; + enum class DeviceType { kUnknown = 0, kPhone, kTablet, kLaptop, kDesktop }; + enum class OsType { kUnknown = 0, kAndroid, kChromeOs, kIos, kWindows, kLinux }; virtual ~DeviceInfo() = default; diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD new file mode 100644 index 00000000..801ab31f --- /dev/null +++ b/internal/platform/implementation/linux/BUILD @@ -0,0 +1,290 @@ +# Copyright 2020 Google LLC +# +# 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 +# +# https://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. +licenses(["notice"]) + +cc_library( + name = "types", + srcs = [ + "device_info.cc", + "log_message.cc", + "timer.cc", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "bluetooth_adapter.h", + "condition_variable.h", + "device_info.h", + "executor.h", + "future.h", + "input_file.h", + "listenable_future.h", + "log_message.h", + "mutex.h", + "output_file.h", + "preferences_manager.h", + "scheduled_executor.h", + "settable_future.h", + "submittable_executor.h", + "timer.h", + "utils.h", + ], + defines = ["_SILENCE_CLANG_COROUTINE_MESSAGE"], + visibility = ["//visibility:public"], + deps = [ + ":comm", + "//base", + "//base:stringprintf", + "//internal/base:bluetooth_address", + "//internal/platform:base", + "//internal/platform:logging", + "//internal/platform:types", + "//internal/platform:uuid", + "//internal/platform/implementation:types", + "//strings:strappendv", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + "@nlohmann_json//:json", + ], +) + +cc_library( + name = "comm", + hdrs = [ + "ble.h", + "ble_gatt_client.h", + "ble_gatt_server.h", + "ble_medium.h", + "ble_peripheral.h", + "ble_socket.h", + "ble_v2.h", + "ble_v2_peripheral.h", + "ble_v2_server_socket.h", + "ble_v2_socket.h", + "bluetooth_adapter.h", + "bluetooth_classic.h", + "bluetooth_classic_device.h", + "bluetooth_classic_medium.h", + "bluetooth_classic_server_socket.h", + "bluetooth_classic_socket.h", + "bluetooth_pairing.h", + "condition_variable.h", + "executor.h", + "file.h", + "file_path.h", + "http_loader.h", + "mutex.h", + "scheduled_executor.h", + "server_sync.h", + "submittable_executor.h", + "thread_pool.h", + "webrtc.h", + "wifi.h", + "wifi_direct.h", + "wifi_hotspot.h", + "wifi_lan.h", + ], + visibility = ["//visibility:private"], + deps = [ + "//internal/platform:base", + "//internal/platform:comm", + "//internal/platform:types", + "//internal/platform:uuid", + "//internal/platform/implementation:comm", + "//internal/platform/implementation:types", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + ], +) + +cc_library( + name = "crypto", + srcs = [ + "crypto.cc", + ], + visibility = ["//visibility:private"], + deps = [ + "//internal/platform:base", + "//internal/platform/implementation:types", + "@boringssl//:crypto", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "linux", + srcs = [ + "ble_gatt_client.cc", + "ble_gatt_server.cc", + "ble_medium.cc", + "ble_socket.cc", + "ble_v2.cc", + "ble_v2_peripheral.cc", + "ble_v2_server_socket.cc", + "ble_v2_socket.cc", + "bluetooth_adapter.cc", + "bluetooth_classic_device.cc", + "bluetooth_classic_medium.cc", + "bluetooth_classic_server_socket.cc", + "bluetooth_classic_socket.cc", + "bluetooth_pairing.cc", + "executor.cc", + "file.cc", + "file_path.cc", + "http_loader.cc", + "platform.cc", + "preferences_manager.cc", + "preferences_repository.cc", + "preferences_repository.h", + "scheduled_executor.cc", + "submittable_executor.cc", + "system_clock.cc", + "thread_pool.cc", + "utils.cc", + "webrtc.cc", + "wifi_direct_medium.cc", + "wifi_direct_server_socket.cc", + "wifi_direct_socket.cc", + "wifi_hotspot_medium.cc", + "wifi_hotspot_server_socket.cc", + "wifi_hotspot_socket.cc", + "wifi_lan_medium.cc", + "wifi_lan_server_socket.cc", + "wifi_lan_socket.cc", + "wifi_medium.cc", + ], + defines = ["_SILENCE_CLANG_COROUTINE_MESSAGE"], + visibility = [ + "//connections:__subpackages__", + "//fastpair:__subpackages__", + "//location/nearby:__subpackages__", + "//presence:__subpackages__", + ], + deps = [ + ":comm", + ":crypto", # build_cleaner: keep + ":types", + "//internal/flags:nearby_flags", + "//internal/platform:base", + "//internal/platform:cancellation_flag", + "//internal/platform:comm", + "//internal/platform:logging", + "//internal/platform:types", + "//internal/platform:uuid", + "//internal/platform/flags:platform_flags", + "//internal/platform/implementation:comm", + "//internal/platform/implementation:platform", + "//internal/platform/implementation:types", + "//internal/platform/implementation/shared:count_down_latch", + "//internal/platform/implementation/shared:file", + "//third_party/webrtc/files/stable/webrtc/api/task_queue:default_task_queue_factory", + "//third_party/webrtc/files/stable/webrtc/rtc_base:checks", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@nlohmann_json//:json", + ], +) + +cc_library( + name = "test_utils", + srcs = [ + "test_utils.cc", + ], + hdrs = [ + "test_data.h", + "test_utils.h", + ], + visibility = [ + "//visibility:private", # Only private by automation, not intent. Owner may accept CLs adding visibility. See go/scheuklappen#explicit-private. + ], + deps = [ + "//internal/platform:base", + "@nlohmann_json//:json", + ], +) + +cc_test( + name = "impl_test", + size = "small", + srcs = [ + "atomic_boolean_test.cc", + "atomic_reference_test.cc", + "ble_gatt_server_test.cc", + "ble_medium_test.cc", + "ble_v2_peripheral_test.cc", + "ble_v2_test.cc", + "bluetooth_adapter_test.cc", + "count_down_latch_test.cc", + "crypto_test.cc", + "device_info_test.cc", + "executor_test.cc", + "file_path_test.cc", + "http_loader_test.cc", + "preferences_manager_test.cc", + "preferences_repository_test.cc", + "scheduled_executor_test.cc", + "submittable_executor_test.cc", + "thread_pool_test.cc", + "timer_test.cc", + "utils_test.cc", + "webrtc_test.cc", + ], + tags = ["notap"], + deps = [ + ":comm", + ":crypto", + ":test_utils", + ":types", + ":windows", + "//internal/platform:base", + "//internal/platform:logging", + "//internal/platform/implementation:comm", + "//internal/platform/implementation:platform", + "//internal/platform/implementation:types", + "//internal/platform/implementation/shared:count_down_latch", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest_main", + "@nlohmann_json//:json", + ], +) diff --git a/internal/platform/implementation/linux/atomic_boolean.h b/internal/platform/implementation/linux/atomic_boolean.h new file mode 100644 index 00000000..8147dabb --- /dev/null +++ b/internal/platform/implementation/linux/atomic_boolean.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_ +#define PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_ + +#include + +#include "internal/platform/implementation/atomic_boolean.h" + +namespace nearby { +namespace linux { + +// A boolean value that may be updated atomically. +class AtomicBoolean : public api::AtomicBoolean { + public: + ~AtomicBoolean() override = default; + + // Atomically read and return current value. + bool Get() const override { return atomic_boolean_; }; + + // Atomically exchange original value with a new one. Return previous value. + bool Set(bool value) override { return atomic_boolean_.exchange(value); }; + + private: + std::atomic_bool atomic_boolean_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_ \ No newline at end of file diff --git a/internal/platform/implementation/linux/atomic_boolean_test.cc b/internal/platform/implementation/linux/atomic_boolean_test.cc new file mode 100644 index 00000000..ecd5e41a --- /dev/null +++ b/internal/platform/implementation/linux/atomic_boolean_test.cc @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/atomic_boolean.h" + +#include "gtest/gtest.h" + +TEST(atomic_boolean, SuccessfulCreation) { + // Arrange + nearby::linux::AtomicBoolean atomicBoolean; + bool oldValue = true; + bool result = false; + + // Act + oldValue = atomicBoolean.Set(true); + result = atomicBoolean.Get(); + + // Assert + EXPECT_TRUE(result); + EXPECT_FALSE(oldValue); +} \ No newline at end of file diff --git a/internal/platform/implementation/linux/atomic_reference.h b/internal/platform/implementation/linux/atomic_reference.h new file mode 100644 index 00000000..ec7793dd --- /dev/null +++ b/internal/platform/implementation/linux/atomic_reference.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_ATOMIC_REFERENCE_H_ +#define PLATFORM_IMPL_LINUX_ATOMIC_REFERENCE_H_ + +#include + +#include "internal/platform/implementation/atomic_reference.h" + +namespace nearby { +namespace linux { + +// Type that allows 32-bit atomic reads and writes. +class AtomicUint32 : public api::AtomicUint32 { + public: + ~AtomicUint32() override = default; + + // Atomically reads and returns stored value. + std::uint32_t Get() const override { return atomic_uint32_; }; + + // Atomically stores value. + void Set(std::uint32_t value) override { atomic_uint32_ = value; } + + private: + std::atomic_int32_t atomic_uint32_ = 0; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_ATOMIC_REFERENCE_H_ \ No newline at end of file diff --git a/internal/platform/implementation/linux/atomic_reference_test.cc b/internal/platform/implementation/linux/atomic_reference_test.cc new file mode 100644 index 00000000..8e69a64a --- /dev/null +++ b/internal/platform/implementation/linux/atomic_reference_test.cc @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/atomic_reference.h" + +#include "gtest/gtest.h" + +TEST(atomic_reference, SuccessfulCreation) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = UINT32_MAX; + const uint32_t expected = 0; + + // Act + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(atomic_reference, SuccessfulMaxSet) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = 0; + const uint32_t expected = UINT32_MAX; + + // Act + atomicUint32.Set(UINT32_MAX); + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(atomic_reference, SuccessfulMinSet) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = UINT32_MAX; + const uint32_t expected = 0; + + // Act + atomicUint32.Set(0); + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(atomic_reference, SetNegativeOneReturnsMAXUINT) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = 0; + const uint32_t expected = UINT32_MAX; + + // Act + atomicUint32.Set(-1); // Try Set -1, should actually store UINT32_MAX + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} \ No newline at end of file diff --git a/internal/platform/implementation/linux/condition_variable.h b/internal/platform/implementation/linux/condition_variable.h new file mode 100644 index 00000000..3661b3a7 --- /dev/null +++ b/internal/platform/implementation/linux/condition_variable.h @@ -0,0 +1,51 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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. +#ifndef PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_ + +#include // NOLINT + +#include "internal/platform/implementation/condition_variable.h" +#include "internal/platform/implementation/linux/mutex.h" +#include "internal/platform/mutex.h" + +namespace nearby { +namespace linux { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(api::Mutex* mutex) + : mutex_(&(static_cast(mutex))->mutex_) {} + ~ConditionVariable() override = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + + Exception Wait(absl::Duration timeout) override { + cond_var_.WaitWithTimeout(mutex_, timeout); + return {Exception::kSuccess}; + } + + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex* mutex_; + absl::CondVar cond_var_; +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_ diff --git a/internal/platform/implementation/linux/condition_variable_test.cc b/internal/platform/implementation/linux/condition_variable_test.cc new file mode 100644 index 00000000..a45aae8e --- /dev/null +++ b/internal/platform/implementation/linux/condition_variable_test.cc @@ -0,0 +1,102 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/condition_variable.h" + +#include // NOLINT + +#include "absl/time/clock.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/mutex.h" + +#include "gtest/gtest.h" + +class ConditionVariableTests : public testing::Test { + public: + class ConditionVariableTest { + public: + ConditionVariableTest() {} + + std::future WaitForEvent(bool timedWait, // NOLINT + const absl::Duration* timeout) { + return std::async( + std::launch::async, + [this, timedWait, timeout]() mutable -> bool { + + if (timedWait == true) { + auto result = this->condition_variable_actual_.Wait(*timeout); + if (result.value == nearby::Exception::kSuccess) { + return true; + } else { + return false; + } + } else { + this->condition_variable_actual_.Wait(); + } + return true; + }); + } + + void PostEvent() { + absl::MutexLock(&mutex_actual_.GetMutex()); + condition_variable_actual_.Notify(); + } + + private: + nearby::linux::Mutex mutex_actual_ = + nearby::linux::Mutex(nearby::linux::Mutex::Mode::kRegular); + nearby::linux::Mutex& mutex_ = mutex_actual_; + nearby::linux::ConditionVariable condition_variable_actual_ = + nearby::linux::ConditionVariable(&mutex_); + nearby::linux::ConditionVariable& condition_variable_ = + condition_variable_actual_; + }; +}; + +TEST_F(ConditionVariableTests, SuccessfulCreation) { + // Arrange + ConditionVariableTest conditionVariableTest; + + auto result = conditionVariableTest.WaitForEvent(false, nullptr); + + sleep(1); + + // Act + conditionVariableTest.PostEvent(); + + // Assert + ASSERT_TRUE(result.get()); +} + +TEST_F(ConditionVariableTests, TimedCreation) { + // Arrange + ConditionVariableTest conditionVariableTest; + const absl::Duration duration = absl::Milliseconds(100); + + // Act + auto result = conditionVariableTest.WaitForEvent(true, &duration); + + // Assert + ASSERT_FALSE(result.get()); // Timed out + + // Act + result = conditionVariableTest.WaitForEvent(true, &duration); + + sleep(1); + + conditionVariableTest.PostEvent(); + + // Assert + ASSERT_TRUE(result.get()); // Didn't timeout +} diff --git a/internal/platform/implementation/linux/crypto.cc b/internal/platform/implementation/linux/crypto.cc new file mode 100644 index 00000000..bdd1d926 --- /dev/null +++ b/internal/platform/implementation/linux/crypto.cc @@ -0,0 +1,54 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/crypto.h" + +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" + +#include + +// Function implementations for platform/implementation/crypto.h. + +namespace nearby { + +// Initialize global crypto state. +void Crypto::Init() {} + +static ByteArray Hash(absl::string_view input, const EVP_MD* algo) { + unsigned int md_out_size = EVP_MAX_MD_SIZE; + uint8_t digest_buffer[EVP_MAX_MD_SIZE]; + if (input.empty()) return {}; + + if (!EVP_Digest(input.data(), input.size(), digest_buffer, &md_out_size, algo, + nullptr)) + return {}; + + return ByteArray{reinterpret_cast(digest_buffer), md_out_size}; +} + +// Return MD5 hash of input. +ByteArray Crypto::Md5(absl::string_view input) { + return Hash(input, EVP_md5()); +} + +// Return SHA256 hash of input. +ByteArray Crypto::Sha256(absl::string_view input) { + return Hash(input, EVP_sha256()); +} + +} // namespace nearby diff --git a/internal/platform/implementation/linux/crypto_test.cc b/internal/platform/implementation/linux/crypto_test.cc new file mode 100644 index 00000000..551616ee --- /dev/null +++ b/internal/platform/implementation/linux/crypto_test.cc @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/crypto.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace { + +TEST(CryptoTest, Md5Hash) { + const std::string input{"Hello Nearby Connection"}; + const ByteArray expected_md5( + "\x94\xa3\xbe\xc1\x8d\x30\xe3\x24\x5f\xa1\x4c\xee\xe7\x52\xe9\x36"); + ByteArray md5_hash = Crypto::Md5(input); + EXPECT_EQ(md5_hash, expected_md5); +} + +TEST(CryptoTest, Md5HashOnEmptyInput) { + EXPECT_EQ(Crypto::Md5(""), ByteArray{}); +} + +TEST(CryptoTest, Sha256Hash) { + const std::string input("Hello Nearby Connection"); + const ByteArray expected_sha256( + "\xb4\x24\xd3\xc0\x58\x12\x9a\x42\xcb\x81\xa0\x4b\x6e\x9d\xfe\x45\x45\x9f" + "\x15\xf7\xc0\xa9\x32\x2f\xfb\x9\x45\xf0\xf9\xbe\x75\xb"); + ByteArray sha256_hash = Crypto::Sha256(input); + EXPECT_EQ(sha256_hash, expected_sha256); +} + +TEST(CryptoTest, Sha256HashOnEmptyInput) { + EXPECT_EQ(Crypto::Sha256(""), ByteArray{}); +} + +} // namespace +} // namespace nearby \ No newline at end of file diff --git a/internal/platform/implementation/linux/device_info.cc b/internal/platform/implementation/linux/device_info.cc new file mode 100644 index 00000000..b05aeda8 --- /dev/null +++ b/internal/platform/implementation/linux/device_info.cc @@ -0,0 +1,264 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/device_info.h" + +// For Linux device specific stuff +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "internal/base/bluetooth_address.h" +#include "internal/platform/implementation/device_info.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +std::optional DeviceInfo::GetOsDeviceName() const { + // As I know of, there is no way to fully determine the length of the Hostname (device nickname) + + // https://stackoverflow.com/a/18851841 Max Host name length is limited to 64 bytes + char *device_name = new char[HOST_NAME_MAX]; + + // Linux hostnames are limited to UTF-8, 8-bit wide characters, so we need to convert + // that to the return type for a UTF-16 string, 16-bits wide. + if (gethostname(device_name, HOST_NAME_MAX) == 0) { + std::string name(device_name); + delete[] device_name; + return std::wstring_convert, char16_t>().from_bytes(name); + } + + NEARBY_LOGS(ERROR) << ": Failed to get device name, error:" + << strerror(errno); + delete[] device_name; + return std::nullopt; +} + +api::DeviceInfo::DeviceType DeviceInfo::GetDeviceType() const { + // While there is no *official* way to detect if a Linux system is a laptop or not, we can try to + // find its chassis type and make a very educated guess as to what the user is using. + // See https://superuser.com/a/1107191 + std::fstream chassis_type("/sys/class/dmi/id/chassis_type", std::ios::binary | std::ios::in); + + char chartype = 0; + + chassis_type.get(chartype); + + // The code is stored as text in the file, in order to get a correct + // representation in decimal, we can subtract 48 from it. + int type = chartype - 48; + + switch (type) { + case 3: + case 4: + // Type 3 and 4 are both types of desktops + return api::DeviceInfo::DeviceType::kDesktop; + case 6: + case 7: + // Type 6 and 7 are both Towers + return api::DeviceInfo::DeviceType::kDesktop; + case 9: + case 10: + // Type 9 and 10 are laptop esc, laptop and notebook + return api::DeviceInfo::DeviceType::kLaptop; + case 11: + case 30: + // Type 11 is labled as "Hand Held" + return api::DeviceInfo::DeviceType::kTablet; + case 31: + case 32: + // Type 31 and 32 are Convertable / Detatchable + return api::DeviceInfo::DeviceType::kLaptop; + default: + return api::DeviceInfo::DeviceType::kUnknown; + } +} + +api::DeviceInfo::OsType DeviceInfo::GetOsType() const { + return api::DeviceInfo::OsType::kLinux; +} + +std::optional DeviceInfo::GetFullName() const { + // We can use the C function getpwnam() to get information in a passwd database. + // The users full name is optionally in the passwd->pw_gecos member of the passwd struct. + + struct passwd *full_user_data = getpwnam(getlogin()); + std::u16string u16str; + + if (full_user_data == nullptr) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error retrieving locally authenticated user."; + return std::nullopt; + } + + // The GECOS field is optional and used for information purposes only. + // Usually it would contain the full name of the user. + // See https://man7.org/linux/man-pages/man5/passwd.5.html + u16str = *full_user_data->pw_gecos; + + if (u16str.empty()) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error retrieving full name of user. (GECOS field empty)"; + return std::nullopt; + } + + return u16str; +} + +std::optional DeviceInfo::GetGivenName() const { + std::optional user_full_name = GetFullName(); + + if (user_full_name == std::nullopt) { + NEARBY_LOGS(ERROR) << __func__ << ": Error retrieving first name of user."; + return std::nullopt; + } + + if (user_full_name->empty()) { + NEARBY_LOGS(ERROR) + << __func__ << ": Error unboxing string value for first name of user."; + return std::nullopt; + } + + std::u16string::size_type seperator = user_full_name->find_first_of(u" "); + + // If the Full Name doesn't contain a space, then we assume the whole thing is a first name + if (seperator == std::u16string::npos) { + return user_full_name.value(); + } + + return user_full_name->substr(*user_full_name->begin(), seperator); +} + +std::optional DeviceInfo::GetLastName() const { + std::optional user_full_name = GetFullName(); + + if (user_full_name == std::nullopt) { + NEARBY_LOGS(ERROR) << __func__ << ": Error retrieving last name of user."; + return std::nullopt; + } + + if (user_full_name->empty()) { + NEARBY_LOGS(ERROR) + << __func__ << ": Error unboxing string value for last name of user."; + return std::nullopt; + } + std::u16string::size_type seperator = user_full_name->find_first_of(u" "); + + // If the Full Name doesn't contain a space, then we assume there is no last name + if (seperator == std::u16string::npos) { + return std::nullopt; + } + + return user_full_name->substr(*user_full_name->begin(), seperator); +} + +std::optional DeviceInfo::GetProfileUserName() const { + + std::string user_name = getlogin(); + + + if (user_name.empty()) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error retrieving account name of user." + << strerror(errno); + return std::nullopt; + } + + return user_name; +} + +std::optional DeviceInfo::GetDownloadPath() const { + // This assumes xdg-user-dir is installed on the target system, which is better + // than guessing completely. This is 99.9% of the time going to be installed + // automatically by the Distro/DE + FILE *fp = popen("xdg-user-dir DOWNLOAD", "r"); + char *path = new char[512]; + if (fp == nullptr) { + pclose(fp); + delete fp; + delete[] path; + return std::nullopt; + } + + while (fgets(path, sizeof(path), fp) != nullptr) { + + } + int exit_status = WEXITSTATUS(fclose(fp)); + if (exit_status != 0) { + delete fp; + delete[] path; + return std::nullopt; + } + delete fp; + std::filesystem::path fpath = std::filesystem::path(path); + delete[] path; + return fpath; +} + +std::optional DeviceInfo::GetLocalAppDataPath() const { + //TODO: Figure out how to get cross distro path + + return std::nullopt; +} + +std::optional DeviceInfo::GetCommonAppDataPath() const { + //TODO: Figure out how to get cross distro path + + return std::nullopt; +} + +std::optional DeviceInfo::GetTemporaryPath() const { + return std::filesystem::temp_directory_path(); +} + +std::optional DeviceInfo::GetLogPath() const { + //TODO: Figure out how to get cross distro path + + return std::nullopt; +} + +std::optional DeviceInfo::GetCrashDumpPath() const { + //TODO: Figure out how to get cross distro path + + return std::nullopt; +} + +bool DeviceInfo::IsScreenLocked() const { + // TODO: Determine if it's actually possible to detect Linux screen lock cross DE, WM, WE, etc. + return false; +} + +void DeviceInfo::RegisterScreenLockedListener(absl::string_view listener_name, + std::function callback) { + //TODO: Figure out what this does + // Assuming it has to do with detecting if the screen is locked, that may not be possible. +} + +void DeviceInfo::UnregisterScreenLockedListener(absl::string_view listener_name) { + //TODO: Figure out what this does too + // Assuming it has to do with detecting if the screen is locked, that may not be possible. +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/device_info.h b/internal/platform/implementation/linux/device_info.h new file mode 100644 index 00000000..55e80eda --- /dev/null +++ b/internal/platform/implementation/linux/device_info.h @@ -0,0 +1,62 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ +#define PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ + +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/device_info.h" + +namespace nearby { +namespace linux { + +class DeviceInfo : public api::DeviceInfo { + public: + ~DeviceInfo() override = default; + + std::optional GetOsDeviceName() const override; + api::DeviceInfo::DeviceType GetDeviceType() const override; + api::DeviceInfo::OsType GetOsType() const override; + std::optional GetFullName() const override; + std::optional GetGivenName() const override; + std::optional GetLastName() const override; + std::optional GetProfileUserName() const override; + + std::optional GetDownloadPath() const override; + std::optional GetLocalAppDataPath() const override; + std::optional GetCommonAppDataPath() const override; + std::optional GetTemporaryPath() const override; + std::optional GetLogPath() const override; + std::optional GetCrashDumpPath() const override; + + bool IsScreenLocked() const override; + void RegisterScreenLockedListener( + absl::string_view listener_name, + std::function callback) override; + void UnregisterScreenLockedListener(absl::string_view listener_name) override; + absl::flat_hash_map> + screen_locked_listeners_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ \ No newline at end of file diff --git a/internal/platform/implementation/linux/executor.cc b/internal/platform/implementation/linux/executor.cc new file mode 100644 index 00000000..37dfcd9c --- /dev/null +++ b/internal/platform/implementation/linux/executor.cc @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/executor.h" + +#include + +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +Executor::Executor() : Executor(1) {} + +Executor::Executor(int32_t max_concurrency) + : max_concurrency_(max_concurrency) { + assert(max_concurrency_ >= 1); + thread_pool_ = linux::ThreadPool::Create(max_concurrency); + assert(thread_pool_ != nullptr); +} + +void Executor::Execute(Runnable&& runnable) { + if (shut_down_) { + NEARBY_LOGS(VERBOSE) << "Warning: " << __func__ + << ": Attempt to execute on a shut down pool."; + return; + } + + if (runnable == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Runnable was null."; + return; + } + + thread_pool_->Run(std::move(runnable)); +} + +void Executor::Shutdown() { + shut_down_ = true; + thread_pool_->ShutDown(); + thread_pool_ = nullptr; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/executor.h b/internal/platform/implementation/linux/executor.h new file mode 100644 index 00000000..bd96ef06 --- /dev/null +++ b/internal/platform/implementation/linux/executor.h @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_EXECUTOR_H_ + +#include +#include + +#include "internal/platform/implementation/executor.h" +#include "internal/platform/implementation/linux/thread_pool.h" + +namespace nearby { +namespace linux { + +// This abstract class is the superclass of all classes representing an +// Executor. +class Executor : public api::Executor { + public: + Executor(); + explicit Executor(int max_concurrency); + + // Before returning from destructor, executor must wait for all pending + // jobs to finish. + ~Executor() override = default; + + void Execute(Runnable&& runnable) override; + void Shutdown() override; + + private: + std::unique_ptr thread_pool_ = nullptr; + std::atomic shut_down_ = false; + int32_t max_concurrency_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_EXECUTOR_H_ diff --git a/internal/platform/implementation/linux/executor_test.cc b/internal/platform/implementation/linux/executor_test.cc new file mode 100644 index 00000000..858c7fc2 --- /dev/null +++ b/internal/platform/implementation/linux/executor_test.cc @@ -0,0 +1,323 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/executor.h" + +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "absl/time/time.h" +#include "internal/platform/implementation/linux/test_data.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); + +TEST(ExecutorTests, SingleThreadedExecutorSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto executor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + executor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ExecutorTests, SingleThreadedExecutorAfterShutdownFails) { + // Arrange + std::string expected(""); + + std::unique_ptr executor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + executor->Shutdown(); + + // Act + executor->Execute([&output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(*output.get(), expected); +} + +TEST(ExecutorTests, SingleThreadedExecutorExecuteNullSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto executor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + executor->Execute(nullptr); + executor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + executor->Execute(nullptr); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ExecutorTests, SingleThreadedExecutorMultipleTasksSucceeds) { + absl::BlockingCounter block_count(5); + + // Arrange + std::string expected(RUNNABLE_ALL_TEXT.c_str()); + + auto executor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + auto parent_thread = std::this_thread::get_id(); + + // Act + for (int index = 0; index < 5; index++) { + executor->Execute([&, index]() { + threadIds->push_back(std::this_thread::get_id()); + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s%d, ", RUNNABLE_TEXT.c_str(), index); + output.append(std::string(buffer)); + block_count.DecrementCount(); + }); + } + + block_count.Wait(); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 5); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), parent_thread); + // We should've run all runnables on the worker thread + auto workerThreadId = threadIds->at(0); + for (int index = 0; index < threadIds->size(); index++) { + ASSERT_EQ(threadIds->at(index), workerThreadId); + } + + // We should of run them in the order submitted + ASSERT_EQ(output, expected); +} + +TEST(ExecutorTests, MultiThreadedExecutorSingleTaskSucceeds) { + absl::Notification notification; + + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto executor = std::make_unique(2); + + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + std::shared_ptr output = std::make_shared(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + executor->Execute([&, output]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run the task + ASSERT_EQ(*output.get(), expected); +} + +TEST(ExecutorTests, MultiThreadedExecutorMultipleTasksSucceeds) { + absl::BlockingCounter block_count(5); + + // Arrange + auto executor = std::make_unique(2); + + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + std::shared_ptr output = std::make_shared(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + for (int index = 0; index < 5; index++) { + executor->Execute([&, index]() { + threadIds->push_back(std::this_thread::get_id()); + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s %d, ", RUNNABLE_TEXT.c_str(), index); + output->append(std::string(buffer)); + block_count.DecrementCount(); + }); + } + + block_count.Wait(); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 6); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); +} + +TEST(ExecutorTests, MultiThreadedExecutorSingleTaskAfterShutdownFails) { + // Arrange + std::string expected(""); + + auto executor = std::make_unique(2); + + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + std::shared_ptr output = std::make_shared(); + + threadIds->push_back(std::this_thread::get_id()); + + executor->Shutdown(); + + // Act + executor->Execute([output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run the task + ASSERT_EQ(*output.get(), expected); +} + +TEST(ExecutorTests, + MultiThreadedExecutorMultipleTasksLargeNumberOfThreadsSucceeds) { + absl::BlockingCounter block_count(250); + + // Arrange + auto executor = std::make_unique(32); + + // Container to note threads that ran + std::vector threadIds = std::vector(); + + threadIds.push_back(std::this_thread::get_id()); + absl::Mutex mutex; + // Act + for (int index = 0; index < 250; index++) { + executor->Execute([&]() mutable { + std::thread::id id = std::this_thread::get_id(); + { + absl::MutexLock lock(&mutex); + threadIds.push_back(id); + } + + // Using rand since this is in a critical section + // and windows doesn't have a rand_r anyway + auto sleepTime = (std::rand() % 101) + 1; // NOLINT + + sleep(sleepTime); + block_count.DecrementCount(); + }); + } + + block_count.Wait(); + executor->Shutdown(); + + // Assert + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds.at(0)); + + // We should've run 1 time on the main thread, and 200 times on the + // workerThreads + ASSERT_EQ(threadIds.size(), 251); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file.cc b/internal/platform/implementation/linux/file.cc new file mode 100644 index 00000000..acb5fd0c --- /dev/null +++ b/internal/platform/implementation/linux/file.cc @@ -0,0 +1,111 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/file.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/utils.h" + +namespace nearby { +namespace linux { + +// InputFile +std::unique_ptr IOFile::CreateInputFile( + const absl::string_view file_path, size_t size) { + return absl::WrapUnique(new IOFile(file_path, size)); +} + +IOFile::IOFile(const absl::string_view file_path, size_t size) + : path_(file_path) { + // Always open input file path as wide string on Linux platform. + file_.open(std::filesystem::path(linux::string_to_wstring(path_)), std::ios::binary | std::ios::in | std::ios::ate); + + total_size_ = file_.tellg(); + file_.seekg(0); +} + +std::unique_ptr IOFile::CreateOutputFile(const absl::string_view path) { + return std::unique_ptr(new IOFile(path)); +} + +IOFile::IOFile(const absl::string_view file_path) + : file_(), path_(file_path), total_size_(0) { + // Always open input file path as wide string on Windows platform. + std::wstring_convert> converter; + file_.open(std::filesystem::path(converter.from_bytes(path_)), std::ios::binary | std::ios::out); +} + +ExceptionOr IOFile::Read(std::int64_t size) { + if (!file_.is_open()) { + return ExceptionOr{Exception::kIo}; + } + + if (file_.peek() == EOF) { + return ExceptionOr{ByteArray{}}; + } + + if (!file_.good()) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray bytes(size); + std::unique_ptr read_bytes{new char[size]}; + file_.read(read_bytes.get(), static_cast(size)); + auto num_bytes_read = file_.gcount(); + if (num_bytes_read == 0) { + return ExceptionOr{Exception::kIo}; + } + + return ExceptionOr(ByteArray(read_bytes.get(), num_bytes_read)); +} + +Exception IOFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +Exception IOFile::Write(const ByteArray& data) { + if (!file_.is_open()) { + return {Exception::kIo}; + } + + if (!file_.good()) { + return {Exception::kIo}; + } + + file_.write(data.data(), data.size()); + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception IOFile::Flush() { + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file.h b/internal/platform/implementation/linux/file.h new file mode 100644 index 00000000..5176f33c --- /dev/null +++ b/internal/platform/implementation/linux/file.h @@ -0,0 +1,60 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_FILE_H_ +#define PLATFORM_IMPL_LINUX_FILE_H_ + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/input_file.h" +#include "internal/platform/implementation/output_file.h" + +namespace nearby { +namespace linux { + +class IOFile final : public api::InputFile, public api::OutputFile { + public: + static std::unique_ptr CreateInputFile( + const absl::string_view file_path, size_t size); + + static std::unique_ptr CreateOutputFile(const absl::string_view path); + + ExceptionOr Read(std::int64_t size) override; + + std::string GetFilePath() const override { return path_; } + + std::int64_t GetTotalSize() const override { return total_size_; } + Exception Close() override; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + + private: + explicit IOFile(const absl::string_view file_path, size_t size); + explicit IOFile(const absl::string_view file_path); + + std::fstream file_; + std::string path_; + std::int64_t total_size_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_FILE_H_ diff --git a/internal/platform/implementation/linux/mutex.h b/internal/platform/implementation/linux/mutex.h new file mode 100644 index 00000000..f9235a16 --- /dev/null +++ b/internal/platform/implementation/linux/mutex.h @@ -0,0 +1,69 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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. +#ifndef PLATFORM_IMPL_LINUX_MUTEX_H_ +#define PLATFORM_IMPL_LINUX_MUTEX_H_ + +#include +#include // NOLINT + +#include "absl/memory/memory.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/mutex.h" + +namespace nearby { +namespace linux { + +// A lock is a tool for controlling access to a shared resource by multiple +// threads. +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(Mode mode) : mode_(mode) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + if (mode_ == Mode::kRegularNoCheck) mutex_.ForgetDeadlockInfo(); + if (mode_ == Mode::kRegular || mode_ == Mode::kRegularNoCheck) { + mutex_.Lock(); + } else { + recursive_mutex_.lock(); + } + } + + void Unlock() ABSL_UNLOCK_FUNCTION() override { + if (mode_ == Mode::kRegular || mode_ == Mode::kRegularNoCheck) { + mutex_.Unlock(); + } else { + recursive_mutex_.unlock(); + } + } + + absl::Mutex& GetMutex() { return mutex_; } + std::recursive_mutex& GetRecursiveMutex() { return recursive_mutex_; } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + std::recursive_mutex recursive_mutex_; // The actual mutex allocation + Mode mode_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_MUTEX_H_ \ No newline at end of file diff --git a/internal/platform/implementation/linux/mutext_test.cc b/internal/platform/implementation/linux/mutext_test.cc new file mode 100644 index 00000000..466575d2 --- /dev/null +++ b/internal/platform/implementation/linux/mutext_test.cc @@ -0,0 +1,105 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/mutex.h" + +#include // NOLINT + +#include "gtest/gtest.h" + +class MutexTests : public testing::Test { + public: + class MutexTest { + public: + MutexTest(nearby::linux::Mutex& mutex) : mutex_(mutex) {} + + std::future WaitForLock() { // NOLINT + return std::async(std::launch::async, + // for this lambda you need C++14 + [this]() mutable { + absl::MutexLock(&mutex_.GetMutex()); + return true; + }); + } + + void PostEvent() { + absl::MutexLock(&mutex_.GetMutex()); + mutex_.Unlock(); + } + + private: + nearby::linux::Mutex& mutex_; + }; +}; + +TEST_F(MutexTests, SuccessfulRecursiveCreation) { + // Arrange + nearby::linux::Mutex mutex = + nearby::linux::Mutex(nearby::linux::Mutex::Mode::kRecursive); + + // Act + std::recursive_mutex& actual = mutex.GetRecursiveMutex(); + + // Assert + ASSERT_TRUE(actual.native_handle() != nullptr); +} + +TEST_F(MutexTests, SuccessfulCreation) { + // Arrange + nearby::linux::Mutex mutex(nearby::linux::Mutex::Mode::kRegular); + + // Act + absl::Mutex& actual = mutex.GetMutex(); + + // Assert + ASSERT_TRUE(&actual != nullptr); +} + +TEST_F(MutexTests, SuccessfulSignal) { + // Arrange + nearby::linux::Mutex mutex(nearby::linux::Mutex::Mode::kRegular); + + nearby::linux::Mutex& mutexRef = mutex; + MutexTest mutexTest(mutexRef); + + mutex.Lock(); + + // Act + auto result = mutexTest.WaitForLock(); + mutex.Unlock(); + + // Assert + ASSERT_TRUE(result.get()); +} + +TEST_F(MutexTests, SuccessfulRecursiveSignal) { + // Arrange + nearby::linux::Mutex mutex(nearby::linux::Mutex::Mode::kRecursive); + + nearby::linux::Mutex& mutexRef = mutex; + MutexTest mutexTest(mutexRef); + + mutex.Lock(); + mutex.Lock(); + mutex.Lock(); + + // Act + auto result = mutexTest.WaitForLock(); + mutex.Unlock(); + mutex.Unlock(); + mutex.Unlock(); + + // Assert + ASSERT_TRUE(result.get()); +} diff --git a/internal/platform/implementation/linux/test_data.h b/internal/platform/implementation/linux/test_data.h new file mode 100644 index 00000000..7cdddf9b --- /dev/null +++ b/internal/platform/implementation/linux/test_data.h @@ -0,0 +1,34 @@ +// Copyright 2021 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_TEST_DATA_H_ +#define PLATFORM_IMPL_LINUX_TEST_DATA_H_ + +#define INVALID_ARGUMENT_TEXT "max_concurrency" +#define THREADPOOL_MAX_SIZE_TEXT "Thread pool max size exceeded." +#define RUNNABLE_TEXT std::string("runnable ") +#define RUNNABLE_0_TEXT RUNNABLE_TEXT + std::string("0") +#define RUNNABLE_1_TEXT RUNNABLE_TEXT + std::string("1") +#define RUNNABLE_2_TEXT RUNNABLE_TEXT + std::string("2") +#define RUNNABLE_3_TEXT RUNNABLE_TEXT + std::string("3") +#define RUNNABLE_4_TEXT RUNNABLE_TEXT + std::string("4") +#define RUNNABLE_SEPARATOR_TEXT std::string(", ") +#define RUNNABLE_ALL_TEXT \ + (RUNNABLE_0_TEXT + RUNNABLE_SEPARATOR_TEXT + RUNNABLE_1_TEXT + \ + RUNNABLE_SEPARATOR_TEXT + RUNNABLE_2_TEXT + RUNNABLE_SEPARATOR_TEXT + \ + RUNNABLE_3_TEXT + RUNNABLE_SEPARATOR_TEXT + RUNNABLE_4_TEXT + \ + RUNNABLE_SEPARATOR_TEXT) + +#endif // PLATFORM_IMPL_LINUX_TEST_DATA_H_ + diff --git a/internal/platform/implementation/linux/thread_pool.cc b/internal/platform/implementation/linux/thread_pool.cc new file mode 100644 index 00000000..40dec614 --- /dev/null +++ b/internal/platform/implementation/linux/thread_pool.cc @@ -0,0 +1,128 @@ +// Copyright 2021-2023 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/thread_pool.h" + +#include +#include + +#include "absl/memory/memory.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/logging.h" +#include "internal/platform/runnable.h" + +namespace nearby { +namespace linux { + +std::unique_ptr ThreadPool::Create(int max_pool_size) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Create thread pool with maximum size(" + << max_pool_size << ")."; + + if (max_pool_size <= 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Maximum pool size must be positive integer value."; + return nullptr; + } + + std::unique_ptr> thread_pool = std::make_unique>(); + + // Sets thread pool maximum value. + thread_pool->resize(max_pool_size); + + return absl::WrapUnique( + new ThreadPool(thread_pool, max_pool_size)); +} + +ThreadPool::ThreadPool(std::unique_ptr> &thread_pool, int max_pool_size) + : thread_pool_(std::move(thread_pool)), + max_pool_size_(max_pool_size) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Thread pool(" << this + << ") is created with size:" << max_pool_size_; + + for (int size = 0; size < max_pool_size_; size++) { + thread_pool_->at(size) = std::thread([this]() { + while (!tasks_.empty()) { + RunNextTask(); + // Possibly don't need but here to prevent 100% usage for loop + sleep(300); + } + }); + } +} + +ThreadPool::~ThreadPool() { + NEARBY_LOGS(VERBOSE) << __func__ << ": Thread pool(" << this + << ") is released."; + + ShutDown(); +} + +bool ThreadPool::Run(Runnable task) { + absl::MutexLock lock(&mutex_); + + if (thread_pool_->size() == max_pool_size_) { + return false; + } + + tasks_.push(std::move(task)); + NEARBY_LOGS(VERBOSE) << __func__ << ": Scheduled to run task(" + << &tasks_.back() << ")."; + return true; +} + +void ThreadPool::ShutDown() { + absl::MutexLock lock(&mutex_); + + if (!thread_pool_->empty()) { + NEARBY_LOGS(WARNING) << __func__ << ": Request to shutdown thread pool with " << thread_pool_->size() << " tasks not finished(" << this << ")."; + } + + NEARBY_LOGS(VERBOSE) << __func__ << ": Shutdown thread pool(" << this << ")."; + if (thread_pool_ == nullptr) { + NEARBY_LOGS(WARNING) << __func__ << ": Shutdown on closed thread pool(" + << this << ")."; + return; + } + + thread_pool_.reset(); +} + +void ThreadPool::RunNextTask() { + Runnable task = nullptr; + + { + absl::MutexLock lock(&mutex_); + + if (!thread_pool_) { + return; + } + if (!tasks_.empty()) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Run task(" << &tasks_.front() + << ")."; + + task = std::move(tasks_.front()); + tasks_.pop(); + } + } + if (task == nullptr) { + NEARBY_LOGS(WARNING) << __func__ + << ": Tried to run task in an empty thread pool."; + return; + } + + task(); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/thread_pool.h b/internal/platform/implementation/linux/thread_pool.h new file mode 100644 index 00000000..889fe8b6 --- /dev/null +++ b/internal/platform/implementation/linux/thread_pool.h @@ -0,0 +1,72 @@ +// Copyright 2020-2023 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_THREAD_POOL_H_ +#define PLATFORM_IMPL_LINUX_THREAD_POOL_H_ + +#include +#include +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/runnable.h" + +namespace nearby { +namespace linux { + +class ThreadPool { + public: + virtual ~ThreadPool(); + static std::unique_ptr Create(int max_pool_size); + + // Runs a task on thread pool. The result indicates whether the task is put + // into the thread pool. + bool Run(Runnable task) ABSL_LOCKS_EXCLUDED(mutex_); + + // The thread pool is closed immediately if there is no outstanding work, + // I/O, timer, or wait objects that are bound to the pool; otherwise, the + // thread pool is released asynchronously after the outstanding objects are + // freed. + void ShutDown() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + ThreadPool(std::unique_ptr> &thread_pool, int max_pool_size); + + void RunNextTask(); + + // Starts each task and injects a function that removes the task when it is finished + std::thread tasks_runner_; + + // Protects the access to tasks of the thread pool. + mutable absl::Mutex mutex_; + + // The task queue of the thread pool. Thread pool will pick up task to run + // when it is idle. + std::queue tasks_ ABSL_GUARDED_BY(mutex_); + + // All the threads run in the pool + std::unique_ptr> thread_pool_ ABSL_GUARDED_BY(mutex_); + + // The maximum thread count in the thread pool + int max_pool_size_ ABSL_GUARDED_BY(mutex_) = 0; + +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_THREAD_POOL_H_ diff --git a/internal/platform/implementation/linux/utils.cc b/internal/platform/implementation/linux/utils.cc new file mode 100644 index 00000000..1d22b21b --- /dev/null +++ b/internal/platform/implementation/linux/utils.cc @@ -0,0 +1,362 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/linux/utils.h" + +// Standard C/C++ headers +#include +#include +#include +#include +#include +#include +#include + +// Third party headers +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" + +// Nearby connections headers +#include "absl/strings/string_view.h" +#include "internal/platform/bluetooth_utils.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/crypto.h" +#include "internal/platform/logging.h" +#include "internal/platform/uuid.h" + +// Linux headers +#include +#include +#include +#include + +namespace nearby { +namespace linux { +namespace { + +std::string uint64_to_mac_address_string(uint64_t bluetoothAddress) { + std::string buffer = absl::StrFormat( + "%02llx:%02llx:%02llx:%02llx:%02llx:%02llx", bluetoothAddress >> 40, + (bluetoothAddress >> 32) & 0xff, (bluetoothAddress >> 24) & 0xff, + (bluetoothAddress >> 16) & 0xff, (bluetoothAddress >> 8) & 0xff, + bluetoothAddress & 0xff); + + return absl::AsciiStrToUpper(buffer); +} + +uint64_t mac_address_string_to_uint64(absl::string_view mac_address) { + ByteArray mac_address_array = BluetoothUtils::FromString(mac_address); + uint64_t mac_address_uint64 = 0; + for (int i = 0; i < mac_address_array.size(); i++) { + mac_address_uint64 <<= 8; + mac_address_uint64 |= static_cast( + static_cast(*(mac_address_array.data() + i))); + } + return mac_address_uint64; +} + +std::string ipaddr_4bytes_to_dotdecimal_string( + absl::string_view ipaddr_4bytes) { + + union addrs { + in_addr_t addr; + uint8_t bits[4]; + } address; + + address.bits[0] = ipaddr_4bytes[0]; + address.bits[1] = ipaddr_4bytes[1]; + address.bits[2] = ipaddr_4bytes[2]; + address.bits[3] = ipaddr_4bytes[3]; + + struct in_addr addr; + + addr.s_addr = address.addr; + char* ipv4_address = inet_ntoa(addr); + if (ipv4_address == nullptr) { + return {}; + } + + return std::string(ipv4_address); +} + +std::string ipaddr_dotdecimal_to_4bytes_string(std::string ipv4_s) { + if (ipv4_s.empty()) { + return {}; + } + + struct in_addr addr; + + if (inet_aton(ipv4_s.c_str(), &addr) != 0) { + return {}; + } + + std::string ipv4_b = std::to_string(addr.s_addr); + + return std::string(); +} + +std::wstring string_to_wstring(std::string str) { + std::wstring_convert> converter; + return converter.from_bytes(str); +} + +std::string wstring_to_string(std::wstring wstr) { + std::wstring_convert> converter; + return converter.to_bytes(wstr); +} + +std::vector GetIpv4Addresses() { + std::vector result; + + struct ifaddrs *interface = nullptr; + char host[NI_MAXHOST]; + + if (getifaddrs(&interface) != 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Failed to get interfaces. Error: " + << strerror(errno); + freeifaddrs(interface); + return {}; + } + int status = 0; + for (struct ifaddrs *ifa = interface; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family == AF_INET) { + status = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST); + } + switch (status) { + case EAI_AGAIN: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : The name could not be resolved at this time. " + << "Try again later."; + break; + case EAI_BADFLAGS: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : The flags argument has an invalid value."; + break; + case EAI_FAIL: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : A nonrecoverable error occured."; + break; + case EAI_FAMILY: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : The address family was not recognized, " + << "or the address length was invalid for the " + << "specified family."; + break; + case EAI_MEMORY: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : Out of memory."; + break; + case EAI_NONAME: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : The name does not resolve for the suplied arguments." + << " NI_NAMEREQD is set and the host's name cannot be located, " + << "or neither hostname nor service name were requsted."; + break; + case EAI_OVERFLOW: + NEARBY_LOGS(ERROR) << __func__ + << "Failed to get IP for interface: " + << ifa->ifa_name + << " : The bugger pointed to by `host` or `serv` was too small."; + break; + case EAI_SYSTEM: + NEARBY_LOGS(ERROR) << __func__ + << "A system error occured. Error code: " + << errno + << ": " << strerror(errno); + break; + } + } + return result; +} + +std::vector Get4BytesIpv4Addresses() { + std::vector result; + std::vector ipv4_addresses = GetIpv4Addresses(); + for (const auto& ipv4_address : ipv4_addresses) { + // Converts IP address from x.x.x.x to 4 bytes format using utils function + result.push_back(ipaddr_dotdecimal_to_4bytes_string(ipv4_address)); + } + + return result; +} + +/* +Uuid winrt_guid_to_nearby_uuid(const ::winrt::guid& guid) { + int64_t data1 = guid.Data1; + int64_t data2 = guid.Data2; + int64_t data3 = guid.Data3; + + int64_t msb = ((data1 >> 24) & 0xff) << 56 | ((data1 >> 16) & 0xff) << 48 | + ((data1 >> 8) & 0xff) << 40 | ((data1)&0xff) << 32 | + ((data2 >> 8) & 0xff) << 24 | ((data2)&0xff) << 16 | + ((data3 >> 8) & 0xff) << 8 | (data3 & 0xff); + + int64_t lsb = + ((int64_t)guid.Data4[0]) << 56 | ((int64_t)guid.Data4[1]) << 48 | + ((int64_t)guid.Data4[2]) << 40 | ((int64_t)guid.Data4[3]) << 32 | + ((int64_t)guid.Data4[4]) << 24 | ((int64_t)guid.Data4[5]) << 16 | + ((int64_t)guid.Data4[6]) << 8 | (int64_t)guid.Data4[7]; + + return Uuid(msb, lsb); +} +*/ + +/* +winrt::guid nearby_uuid_to_winrt_guid(Uuid uuid) { + winrt::guid guid; + uint64_t msb = uuid.GetMostSigBits(); + guid.Data1 = ((msb >> 56) & 0xff) << 24 | ((msb >> 48) & 0xff) << 16 | + ((msb >> 40) & 0xff) << 8 | ((msb >> 32) & 0xff); + guid.Data2 = ((msb >> 24) & 0xff) << 8 | ((msb >> 16) & 0xff); + guid.Data3 = ((msb >> 8) & 0xff) << 8 | (msb & 0xff); + uint64_t lsb = uuid.GetLeastSigBits(); + guid.Data4[0] = (lsb >> 56) & 0xff; + guid.Data4[1] = (lsb >> 48) & 0xff; + guid.Data4[2] = (lsb >> 40) & 0xff; + guid.Data4[3] = (lsb >> 32) & 0xff; + guid.Data4[4] = (lsb >> 24) & 0xff; + guid.Data4[5] = (lsb >> 16) & 0xff; + guid.Data4[6] = (lsb >> 8) & 0xff; + guid.Data4[7] = lsb & 0xff; + return guid; +} +*/ + +/* +bool is_nearby_uuid_equal_to_winrt_guid(const Uuid& uuid, + const ::winrt::guid& guid) { + return uuid == winrt_guid_to_nearby_uuid(guid); +} +*/ + +ByteArray Sha256(absl::string_view input, size_t size) { + ByteArray hash = nearby::Crypto::Sha256(input); + return ByteArray{hash.data(), size}; +} +/* +bool InspectableReader::ReadBoolean(IInspectable inspectable) { + if (inspectable == nullptr) { + return false; + } + + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::Boolean) { + throw std::invalid_argument("not uin16 data type."); + } + + return property_value.GetBoolean(); +} + +uint16 InspectableReader::ReadUint16(IInspectable inspectable) { + if (inspectable == nullptr) { + return 0; + } + + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::UInt16) { + throw std::invalid_argument("not uin16 data type."); + } + + return property_value.GetUInt16(); +} + +uint32 InspectableReader::ReadUint32(IInspectable inspectable) { + if (inspectable == nullptr) { + return 0; + } + + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::UInt32) { + throw std::invalid_argument("not uin32 data type."); + } + + return property_value.GetUInt32(); +} + +std::string InspectableReader::ReadString(IInspectable inspectable) { + if (inspectable == nullptr) { + return ""; + } + + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::String) { + throw std::invalid_argument("not string data type."); + } + + return wstring_to_string(property_value.GetString().c_str()); +} + +std::vector InspectableReader::ReadStringArray( + IInspectable inspectable) { + std::vector result; + if (inspectable == nullptr) { + return result; + } + + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::StringArray) { + throw std::invalid_argument("not string array data type."); + } + + winrt::com_array strings; + property_value.GetStringArray(strings); + + for (winrt::hstring str : strings) { + result.push_back(winrt::to_string(str)); + } + return result; +} +*/ +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/utils.h b/internal/platform/implementation/linux/utils.h new file mode 100644 index 00000000..ef43e77e --- /dev/null +++ b/internal/platform/implementation/linux/utils.h @@ -0,0 +1,85 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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. + +#ifndef PLATFORM_IMPL_LINUX_UTILS_H_ +#define PLATFORM_IMPL_LINUX_UTILS_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { + +std::string uint64_to_mac_address_string(uint64_t bluetoothAddress); +uint64_t mac_address_string_to_uint64(absl::string_view mac_address); + +std::string ipaddr_4bytes_to_dotdecimal_string(absl::string_view ipaddr_4bytes); +std::string ipaddr_dotdecimal_to_4bytes_string(std::string ipv4_s); + +// Helpers to linux platform +std::wstring string_to_wstring(std::string str); +std::string wstring_to_string(std::wstring wstr); +ByteArray Sha256(absl::string_view input, size_t size); + +// Reads the IPv4 addresses +std::vector GetIpv4Addresses(); +std::vector Get4BytesIpv4Addresses(); + +/* +// Help methods to convert between Uuid and winrt::guid +Uuid winrt_guid_to_nearby_uuid(const ::winrt::guid& guid); +winrt::guid nearby_uuid_to_winrt_guid(Uuid uuid); + +// Check whether Uuid and guid is the same value. +bool is_nearby_uuid_equal_to_winrt_guid(const Uuid& uuid, + const ::winrt::guid& guid); +*/ + +namespace Constants { +// The Id of the Service Name SDP attribute +const uint16_t SdpServiceNameAttributeId = 0x100; + +// The SDP Type of the Service Name SDP attribute. +// The first byte in the SDP Attribute encodes the SDP Attribute Type as +// follows: +// - the Attribute Type size in the least significant 3 bits, +// - the SDP Attribute Type value in the most significant 5 bits. +const char SdpServiceNameAttributeType = (4 << 3) | 5; + +// Possible values for the adapter type. Refer to: +// https://learn.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_adapter_info +const uint16_t kInterfaceTypeEthernet = 6; +const uint16_t kInterfaceTypeWifi = 71; +} // namespace Constants + +/* +class InspectableReader { + public: + static bool ReadBoolean(IInspectable inspectable); + static uint16_t ReadUint16(IInspectable inspectable); + static uint32_t ReadUint32(IInspectable inspectable); + static std::string ReadString(IInspectable inspectable); + static std::vector ReadStringArray(IInspectable inspectable); +}; +*/ + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_UTILS_H_