From ff9438a3ed04cbc1c0f45869ba1da7705ac1757f Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Fri, 19 May 2023 19:19:15 -0500 Subject: [PATCH 01/13] 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. --- 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 ++++ 23 files changed, 2517 insertions(+) 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/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_ From d387a441593f7d4399bcf5b6b2b4790db45ffb54 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Fri, 19 May 2023 19:51:04 -0500 Subject: [PATCH 02/13] Fix memory leak with getting interfaces There was no free after we were finished with the interface pointer --- internal/platform/implementation/linux/utils.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/platform/implementation/linux/utils.cc b/internal/platform/implementation/linux/utils.cc index 1d22b21b..0660b9b8 100644 --- a/internal/platform/implementation/linux/utils.cc +++ b/internal/platform/implementation/linux/utils.cc @@ -191,6 +191,7 @@ std::vector GetIpv4Addresses() { break; } } + freeifaddrs(interface); return result; } From cbef0de68dd916ba61e80b80f87b805730425610 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Sun, 21 May 2023 17:07:15 -0500 Subject: [PATCH 03/13] Added program data directories to Linux platform device_info There are a couple of places where apps can store data in Linux, one is in the HOME directory, which is being phased out, the second, is in HOME/.config, and the other is HOME/.local/share. This uses HOME/.local/share, and since there isn't a common appdata path on Linux that function will just return the local appdata path. --- .../implementation/linux/device_info.cc | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/internal/platform/implementation/linux/device_info.cc b/internal/platform/implementation/linux/device_info.cc index b05aeda8..5ee10822 100644 --- a/internal/platform/implementation/linux/device_info.cc +++ b/internal/platform/implementation/linux/device_info.cc @@ -49,7 +49,7 @@ std::optional DeviceInfo::GetOsDeviceName() const { return std::wstring_convert, char16_t>().from_bytes(name); } - NEARBY_LOGS(ERROR) << ": Failed to get device name, error:" + NEARBY_LOGS(ERROR) << ": Failed to get device name, error: " << strerror(errno); delete[] device_name; return std::nullopt; @@ -210,6 +210,7 @@ std::optional DeviceInfo::GetDownloadPath() const { delete[] path; return std::nullopt; } + delete fp; std::filesystem::path fpath = std::filesystem::path(path); delete[] path; @@ -217,15 +218,32 @@ std::optional DeviceInfo::GetDownloadPath() const { } std::optional DeviceInfo::GetLocalAppDataPath() const { - //TODO: Figure out how to get cross distro path + // From lots of research I have done, it seems there is no way to get a path + // for this cross distro wise, some distros might implement this path in + // a different way. Because of that, this will have to be hard-coded and in + // the future may be able to detect which distro and if the path is different + // deal with that. + + std::string app_data_dir(getenv("HOME")); + app_data_dir.append("/.local/share"); + + if (std::filesystem::is_directory(app_data_dir)) { + return app_data_dir; + } return std::nullopt; } std::optional DeviceInfo::GetCommonAppDataPath() const { - //TODO: Figure out how to get cross distro path + // From lots of research I have done, it seems there is no way to get a path + // for this cross distro wise, some distros might implement this path in + // a different way. Because of that, this will have to be hard-coded and in + // the future may be able to detect which distro and if the path is different + // deal with that. + + // Also there is no common appdata path on Linux - return std::nullopt; + return GetLocalAppDataPath(); } std::optional DeviceInfo::GetTemporaryPath() const { @@ -233,14 +251,13 @@ std::optional DeviceInfo::GetTemporaryPath() const { } std::optional DeviceInfo::GetLogPath() const { - //TODO: Figure out how to get cross distro path - - return std::nullopt; + return std::nullopt; } std::optional DeviceInfo::GetCrashDumpPath() const { - //TODO: Figure out how to get cross distro path - + // Crash dumps are handled by the system, it will generate a core (crash) dump + // and put it in a directory. + // E.g. "Segmentation fault (core dumped)" return std::nullopt; } From 46928a3347303d23ac08956cf6049025ab56bdab Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Sun, 21 May 2023 18:58:33 -0500 Subject: [PATCH 04/13] Implemented Linux equivalent to Windows file_path* This adds a Linux equivalent to file path operations done in the Windows headers and source files. Some differences that will occur are the invalid path names and contents, as there are no invalid path names, and only one invalid path content (excluding non-printable characters) on Linux. --- .../implementation/linux/file_path.cc | 209 +++++ .../platform/implementation/linux/file_path.h | 49 ++ .../implementation/linux/file_path_test.cc | 770 ++++++++++++++++++ 3 files changed, 1028 insertions(+) create mode 100644 internal/platform/implementation/linux/file_path.cc create mode 100644 internal/platform/implementation/linux/file_path.h create mode 100644 internal/platform/implementation/linux/file_path_test.cc diff --git a/internal/platform/implementation/linux/file_path.cc b/internal/platform/implementation/linux/file_path.cc new file mode 100644 index 00000000..f66fe6a4 --- /dev/null +++ b/internal/platform/implementation/linux/file_path.cc @@ -0,0 +1,209 @@ +// Copyright 2022 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_path.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/implementation/linux/device_info.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +const wchar_t* kUpOneLevel = L"/.."; +constexpr wchar_t kPathDelimiter = L'/'; +constexpr wchar_t kReplacementChar = L'_'; +constexpr wchar_t kForwardSlash = L'/'; +constexpr wchar_t kBackSlash = L'\\'; + +std::wstring FilePath::GetCustomSavePath(std::wstring parent_folder, + std::wstring file_name) { + std::wstring path; + path += parent_folder + kPathDelimiter + file_name; + return CreateOutputFileWithRename(path); +} + +std::wstring FilePath::GetDownloadPath(std::wstring parent_folder, + std::wstring file_name) { + return CreateOutputFileWithRename( + GetDownloadPathInternal(parent_folder, file_name)); +} + +std::wstring FilePath::GetDownloadPathInternal(std::wstring parent_folder, + std::wstring file_name) { + DeviceInfo info = DeviceInfo(); + + std::optional download_path = info.GetDownloadPath(); + + std::string base_path; + + std::wstring wide_path(string_to_wstring(base_path)); + + if (!download_path) { + // If grabbing the download path fails then we make a custom one + base_path = getenv("HOME"); + base_path.append("/Downloads"); + } + else { + base_path = download_path.value(); + } + + // If parent_folder starts with a \\ or /, then strip it + while (!parent_folder.empty() && (*parent_folder.begin() == kBackSlash || + *parent_folder.begin() == kForwardSlash)) { + parent_folder.erase(0, 1); + } + + // If parent_folder ends with a \\ or /, then strip it + while (!parent_folder.empty() && (*parent_folder.rbegin() == kBackSlash || + *parent_folder.rbegin() == kForwardSlash)) { + parent_folder.erase(parent_folder.size() - 1, 1); + } + + // If file_name starts with a \\, then strip it + while (!file_name.empty() && (*file_name.begin() == kBackSlash || + *file_name.begin() == kForwardSlash)) { + file_name.erase(0, 1); + } + + // If file_name ends with a \\, then strip it + while (!file_name.empty() && (*file_name.rbegin() == kBackSlash || + *file_name.rbegin() == kForwardSlash)) { + file_name.erase(file_name.size() - 1, 1); + } + + std::wstring path; + + if (parent_folder.empty()) { + path = + file_name.empty() ? wide_path : wide_path + kForwardSlash + file_name; + } else { + path = file_name.empty() ? wide_path + kForwardSlash + parent_folder + : wide_path + kForwardSlash + parent_folder + + kForwardSlash + file_name; + } + + // Convert to UTF8 format. + return path; +} + +// If the file already exists we add " (x)", where x is an incrementing number, +// starting at 1, using the next non-existing number, to the file name, just +// before the first dot, or at the end if no dot. The absolute path is returned. +std::wstring FilePath::CreateOutputFileWithRename(std::wstring path) { + std::wstring sanitized_path(path); + + // Replace any \\ with / + std::replace(sanitized_path.begin(), sanitized_path.end(), kBackSlash, + kForwardSlash); + + // Remove any /..'s + SanitizePath(sanitized_path); + + auto last_delimiter = sanitized_path.find_last_of(kPathDelimiter); + std::wstring folder(sanitized_path.substr(0, last_delimiter)); + std::wstring file_name(sanitized_path.substr(last_delimiter)); + + // Locate the last dot + auto first = file_name.find_last_of('.'); + + if (first == std::string::npos) { + first = file_name.size(); + } + + // Break the string at the dot. + auto file_name1 = file_name.substr(0, first); + auto file_name2 = file_name.substr(first); + + // Construct the target file name + std::wstring target(sanitized_path); + + std::fstream file; + + // Open file as std::wstring + file.open(wstring_to_string(target), std::fstream::binary | std::fstream::in); + + // While we successfully open the file, keep incrementing the count. + int count = 0; + while (!(file.rdstate() & std::ifstream::failbit)) { + file.close(); + + target = (folder + file_name1 + L" (" + std::to_wstring(++count) + L")" + + file_name2); + + file.clear(); + file.open(wstring_to_string(target), std::fstream::binary | std::fstream::in); + } + + if (count > 0) { + NEARBY_LOGS(INFO) << "Renamed " << wstring_to_string(path) << " to " + << wstring_to_string(target); + } + + // The above leaves the file open, so close it. + file.close(); + + return target; +} + +std::wstring FilePath::MutateForbiddenPathElements(std::wstring& str) { + // There are no forbidden paths in Linux + return str; +} + +void FilePath::SanitizePath(std::wstring& path) { + size_t pos = std::wstring::npos; + // Search for the substring in string in a loop until nothing is found + while ((pos = path.find(kUpOneLevel)) != std::string::npos) { + // If found then erase it from string + path.erase(pos, wcslen(kUpOneLevel)); + } + + ReplaceInvalidCharacters(path); +} + +// Legit the only illegal character in Linux +char kIllegalFileCharacters[] = {'/'}; + +void FilePath::ReplaceInvalidCharacters(std::wstring& path) { + + for (auto &character : path) { + // If 0 < character < 32, it's illegal, replace it + if (character > 0 && character < 32) { + NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path) + << " replaced \'" << std::string(1, character) << "\' with \'" + << std::string(1, kReplacementChar); + character = kReplacementChar; + } + for (auto illegal_character : kIllegalFileCharacters) { + if (character == illegal_character) { + NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path) + << " replaced \'" << std::string(1, character) + << "\' with \'" << std::string(1, kReplacementChar); + character = kReplacementChar; + } + } + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file_path.h b/internal/platform/implementation/linux/file_path.h new file mode 100644 index 00000000..6053b2f4 --- /dev/null +++ b/internal/platform/implementation/linux/file_path.h @@ -0,0 +1,49 @@ +// Copyright 2022 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace nearby { +namespace linux { + +class FilePath { + public: + static std::wstring GetCustomSavePath(std::wstring parent_folder, + std::wstring file_name); + static std::wstring GetDownloadPath(std::wstring parent_folder, + std::wstring file_name); + + private: + // If the file already exists we add " (x)", where x is an incrementing + // number, starting at 1, using the next non-existing number, to the + // file name, just before the first dot, or at the end if no dot. The + // absolute path is returned. + static std::wstring CreateOutputFileWithRename(std::wstring path); + + static void ReplaceInvalidCharacters(std::wstring& path); + static void SanitizePath(std::wstring& path); + static std::wstring MutateForbiddenPathElements(std::wstring& str); + static std::wstring GetDownloadPathInternal(std::wstring parent_folder, + std::wstring file_name); +}; + +} // namespace linux +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ diff --git a/internal/platform/implementation/linux/file_path_test.cc b/internal/platform/implementation/linux/file_path_test.cc new file mode 100644 index 00000000..a9c7e878 --- /dev/null +++ b/internal/platform/implementation/linux/file_path_test.cc @@ -0,0 +1,770 @@ +// Copyright 2022 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_path.h" + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include +#include + +namespace nearby { +namespace linux { + +namespace { + +const wchar_t* kFileName(L"increment_file_test.txt"); +const wchar_t* kFirstIterationFileName(L"/increment_file_test (1).txt"); +const wchar_t* kSecondIterationFileName(L"/increment_file_test (2).txt"); +const wchar_t* kThirdIterationFileName(L"/increment_file_test (3).txt"); +const wchar_t* kNoDotsFileName(L"incrementfiletesttxt"); +const wchar_t* kOneIterationNoDotsFileName(L"/incrementfiletesttxt (1)"); +const wchar_t* kMultipleDotsFileName(L"increment.file.test.txt"); +const wchar_t* kOneIterationMultipleDotsFileName( + L"/increment.file.test (1).txt"); +const wchar_t* kImmediateEscape(L"../"); +const wchar_t* kLongEscapeBackSlash(L"..\\test\\..\\..\\test"); +const wchar_t* kTwoLevelFolder(L"/test/test"); +const wchar_t* kLongEscapeSlash(L"../test/../../test"); +const wchar_t* kLongEscapeMixedSlash(L"../test\\..\\../test"); +const wchar_t* kLongEscapeEndingEscape(L"../test/../../test/.."); +const wchar_t* kLongEscapeEndingEscapeWithSlash( + L"../test/../../test/../../../"); +} // namespace + +// Can't run on google 3, I presume the SHGetKnownFolderPath +// fails. +class FilePathTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + FilePathTests() { + default_download_path_ = string_to_wstring(DeviceInfo().GetDownloadPath().value_or(std::string(getenv("HOME")).append("/Downloads"))); + } + std::wstring default_download_path_; +}; + +TEST_F(FilePathTests, GetDownloadPathWithEmptyStringArguments\ +ShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithSlashParent\ +FolderArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L"/"); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithBackslashParent\ +FolderArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L"\\"); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithAttemptToEscape\ +UsersDownloadFolderShouldReturnDownloadPathNotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kImmediateEscape); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithBackslashShouldReturnDownloadPath\ +NotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeBackSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderShouldReturnDownloadPathNotEscapingUsers\ +DownloadFolder) { + std::wstring parent_folder(kLongEscapeSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithMixedSlashShouldReturnDownloadPath\ +NotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeMixedSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithEndingEscapeShouldReturnDownload\ +PathNotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeEndingEscape); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithEndingSlashShouldReturnDownloadPathNot\ +EscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeEndingEscapeWithSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithSlashFileName\ +ArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"/"); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithBackslashFile\ +NameArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"\\"); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + auto result_size = actual.size(); + auto default_size = default_download_path_.size(); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +ShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +StartingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"/test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +StartingWithBackslashArgumentsShouldReturnParentFolderAppendedToBase\ +DownloadPath) { + std::wstring parent_folder(L"\\test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder/"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithBackslashArguments\ +ShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder\\"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +BeginningWithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"/test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +BeginningWithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"\\test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, path.str().c_str()); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\ +WithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"test_file_name.name/"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\ +WithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"test_file_name.name\\"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolderAnd\ +FileNameArgumentsShould\ +ReturnParentFolderAndFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L"test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithBackslashAndFileNameArgumentsShouldReturnParentFolderAndFileName\ +AppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder\\"); + std::wstring file_name(L"test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +StartingWithBackslashAndParentFolderArgumentsShouldReturnParentFolderAnd\ +FileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L"\\test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacters\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x05, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x5Test"); + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_LowestIllegalFileNameCharacter\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x01, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x1Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_HighestIllegalFileNameCharacter\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x1f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x1fTest"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterQuestionMark\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test?Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterAsterisk\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test*Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterLessThan\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"TestTest"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterVerticalBar\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test|Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterColon\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test:Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_FileDoesntExist\ +ReturnsFileWithPassedName) { + std::wstring file_name(kFileName); + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/"); + expected.append(file_name); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_FileExistsReturns\ +FileWithIncrementedName) { + std::wstring file_name(kFileName); + std::wstring renamed_file_name(kFirstIterationFileName); + std::wstring parent_folder(L""); + + std::wstring output_file_path(default_download_path_); + output_file_path.append(L"/"); + output_file_path.append(file_name); + + std::wstring expected(default_download_path_); + expected += renamed_file_name; + + std::wifstream input_file; + std::wofstream output_file; + + output_file.open(wstring_to_string(output_file_path), + std::ofstream::binary | std::ofstream::out); + + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); + + // Remove the file and check that it is removed + // File 1 + std::filesystem::remove(output_file_path.c_str()); + + input_file.open(wstring_to_string(output_file_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_MultipleFilesExist\ +ReturnsNextIncrementedFileName) { + std::ofstream output_file; + std::ifstream input_file; + + std::wstring file_name(kFileName); + std::wstring first_renamed_file_name(kFirstIterationFileName); + std::wstring second_renamed_file_name(kSecondIterationFileName); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(second_renamed_file_name.c_str()); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(first_renamed_file_name); + + // Create the test files + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + output_file.clear(); + + output_file.open(wstring_to_string(output_file2_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + // Remove the test files and check that it is removed + // File 1 + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // File 2 + std::filesystem::remove(wstring_to_string(output_file2_path).c_str()); + + input_file.clear(); + input_file.open(wstring_to_string(output_file2_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameContains\ +MultipleDotsReturnsIncrementBeforeFirstDot) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kMultipleDotsFileName); + std::wstring renamed_file_name(kOneIterationMultipleDotsFileName); + + std::wstring parent_folder(L""); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(renamed_file_name); + + std::wstring expected(default_download_path_); + expected.append(renamed_file_name); + + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameContainsNo\ +DotsReturnsWithIncrementAtEnd) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kNoDotsFileName); + std::wstring renamed_file_name(kOneIterationNoDotsFileName); + + std::wstring parent_folder(L""); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(L"/" + renamed_file_name); + + std::wstring expected(default_download_path_); + expected.append(renamed_file_name); + + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameExistsWith\ +AHoleBetweenRenamedFiles) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kFileName); + std::wstring file_name1(kFirstIterationFileName); + std::wstring file_name2(kSecondIterationFileName); + std::wstring file_name3(kThirdIterationFileName); + + std::wstring parent_folder(L""); + + // Create the path for the original file name + std::wstring output_file_path(default_download_path_); + output_file_path.append( + L"/" + + file_name); // Original file name example: "increment_file_test.txt" + + // Create the path for the first iteration of the original file name + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(file_name1); // First iteration on original file + // name example: + // "increment_file_test (1).txt" + + // Create the path for the third iteration of the original file name + std::wstring output_file3_path(default_download_path_); + output_file3_path.append( + file_name3); // Third iteration on original file + // name example: "increment_file_test (3).txt" + + // Create the expected result which is the second iteration of the original + // file name + std::wstring expected(default_download_path_); + expected.append(file_name2); // Second iteration on original file name + // example: "increment_file_test (2).txt" + + // Create the original file + output_file.open(wstring_to_string(output_file_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // Create the first iteration of the original file + output_file.clear(); + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // Create the third iteration of the original file + output_file.clear(); + output_file.open(wstring_to_string(output_file3_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // This should return the second iteration of the original file + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + // Delete the original file + std::filesystem::remove(wstring_to_string(output_file_path).c_str()); + input_file.open(wstring_to_string(output_file_path), std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // Delete the first iteration of the original file + input_file.clear(); // Reset the input_file state + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // Delete the third iteration of the original file + input_file.clear(); // Reset the input_file state + std::filesystem::remove(wstring_to_string(output_file3_path).c_str()); + input_file.open(wstring_to_string(output_file3_path), std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} +} // namespace linux +} // namespace nearby From 2539ec16100fe0e4370cbb17f2867ee1a38519f1 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Wed, 2 Aug 2023 18:24:19 -0500 Subject: [PATCH 05/13] Implemented some more Linux equivalents --- .../linux/count_down_latch_test.cc | 161 ++++++++++++++++++ .../platform/implementation/linux/future.h | 48 ++++++ .../implementation/linux/log_message.cc | 71 ++++++++ .../implementation/linux/log_message.h | 43 +++++ .../implementation/linux/thread_pool.cc | 2 +- 5 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 internal/platform/implementation/linux/count_down_latch_test.cc create mode 100644 internal/platform/implementation/linux/future.h create mode 100644 internal/platform/implementation/linux/log_message.cc create mode 100644 internal/platform/implementation/linux/log_message.h diff --git a/internal/platform/implementation/linux/count_down_latch_test.cc b/internal/platform/implementation/linux/count_down_latch_test.cc new file mode 100644 index 00000000..6f4caccd --- /dev/null +++ b/internal/platform/implementation/linux/count_down_latch_test.cc @@ -0,0 +1,161 @@ +// 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/shared/count_down_latch.h" + +#include "gtest/gtest.h" +#include "internal/platform/implementation/platform.h" + +#include +#include +#include + +class CountDownLatchTests : public testing::Test { + public: + class TestData { + public: + std::unique_ptr& countDownLatch; + long volatile& count; + }; + + class CountDownLatchTest { + public: + static unsigned int ThreadProcCountDown(void *lpParam) { + TestData* testData = static_cast(lpParam); + + sleep(1); + + __sync_fetch_and_add(&testData->count, 1); + + testData->countDownLatch->CountDown(); + return 0; + } + + static unsigned int ThreadProcAwait(void *lpParam) { + TestData* testData = static_cast(lpParam); + + sleep(1); + + testData->countDownLatch->Await(); + __sync_fetch_and_add(&testData->count, 1); + + return 0; + } + }; + + CountDownLatchTests() {} +}; + +TEST_F(CountDownLatchTests, CountDownLatchAwaitSucceeds) { + // Arrange + long volatile count = 0; + + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(3); + + std::vector threads; + + TestData testData{countDownLatch, count}; + + // Setup 3 threads + for (int i = 0; i < 3; i++) { + // TODO: More complex scenarios may require use of a parameter + // to the thread procedure, such as an event per thread to + // be used for synchronization. + // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread + // Could use C++ concurrency for this possibly + threads.emplace_back(CountDownLatchTest::ThreadProcCountDown, &testData); + } + + // Act + nearby::Exception result = countDownLatch->Await(); + + // Assert + EXPECT_EQ(result.value, nearby::Exception::kSuccess); + EXPECT_EQ(count, 3); +} + +TEST_F(CountDownLatchTests, CountDownLatchAwaitTimeoutTimesOut) { + // Arrange + + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(3); + + // Act + nearby::ExceptionOr result = + countDownLatch->Await(absl::Milliseconds(5)); + + sleep(40); + + // Assert + EXPECT_FALSE(result.GetResult()); + // TODO(jfcarroll)I think there's a bug in the shared version of this, it's + // not returning a timeout exception, need to look at it some more. + // EXPECT_EQ(result.GetException().value, + // nearby::Exception::kTimeout); +} + +TEST_F(CountDownLatchTests, CountDownLatchAwaitNoTimeoutSucceeds) { + // Arrange + long volatile count = 0; + + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(3); + + TestData testData{countDownLatch, count}; + + std::vector threads; + + // Setup 3 threads + for (int i = 0; i < 3; i++) { + // TODO: More complex scenarios may require use of a parameter + // to the thread procedure, such as an event per thread to + // be used for synchronization. + threads.emplace_back(CountDownLatchTest::ThreadProcAwait, &testData); + } + + for (auto &thread : threads) { + thread.join(); + } + // Act + nearby::ExceptionOr result = + countDownLatch->Await(absl::Milliseconds(100)); + + // Assert + EXPECT_TRUE(result.GetResult()); + EXPECT_EQ(result.GetException().value, nearby::Exception::kSuccess); + EXPECT_EQ(count, 3); +} + +void test(std::string str) { + std::cout << str << std::endl; + return; +} + +TEST_F(CountDownLatchTests, CountDownLatchCountDownBeforeAwaitSucceeds) { + // Arrange + long volatile count = 0; + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(1); + + TestData testData{countDownLatch, count}; + std::thread thread(CountDownLatchTest::ThreadProcCountDown, &testData); + + // Act + countDownLatch->CountDown(); // This countdown occurs before the thread has a + // chance to run + // Assert + EXPECT_EQ(count, 1); +} + diff --git a/internal/platform/implementation/linux/future.h b/internal/platform/implementation/linux/future.h new file mode 100644 index 00000000..2f0bcab9 --- /dev/null +++ b/internal/platform/implementation/linux/future.h @@ -0,0 +1,48 @@ +// 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_FUTURE_H_ +#define PLATFORM_IMPL_LINUX_FUTURE_H_ + +#include "internal/platform/implementation/future.h" + +namespace nearby { +namespace linux { + +// A Future represents the result of an asynchronous computation. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html +template +class Future : public api::Future { + public: + // TODO(b/184975123): replace with real implementation. + ~Future() override = default; + + // throws Exception::kInterrupted, Exception::kExecution + // TODO(b/184975123): replace with real implementation. + ExceptionOr Get() override { return ExceptionOr{Exception::kFailed}; } + + // throws Exception::kInterrupted, Exception::kExecution + // throws Exception::kTimeout if timeout is exceeded while waiting for + // result. + // TODO(b/184975123): replace with real implementation. + ExceptionOr Get(absl::Duration timeout) override { + return ExceptionOr{Exception::kFailed}; + } +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_FUTURE_H_ diff --git a/internal/platform/implementation/linux/log_message.cc b/internal/platform/implementation/linux/log_message.cc new file mode 100644 index 00000000..53d16a47 --- /dev/null +++ b/internal/platform/implementation/linux/log_message.cc @@ -0,0 +1,71 @@ +// 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/log_message.h" + +#include + +#include "strings/strappendv.h" + +namespace nearby { +namespace linux { + +api::LogMessage::Severity min_log_severity_ = api::LogMessage::Severity::kInfo; + +inline absl::LogSeverity ConvertSeverity(api::LogMessage::Severity severity) { + switch (severity) { + // api::LogMessage::Severity kVerbose and kInfo is mapped to + // absl::LogSeverity kInfo since absl::LogSeverity doesn't have kVerbose + // level. + case api::LogMessage::Severity::kVerbose: + case api::LogMessage::Severity::kInfo: + return absl::LogSeverity::kInfo; + case api::LogMessage::Severity::kWarning: + return absl::LogSeverity::kWarning; + case api::LogMessage::Severity::kError: + return absl::LogSeverity::kError; + case api::LogMessage::Severity::kFatal: + return absl::LogSeverity::kFatal; + } +} + +LogMessage::LogMessage(const char* file, int line, Severity severity) + : log_streamer_(ConvertSeverity(severity), file, line) {} + +LogMessage::~LogMessage() = default; + +void LogMessage::Print(const char* format, ...) { + va_list ap; + va_start(ap, format); + std::string result; + strings::StrAppendV(&result, format, ap); + log_streamer_.stream() << result; + va_end(ap); +} + +std::ostream& LogMessage::Stream() { return log_streamer_.stream(); } + +} // namespace linux + +namespace api { + +void LogMessage::SetMinLogSeverity(Severity severity) { + windows::min_log_severity_ = severity; +} + +bool LogMessage::ShouldCreateLogMessage(Severity severity) { + return severity >= windows::min_log_severity_; +} +} // namespace api +} // namespace nearby diff --git a/internal/platform/implementation/linux/log_message.h b/internal/platform/implementation/linux/log_message.h new file mode 100644 index 00000000..bd06ab79 --- /dev/null +++ b/internal/platform/implementation/linux/log_message.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_LOG_MESSAGE_H_ +#define PLATFORM_IMPL_LINUX_LOG_MESSAGE_H_ + +#include "glog/logging.h" +#include "internal/platform/implementation/log_message.h" + +namespace nearby { +namespace linux { + +// See documentation in +// cpp/platform/api/log_message.h +class LogMessage : public api::LogMessage { + public: + LogMessage(const char* file, int line, Severity severity); + ~LogMessage() override; + + void Print(const char* format, ...) override; + + std::ostream& Stream() override; + + private: + google::LogMessage log_streamer_; + static api::LogMessage::Severity min_log_severity_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_LOG_MESSAGE_H_ diff --git a/internal/platform/implementation/linux/thread_pool.cc b/internal/platform/implementation/linux/thread_pool.cc index 40dec614..2eb1fa12 100644 --- a/internal/platform/implementation/linux/thread_pool.cc +++ b/internal/platform/implementation/linux/thread_pool.cc @@ -56,7 +56,7 @@ ThreadPool::ThreadPool(std::unique_ptr> &thread_pool, i RunNextTask(); // Possibly don't need but here to prevent 100% usage for loop sleep(300); - } + } }); } } From ea13cbc23711c99167d6cae806414ca3c9f3db76 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Fri, 4 Aug 2023 18:13:05 -0500 Subject: [PATCH 06/13] Implemented Windows implementation compliant Linux `http_loader` The `http_loader` class takes a `WebRequest` with information to send a web request, and sends it. This uses `libcurl`, which is present from installation on most major Linux distrobutions (e.g. Fedora, Ubuntu, PopOS...). If libcurl is not present on the target system, it will need to be installed. --- .../implementation/linux/http_loader.cc | 547 ++++++++++++++++++ .../implementation/linux/http_loader.h | 90 +++ .../implementation/linux/http_loader_test.cc | 51 ++ 3 files changed, 688 insertions(+) create mode 100644 internal/platform/implementation/linux/http_loader.cc create mode 100644 internal/platform/implementation/linux/http_loader.h create mode 100644 internal/platform/implementation/linux/http_loader_test.cc diff --git a/internal/platform/implementation/linux/http_loader.cc b/internal/platform/implementation/linux/http_loader.cc new file mode 100644 index 00000000..8cc4abcb --- /dev/null +++ b/internal/platform/implementation/linux/http_loader.cc @@ -0,0 +1,547 @@ +// 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/http_loader.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/ascii.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr int32_t kSchemaMaximumLength = 10; +constexpr int32_t kHostNameMaximumLength = 256; + +using ::nearby::api::WebResponse; + +} // namespace + +HttpLoader::HttpLoader(const nearby::api::WebRequest &request) : + request_(request), + header_data_(open_memstream(&header_strings_, &header_sizeloc_)), + our_header_data_(nullptr), + response_data_(open_memstream(&response_strings_, &response_sizeloc_)), + curl_(curl_easy_init()) {} + +HttpLoader::~HttpLoader() { + DisconnectWebServer(); +} + +absl::StatusOr HttpLoader::GetResponse() { + absl::Status status; + + status = ParseUrl(); + if (!status.ok()) { + return status; + } + + status = ConnectWebServer(); + if (!status.ok()) { + return status; + } + + // Sends request to web server. + status = SendRequest(); + if (!status.ok()) { + return status; + } + + // Processes response from web server + absl::StatusOr result = ProcessResponse(); + if (!result.ok()) { + return result; + } + + DisconnectWebServer(); + return result; +} + +absl::StatusOr HttpLoader::QueryStatusCode(CURL *file_handle) { + long status_code; + absl::Status status; + status = QueryResponseInfo(file_handle, CURLINFO_RESPONSE_CODE, &status_code); + if (!status.ok()) { + return status; + } + + if (status_code < 0) { + return absl::InternalError("Invalid status code."); + } + + return status_code; +} + +absl::StatusOr HttpLoader::QueryStatusText( + CURL *request_handle) { + absl::StatusOr status; + std::string status_text; + + status = QueryStatusCode(request_handle); + if (!status.ok()) { + return status.status(); + } + + switch (status.value()) { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Lenth Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot!"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Content"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 509: + return "Network Authentication Required"; + default: + return absl::InternalError("Invalid status code."); + } +} + +absl::StatusOr> +HttpLoader::QueryResponseHeaders(CURL *request_handle) { + absl::Status status; + long header_size; + + status = QueryResponseInfo(curl_, CURLINFO_HEADER_SIZE, &header_size); + + if (!status.ok()) { + return status; + } + + std::string headers_string(header_strings_, header_size); + + std::multimap headers; + // Parse headers in response + size_t start = 0; + size_t pos = 0; + while ((pos = headers_string.find("\r\n", start)) != std::string::npos) { + std::string header = headers_string.substr(start, pos - start); + // Get key and value in header + size_t split_pos = 0; + if ((split_pos = header.find(": ")) != std::string::npos) { + std::string key = header.substr(0, split_pos); + std::string value = header.substr(split_pos + 2); + headers.emplace(key, value); + } + + start = pos + 2; + } + + return headers; +} + +const nearby::api::WebRequest& HttpLoader::GetRequest() { + return request_; +} + +size_t HttpLoader::CurlReadCallback(char *buffer, size_t size, size_t nitems, void *userdata) { + size_t write_size_max = size * nitems; + size_t write_amount = 0; + for (const auto &str : reinterpret_cast(userdata)->GetRequest().body) { + if (write_amount == write_size_max) { + break; + } + *(buffer + write_amount) = str; + write_amount++; + } + + return write_amount; +} + +// This function uses the CURL getinfo function to grab info. Each info_level has a different +// type it can return. It would not be feasable to determine the type and return it. +// IT IS UP TO THE CALLER OF THE FUNCTION TO USE THE void* CORRECTLY. +absl::Status HttpLoader::QueryResponseInfo(CURL *request_handle, + CURLINFO info_level, + void *info) { + CURLcode query_result = curl_easy_getinfo(request_handle, info_level, &info); + + if (query_result == CURLE_OK) { + return absl::OkStatus(); + } + + return absl::InvalidArgumentError("Failed to query HTTP information: " + std::string(curl_easy_strerror(query_result))); +} + +absl::Status HttpLoader::ParseUrl() { + CURLU *url_components = curl_url(); + char *schema; + char *host_name; + char *path; + + CURLUcode ret = curl_url_set(url_components, CURLUPART_URL, request_.url.c_str(), CURLU_NON_SUPPORT_SCHEME); + if (ret) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("Invalid URL format: " + std::string(curl_url_strerror(ret))); + } + + ret = curl_url_get(url_components, CURLUPART_SCHEME, &schema, CURLU_URLDECODE | CURLU_URLENCODE | CURLU_DEFAULT_PORT | CURLU_DEFAULT_SCHEME); + if (ret) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("Could not parse URL schema: " + std::string(curl_url_strerror(ret))); + } + + ret = curl_url_get(url_components, CURLUPART_PATH, &path, CURLU_URLDECODE | CURLU_URLENCODE | CURLU_DEFAULT_PORT | CURLU_DEFAULT_SCHEME); + if (ret) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("Could not parse URL path: " + std::string(curl_url_strerror(ret))); + } + + if (!(schema_ == "http" || schema_ == "https")) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("URL supports HTTP and HTTPS only."); + } + + host_ = host_name; + schema_ = schema; + path_ = path; + + if (schema_ == "https") { + is_secure_ = true; + } + + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::OkStatus(); +} + +absl::Status HttpLoader::ConnectWebServer() { + std::vector option_return_codes; + if (curl_) { + curl_ = curl_easy_init(); + header_data_ = open_memstream(&header_strings_, &header_sizeloc_); + response_data_ = open_memstream(&response_strings_, &response_sizeloc_); + } + + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 1L)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_URL, request_.url.c_str())); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_PORT, port_)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_AUTOREFERER, 1L)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1L)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_USERAGENT, "Mozilla/5.0")); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_HEADERDATA, header_data_)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_WRITEDATA, response_data_)); + + // Prepare headers + std::string request_headers; + for (const auto& header : request_.headers) { + struct curl_slist *list = curl_slist_append(our_header_data_, std::string(header.first + ": " + header.second).c_str()); + if (list) { + our_header_data_ = list; + } + } + + if (!request_headers.empty()) { + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, our_header_data_)); + } + + if (request_.method == "GET") { + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1L)); + } + else if (request_.method == "POST") { + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, static_cast(request_.body.size()))); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, request_.body.c_str())); + } + else if (request_.method == "PUT") { + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_UPLOAD, 1L)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_READFUNCTION, CurlReadCallback)); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_READDATA, this)); + option_return_codes.push_back(curl_easy_setopt(curl_, (request_.body.size() < std::numeric_limits::max() ? CURLOPT_INFILESIZE : CURLOPT_INFILESIZE_LARGE), request_.body.size())); + + } + else { + NEARBY_LOGS(ERROR) << "Failed to open internet with error " + << "Invalid request method: " << request_.method << "."; + return absl::FailedPreconditionError("Failed to open internet: Invalid request method."); + } + + for (const auto &ret : option_return_codes) { + if (ret) { + NEARBY_LOGS(ERROR) << "Failed to open internet with error " + << curl_easy_strerror(ret) << "."; + return absl::FailedPreconditionError(absl::StrCat(curl_easy_strerror(ret))); + } + } + + return absl::OkStatus(); +} + +absl::Status HttpLoader::SendRequest() { + + CURLcode ret = curl_easy_perform(curl_); + + if (ret != CURLE_OK) { + NEARBY_LOGS(ERROR) + << "Failed to send request to remote web server with error " + << curl_easy_strerror(ret) << "."; + return absl::FailedPreconditionError(absl::StrCat(curl_easy_strerror(ret))); + } + + return absl::OkStatus(); +} + +absl::StatusOr HttpLoader::ProcessResponse() { + absl::Status status; + WebResponse web_response; + auto status_code = QueryStatusCode(curl_); + if (!status_code.ok()) { + return absl::InternalError("Failed to read HTTP status"); + } + + web_response.status_code = status_code.value(); + auto status_text = QueryStatusText(curl_); + if (!status_text.ok()) { + return absl::InternalError("Failed to read HTTP status"); + } + + web_response.status_text = status_text.value(); + auto headers = QueryResponseHeaders(curl_); + if (!headers.ok()) { + headers.status(); + } + web_response.headers = *headers; + + curl_off_t download_size; + + CURLcode ret = curl_easy_getinfo(curl_, CURLINFO_SIZE_DOWNLOAD_T, &download_size); + + if (ret) { + if (download_size != 0) { + // Append data to response + web_response.body.assign(response_strings_, download_size); + } else { + NEARBY_LOGS(ERROR) + << "Failed to read response from remote web server with error " + << curl_easy_strerror(ret) << "."; + return absl::FailedPreconditionError(absl::StrCat(curl_easy_strerror(ret))); + } + } + + status = HTTPCodeToStatus(web_response.status_code, web_response.status_text); + if (!status.ok()) { + return status; + } + + return web_response; +} + +void HttpLoader::DisconnectWebServer() { + fclose(header_data_); + header_data_ = nullptr; + delete header_strings_; + header_strings_ = nullptr; + curl_easy_cleanup(curl_); + curl_ = nullptr; + curl_slist_free_all(our_header_data_); + our_header_data_ = nullptr; + fclose(response_data_); + response_data_ = nullptr; + delete response_strings_; + response_strings_ = nullptr; +} + +absl::Status HttpLoader::HTTPCodeToStatus(int status_code, + absl::string_view status_message) { + switch (status_code) { + case 400: + return absl::InvalidArgumentError(status_message); + case 401: + return absl::UnauthenticatedError(status_message); + case 403: + return absl::PermissionDeniedError(status_message); + case 404: + return absl::NotFoundError(status_message); + case 409: + return absl::AbortedError(status_message); + case 416: + return absl::OutOfRangeError(status_message); + case 429: + return absl::ResourceExhaustedError(status_message); + case 499: + return absl::CancelledError(status_message); + case 504: + return absl::DeadlineExceededError(status_message); + case 501: + return absl::UnimplementedError(status_message); + case 503: + return absl::UnavailableError(status_message); + default: + break; + } + if (status_code >= 200 && status_code < 300) { + return absl::OkStatus(); + } else if (status_code >= 400 && status_code < 500) { + return absl::FailedPreconditionError(status_message); + } else if (status_code >= 500 && status_code < 600) { + return absl::InternalError(status_message); + } + return absl::UnknownError(status_message); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/http_loader.h b/internal/platform/implementation/linux/http_loader.h new file mode 100644 index 00000000..97ca5507 --- /dev/null +++ b/internal/platform/implementation/linux/http_loader.h @@ -0,0 +1,90 @@ +// Copyright 2022 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_HTTP_LOADER_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_HTTP_LOADER_H_ + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/http_loader.h" + +namespace nearby { +namespace linux { + +// HttpLoader is used to get HTTP response from remote server. +// +// HttpLoader gets HTTP request information from caller, and calling Windows +// WinInet APIs to get HTTP response. The platform handles HTTP/HTTPS sessions. +class HttpLoader { + public: + explicit HttpLoader(const nearby::api::WebRequest& request); + ~HttpLoader(); + + absl::StatusOr GetResponse(); + + const nearby::api::WebRequest& GetRequest(); + + private: + // Defines the buffer size. It is used to init a buffer for receiving HTTP + // response. The unit is byte. + static constexpr int kReceiveBufferSize = 8 * 1024; + static size_t CurlReadCallback(char *buffer, size_t size, size_t nitems, void *userdata); + + absl::Status ConnectWebServer(); + absl::Status SendRequest(); + absl::StatusOr ProcessResponse(); + void DisconnectWebServer(); + + absl::StatusOr QueryStatusCode(CURL *file_handle); + absl::StatusOr QueryStatusText(CURL *request_handle); + absl::StatusOr> QueryResponseHeaders( + CURL *request_handle); + absl::Status QueryResponseInfo(CURL *request_handle, CURLINFO info_level, + void *info); + + absl::Status ParseUrl(); + + // Converts HTTP status code to absl Status. + // + // @param status_code HTTP status code, such 200, 404 etc. + // @param status_message short description of the status code. + // @return converted absl status. + absl::Status HTTPCodeToStatus(int status_code, + absl::string_view status_message); + + nearby::api::WebRequest request_; + std::string host_; + std::string path_; + std::string schema_; + bool is_secure_ = false; + int port_ = 80; + + FILE *header_data_; + char *header_strings_; + size_t header_sizeloc_; + struct curl_slist *our_header_data_; + FILE *response_data_; + char *response_strings_; + size_t response_sizeloc_; + CURL *curl_; +}; + +} // namespace linux +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_HTTP_LOADER_H_ diff --git a/internal/platform/implementation/linux/http_loader_test.cc b/internal/platform/implementation/linux/http_loader_test.cc new file mode 100644 index 00000000..800ed6a9 --- /dev/null +++ b/internal/platform/implementation/linux/http_loader_test.cc @@ -0,0 +1,51 @@ +// Copyright 2022 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/http_loader.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace linux { +namespace { +using ::nearby::api::WebRequest; + +TEST(HttpLoader, DISABLED_TestGetUrl) { + WebRequest request; + request.url = "https://www.google.com?id=456#fragment"; + request.method = "GET"; + auto response = HttpLoader(request).GetResponse(); + ASSERT_TRUE(response.ok()); + EXPECT_EQ(response->status_code, 200); +} + +TEST(HttpLoader, DISABLED_TestGetNotExistingUrl) { + WebRequest request; + request.url = "https://www.abcdefgabcdefg.com"; + request.method = "GET"; + EXPECT_FALSE(HttpLoader(request).GetResponse().ok()); +} + +TEST(HttpLoader, DISABLED_TestInvalidUrl) { + WebRequest request; + request.url = "https:/www.abcdefgabcdefg.com/name?id=456"; + request.method = "GET"; + EXPECT_FALSE(HttpLoader(request).GetResponse().ok()); +} + +} // namespace +} // namespace linux +} // namespace nearby From 1230cbb5d33235394b917b2d995e1dd4010e49e2 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Sun, 6 Aug 2023 16:39:57 -0500 Subject: [PATCH 07/13] Added implementation for test_utils --- .../implementation/linux/test_utils.cc | 37 ++++++++++++++++ .../implementation/linux/test_utils.h | 43 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 internal/platform/implementation/linux/test_utils.cc create mode 100644 internal/platform/implementation/linux/test_utils.h diff --git a/internal/platform/implementation/linux/test_utils.cc b/internal/platform/implementation/linux/test_utils.cc new file mode 100644 index 00000000..209a8904 --- /dev/null +++ b/internal/platform/implementation/linux/test_utils.cc @@ -0,0 +1,37 @@ +// 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/test_utils.h" +#include "internal/platform/implementation/linux/device_info.h" + +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" + +namespace test_utils { +std::wstring StringToWideString(const std::string& s) { + std::wstring_convert> converter; + return converter.from_bytes(s); +} + +std::string GetPayloadPath(nearby::PayloadId payload_id) { + std::filesystem::path path = nearby::linux::DeviceInfo().GetDownloadPath().value_or(std::string(getenv("HOME")).append("Downloads")); + + return path.string(); +} + +} // namespace test_utils diff --git a/internal/platform/implementation/linux/test_utils.h b/internal/platform/implementation/linux/test_utils.h new file mode 100644 index 00000000..5abfb07f --- /dev/null +++ b/internal/platform/implementation/linux/test_utils.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_TEST_UTILS_H_ +#define PLATFORM_IMPL_LINUX_TEST_UTILS_H_ + +#include + +#include "internal/platform/payload_id.h" + +#define TEST_BUFFER_SIZE 256 +#define TEST_PAYLOAD_ID 64l +#define TEST_STRING \ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas " \ + "eleifend nisl at magna maximus, id finibus mauris ultrices. Mauris " \ + "interdum efficitur turpis eget auctor. Nullam commodo metus et ante " \ + "bibendum molestie. Donec iaculis ante nec diam rutrum egestas. Proin " \ + "maximus metus luctus rutrum congue. Integer et eros nunc. Etiam purus " \ + "neque, tincidunt eu elementum in, pharetra sit amet magna. Quisque " \ + "consequat aliquam aliquam. Vestibulum ante ipsum primis in faucibus orci " \ + "luctus et ultrices posuere cubilia curae; Maecenas a semper eros, a " \ + "auctor mi. In luctus diam sem, eu pretium nisi porttitor ac. Sed cursus, " \ + "arcu in bibendum feugiat, leo erat finibus massa, ut tincidunt magna nunc " \ + "eu tellus. Cras feugiat ornare vestibulum. Nullam at ipsum vestibulum " \ + "sapien luctus dictum ac vel ligula." + +namespace test_utils { +std::wstring StringToWideString(const std::string& s); +std::string GetPayloadPath(nearby::PayloadId payload_id); +} // namespace test_utils + +#endif // PLATFORM_IMPL_LINUX_TEST_UTILS_H_ From 21d3dfca4e47c211aa3bac197dda906e6f35fda5 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Sun, 6 Aug 2023 18:28:46 -0500 Subject: [PATCH 08/13] Implemented scheduled_executor --- .../linux/scheduled_executor.cc | 82 ++++++++++++++ .../implementation/linux/scheduled_executor.h | 100 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 internal/platform/implementation/linux/scheduled_executor.cc create mode 100644 internal/platform/implementation/linux/scheduled_executor.h diff --git a/internal/platform/implementation/linux/scheduled_executor.cc b/internal/platform/implementation/linux/scheduled_executor.cc new file mode 100644 index 00000000..ff8b3a1d --- /dev/null +++ b/internal/platform/implementation/linux/scheduled_executor.cc @@ -0,0 +1,82 @@ +// 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/scheduled_executor.h" + +#include +#include +#include + +#include "absl/time/time.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +ScheduledExecutor::ScheduledExecutor() + : executor_(std::make_unique()), + shut_down_(false) {} + +// Cancelable is kept both in the executor context, and in the caller context. +// We want Cancelable to live until both caller and executor are done with it. +// Exclusive ownership model does not work for this case; +// using std:shared_ptr<> instead of std::unique_ptr<>. +std::shared_ptr ScheduledExecutor::Schedule( + Runnable&& runnable, absl::Duration duration) { + if (shut_down_) { + NEARBY_LOGS(ERROR) << __func__ + << ": Attempt to Schedule on a shut down executor."; + + return nullptr; + } + + // Cleans completed tasks + std::remove_if( + scheduled_tasks_.begin(), scheduled_tasks_.end(), + [](std::shared_ptr& task) { return task->IsDone(); }); + + std::shared_ptr task = + std::make_shared(std::move(runnable), duration); + + scheduled_tasks_.push_back(task); + executor_->Execute([task]() { task->Start(); }); + return task; +} + +void ScheduledExecutor::Execute(Runnable&& runnable) { + if (shut_down_) { + NEARBY_LOGS(ERROR) << __func__ + << ": Attempt to Execute on a shut down executor."; + return; + } + + executor_->Execute(std::move(runnable)); +} + +void ScheduledExecutor::Shutdown() { + if (!shut_down_) { + shut_down_ = true; + for (auto& task : scheduled_tasks_) { + task->Cancel(); + } + + scheduled_tasks_.clear(); + executor_->Shutdown(); + return; + } + NEARBY_LOGS(ERROR) << __func__ + << ": Attempt to Shutdown on a shut down executor."; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/scheduled_executor.h b/internal/platform/implementation/linux/scheduled_executor.h new file mode 100644 index 00000000..4dffef8b --- /dev/null +++ b/internal/platform/implementation/linux/scheduled_executor.h @@ -0,0 +1,100 @@ +// 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_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "absl/synchronization/notification.h" +#include "absl/time/time.h" +#include "internal/platform/implementation/cancelable.h" +#include "internal/platform/implementation/scheduled_executor.h" +#include "internal/platform/implementation/linux/executor.h" + +namespace nearby { +namespace linux { + +#define TIMER_NAME_BUFFER_SIZE 64 + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor : public api::ScheduledExecutor { + public: + ScheduledExecutor(); + + ~ScheduledExecutor() override = default; + + // Cancelable is kept both in the executor context, and in the caller context. + // We want Cancelable to live until both caller and executor are done with it. + // Exclusive ownership model does not work for this case; + // using std:shared_ptr<> instead if std::unique_ptr<>. + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration duration) override; + + // Executes the runnable task immedately. + void Execute(Runnable&& runnable) override; + + // Shutdowns the executor, all scheduled task will be cancelled. + void Shutdown() override; + + private: + class ScheduledTask : public api::Cancelable { + public: + explicit ScheduledTask(Runnable&& task, absl::Duration duration) + : task_(std::move(task)), duration_(duration) {} + + bool Cancel() override { + if (is_executed_ || is_cancelled_) { + return false; + } + + is_cancelled_ = true; + notification_.Notify(); + return true; + }; + + void Start() { + if (is_executed_ || + notification_.WaitForNotificationWithTimeout(duration_)) { + return; + } + + is_executed_ = true; + task_(); + } + + bool IsDone() const { return is_cancelled_ || is_executed_; } + + private: + Runnable task_; + absl::Duration duration_; + absl::Notification notification_; + bool is_cancelled_ = false; + bool is_executed_ = false; + }; + + std::unique_ptr executor_ = nullptr; + std::vector> scheduled_tasks_; + std::atomic_bool shut_down_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ From e6f52fba982b196625f5e226611fab5d628072b8 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Mon, 7 Aug 2023 13:58:39 -0500 Subject: [PATCH 09/13] Implemented preferences_manager and helpers --- .../linux/preferences_manager.cc | 282 ++++++++++++++++++ .../linux/preferences_manager.h | 141 +++++++++ .../linux/preferences_manager_test.cc | 189 ++++++++++++ .../linux/preferences_repository.cc | 153 ++++++++++ .../linux/preferences_repository.h | 48 +++ .../linux/preferences_repository_test.cc | 161 ++++++++++ 6 files changed, 974 insertions(+) create mode 100644 internal/platform/implementation/linux/preferences_manager.cc create mode 100644 internal/platform/implementation/linux/preferences_manager.h create mode 100644 internal/platform/implementation/linux/preferences_manager_test.cc create mode 100644 internal/platform/implementation/linux/preferences_repository.cc create mode 100644 internal/platform/implementation/linux/preferences_repository.h create mode 100644 internal/platform/implementation/linux/preferences_repository_test.cc diff --git a/internal/platform/implementation/linux/preferences_manager.cc b/internal/platform/implementation/linux/preferences_manager.cc new file mode 100644 index 00000000..9d174c09 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager.cc @@ -0,0 +1,282 @@ +// 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/preferences_manager.h" + +#include // NOLINT(build/c++17) +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/linux/preferences_repository.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace { +using json = ::nlohmann::json; +} // namespace + +PreferencesManager::PreferencesManager(absl::string_view file_path) + : api::PreferencesManager(file_path) { + std::optional path = + nearby::api::ImplementationPlatform::CreateDeviceInfo() + ->GetLocalAppDataPath(); + if (!path.has_value()) { + path = std::filesystem::temp_directory_path(); + } + + std::filesystem::path full_path = *path / std::string(file_path); + preferences_repository_ = + std::make_unique(full_path.string()); + value_ = preferences_repository_->LoadPreferences(); +} + +bool PreferencesManager::Set(absl::string_view key, const json& value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetBoolean(absl::string_view key, bool value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInteger(absl::string_view key, int value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInt64(absl::string_view key, int64_t value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetString(absl::string_view key, + absl::string_view value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, absl::StrCat(value)); +} + +bool PreferencesManager::SetBooleanArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetIntegerArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetInt64Array(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetStringArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetTime(absl::string_view key, absl::Time value) { + // Save time as nanos + absl::MutexLock lock(&mutex_); + int64_t tt = absl::ToUnixNanos(value); + if (value_[absl::StrCat(key)] == tt) { + return false; + } + + value_[absl::StrCat(key)] = tt; + return Commit(); +} + +// Get JSON value. +json PreferencesManager::Get(absl::string_view key, + const json& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +bool PreferencesManager::GetBoolean(absl::string_view key, + bool default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int PreferencesManager::GetInteger(absl::string_view key, + int default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int64_t PreferencesManager::GetInt64(absl::string_view key, + int64_t default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::string PreferencesManager::GetString( + absl::string_view key, const std::string& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::vector PreferencesManager::GetBooleanArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetIntegerArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetInt64Array( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetStringArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +absl::Time PreferencesManager::GetTime(absl::string_view key, + absl::Time default_value) const { + absl::MutexLock lock(&mutex_); + auto result = value_.find(absl::StrCat(key)); + if (result == value_.end()) { + return default_value; + } + + return absl::FromUnixNanos(result->get()); +} + +// Removes preferences +void PreferencesManager::Remove(absl::string_view key) { + absl::MutexLock lock(&mutex_); + value_.erase(absl::StrCat(key)); +} + +// Private methods + +// Writes data to storage. +bool PreferencesManager::Commit() { + if (!preferences_repository_->SavePreferences(value_)) { + NEARBY_LOGS(ERROR) << "Failed to save preference." << std::endl; + return false; + } + return true; +} + +bool PreferencesManager::SetValue(absl::string_view key, const json& value) { + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + value_ = json::object(); + } + + if (value_[absl::StrCat(key)] == value) { + return false; + } + + value_[absl::StrCat(key)] = value; + return Commit(); +} + +template +T PreferencesManager::GetValue(absl::string_view key, + const T& default_value) const { + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + return default_value; + } + + auto it = value_.find(absl::StrCat(key)); + if (it == value_.end()) { + return default_value; + } + return it->get(); +} + +template +bool PreferencesManager::SetArrayValue(absl::string_view key, + absl::Span value) { + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + value_ = json::object(); + } + + json array_value = json::array(); + for (const T& item_value : value) { + array_value.push_back(item_value); + } + + if (value_[absl::StrCat(key)] == array_value) { + return false; + } + + value_[absl::StrCat(key)] = array_value; + return Commit(); +} + +template +std::vector PreferencesManager::GetArrayValue( + absl::string_view key, absl::Span default_value) const { + std::vector result; + + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + + for (const T& value : default_value) { + result.push_back(value); + } + return result; + } + + auto array_value = value_.find(absl::StrCat(key)); + if (array_value == value_.end() || !array_value->is_array()) { + for (const T& value : default_value) { + result.push_back(value); + } + return result; + } + + auto it = array_value->begin(); + while (it != array_value->end()) { + result.push_back(it->get()); + ++it; + } + + return result; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_manager.h b/internal/platform/implementation/linux/preferences_manager.h new file mode 100644 index 00000000..9aaa3006 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager.h @@ -0,0 +1,141 @@ +// 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. + +#ifndef PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_ +#define PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_ + +#include + +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/preferences_manager.h" +#include "internal/platform/implementation/linux/preferences_repository.h" + +namespace nearby { +namespace linux { + +// Sets and gets preference settings from the application. +// Preferences are persistent storage for application settings, it is key/value +// based settings. Application components can observe the interested preference +// change by the observer. +class PreferencesManager : public api::PreferencesManager { + public: + explicit PreferencesManager(absl::string_view path); + + // Sets values + + bool Set(absl::string_view key, const nlohmann::json& value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBoolean(absl::string_view key, bool value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInteger(absl::string_view key, int value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64(absl::string_view key, int64_t value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetString(absl::string_view key, absl::string_view value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBooleanArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetIntegerArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64Array(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetStringArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetTime(absl::string_view key, absl::Time value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Gets values + nlohmann::json Get(absl::string_view key, + const nlohmann::json& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool GetBoolean(absl::string_view key, bool default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int GetInteger(absl::string_view key, int default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int64_t GetInt64(absl::string_view key, int64_t default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::string GetString(absl::string_view key, + const std::string& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + std::vector GetBooleanArray(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetIntegerArray( + absl::string_view key, absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetInt64Array(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetStringArray( + absl::string_view key, + absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + absl::Time GetTime(absl::string_view key, + absl::Time default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Removes preferences + void Remove(absl::string_view key) override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Writes data to storage. + bool Commit() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool SetValue(absl::string_view key, const nlohmann::json& value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + T GetValue(absl::string_view key, const T& default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + bool SetArrayValue(absl::string_view key, absl::Span value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + std::vector GetArrayValue(absl::string_view key, + absl::Span default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + nlohmann::json value_ ABSL_GUARDED_BY(mutex_); + std::unique_ptr preferences_repository_ + ABSL_GUARDED_BY(mutex_); + + mutable absl::Mutex mutex_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_ diff --git a/internal/platform/implementation/linux/preferences_manager_test.cc b/internal/platform/implementation/linux/preferences_manager_test.cc new file mode 100644 index 00000000..064e4acf --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager_test.cc @@ -0,0 +1,189 @@ +// 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/preferences_manager.h" + +#include + +#include +#include // NOLINT(build/c++17) +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace { +using json = ::nlohmann::json; +constexpr absl::Duration kTimeOut = absl::Milliseconds(200); +constexpr char kPreferencesFilePath[] = "Google/Nearby/Sharing"; +} // namespace + + +TEST(PreferencesManager, CorruptedConfigFile) { + std::filesystem::path settingsPath = + std::filesystem::temp_directory_path(); + std::ofstream output_stream{settingsPath / "preferences.json"}; + output_stream << "CORRUPTED" << std::endl; + + NEARBY_LOGS(INFO) << "Loading preferences from: " << settingsPath.string(); + EXPECT_EQ(PreferencesManager(settingsPath.string()).GetInteger("data", 100), + 100); +} + +TEST(PreferencesManager, ValidConfigFile) { + std::filesystem::path settingsPath = + std::filesystem::temp_directory_path(); + std::ofstream output_stream{settingsPath / "preferences.json"}; + output_stream << "{\"data\":8, \"name\": \"Valid\"}" << std::endl; + output_stream.close(); + + NEARBY_LOGS(INFO) << "Loading preferences from: " << settingsPath.string(); + EXPECT_EQ(PreferencesManager(settingsPath.string()).GetInteger("data", 100), + 8); +} + +TEST(PreferencesManager, SetAndGetBoolean) { + std::string bool_key = "bool_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_TRUE(pm.GetBoolean(bool_key, true)); + pm.SetBoolean(bool_key, true); + EXPECT_TRUE(pm.GetBoolean(bool_key, false)); +} + +TEST(PreferencesManager, SetAndGetInt) { + std::string int_key = "int_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetInteger(int_key, 1234), 1234); + pm.SetInteger(int_key, 6789); + EXPECT_EQ(pm.GetInteger(int_key, 0), 6789); +} + +TEST(PreferencesManager, SetAndGetInt64) { + std::string int64_key = "int64_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetInt64(int64_key, 1234), 1234); + pm.SetInt64(int64_key, 56789); + EXPECT_EQ(pm.GetInt64(int64_key, 0), 56789); +} + +TEST(PreferencesManager, SetAndGetString) { + std::string string_key = "string_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetString(string_key, "abcd"), "abcd"); + pm.SetString(string_key, "this is a test string"); + EXPECT_EQ(pm.GetString(string_key, ""), "this is a test string"); +} + +TEST(PreferencesManager, SetAndGetTime) { + std::string time_key = "time_key"; + PreferencesManager pm(kPreferencesFilePath); + absl::Time time = absl::Now(); + EXPECT_EQ(pm.GetTime(time_key, time), time); + pm.SetTime(time_key, time); + absl::Time ret = pm.GetTime(time_key, absl::Now()); + EXPECT_EQ(absl::ToUnixNanos(ret), absl::ToUnixNanos(time)); +} + +TEST(PreferencesManager, MultipleSetAndGetString) { + std::string string1_key = "string1_key"; + PreferencesManager pm(kPreferencesFilePath); + pm.SetString(string1_key, "this is first string"); + pm.SetString(string1_key, "this is second string"); + EXPECT_EQ(pm.GetString(string1_key, ""), "this is second string"); +} + +TEST(PreferencesManager, SetAndGetValue) { + std::string value_key = "value_key"; + PreferencesManager pm(kPreferencesFilePath); + json value = {{"key1", "value1"}, {"key2", "value2"}}; + EXPECT_TRUE(pm.Get(value_key, json()).empty()); + pm.Set(value_key, value); + auto result = pm.Get(value_key, json()); + ASSERT_FALSE(result.empty()); + auto val = result["key2"]; + EXPECT_EQ(val.get(), "value2"); +} + +TEST(PreferencesManager, SetAndGetBooleanArray) { + std::string bool_array_key = "bool_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto default_result = + pm.GetBooleanArray(bool_array_key, absl::Span({true})); + EXPECT_EQ(default_result[0], true); + pm.SetBooleanArray(bool_array_key, + absl::Span({true, false, false, true, true})); + auto result = + pm.GetBooleanArray(bool_array_key, absl::Span({true})); + EXPECT_EQ(result[2], false); + EXPECT_EQ(result[3], true); +} + +TEST(PreferencesManager, SetAndGetIntArray) { + std::string int_array_key = "int_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetIntegerArray(int_array_key, std::vector{5, 6}); + EXPECT_EQ(result[1], 6); + pm.SetIntegerArray(int_array_key, std::vector{1, 7, 4, 10, 12}); + result = pm.GetIntegerArray(int_array_key, std::vector{11, 17, 14, 110}); + EXPECT_EQ(result[3], 10); +} + +TEST(PreferencesManager, SetAndGetInt64Array) { + std::string int64_array_key = "int64_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetInt64Array(int64_array_key, std::vector{99}); + EXPECT_EQ(result[0], 99); + pm.SetInt64Array(int64_array_key, std::vector{16, 7, 64, 100, 12}); + result = pm.GetInt64Array(int64_array_key, std::vector{1, 5, 6, 12}); + EXPECT_EQ(result[3], 100); + EXPECT_EQ(result[4], 12); +} + +TEST(PreferencesManager, SetAndGetStringArray) { + std::string string_array_key = "string_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetStringArray(string_array_key, + std::vector{"value", "morning"}); + EXPECT_EQ(result[1], "morning"); + pm.SetStringArray( + string_array_key, + std::vector{"one", "two", "three", "four", "five"}); + result = pm.GetStringArray(string_array_key, + std::vector{"good", "morning"}); + EXPECT_EQ(result[3], "four"); +} + +TEST(PreferencesManager, RemoveKey) { + std::string string_key = "string_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + pm.SetString(string_key, "remove key"); + pm.Remove(string_key); + auto result = pm.GetString(string_key, "default key"); + EXPECT_EQ(result, "default key"); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_repository.cc b/internal/platform/implementation/linux/preferences_repository.cc new file mode 100644 index 00000000..c6e3d2f3 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_repository.cc @@ -0,0 +1,153 @@ +// Copyright 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/preferences_repository.h" + +#include +#include // NOLINT(build/c++17) +#include +#include + +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace { +using json = ::nlohmann::json; + +constexpr char kPreferencesFileName[] = "preferences.json"; +constexpr char kPreferencesBackupFileName[] = "preferences_bak.json"; + +} // namespace + +json PreferencesRepository::LoadPreferences() { + absl::MutexLock lock(&mutex_); + std::optional preferences = AttemptLoad(); + if (preferences.has_value()) { + // The top level root should be an object, if it's not then something went + // wrong or the file was corrupted. + if (!preferences.value().is_object()) { + NEARBY_LOGS(ERROR) << "Preferences loaded was not a valid object: " + << preferences.value().dump(4); + + return json::object(); + } + + return preferences.value(); + } + + NEARBY_LOGS(ERROR) << "Could not load preferences file, trying backup."; + + // In the future we should switch to using a transaction log or another + // stable method which doesn't pose a risk of losing settings + preferences = RestoreFromBackup(); + if (preferences.has_value()) { + NEARBY_LOGS(ERROR) << "Successfully recovered from backup."; + return preferences.value(); + } + + NEARBY_LOGS(ERROR) << "Failed to load preferences file from back up."; + + return json::object(); +} + +bool PreferencesRepository::SavePreferences(json preferences) { + absl::MutexLock lock(&mutex_); + try { + std::filesystem::path path = path_; + if (!std::filesystem::exists(path) && + !std::filesystem::create_directories(path)) { + NEARBY_LOGS(ERROR) << "Failed to create preferences path."; + return false; + } + + std::filesystem::path full_name = path / kPreferencesFileName; + std::filesystem::path full_name_backup = path / kPreferencesBackupFileName; + + // Create a backup without moving the bytes on disk + if (std::filesystem::exists(full_name)) { + NEARBY_LOGS(INFO) << "Making backup of preferences file."; + std::filesystem::rename(full_name, full_name_backup); + } + + std::ofstream preferences_file(full_name.c_str()); + preferences_file << preferences; + preferences_file.close(); + + // Make sure the file wasn't saved in a corrupted state + if (!AttemptLoad().has_value()) { + NEARBY_LOGS(ERROR) << "Preferences saved to disk in corrupted state. " + "Restoring from backup."; + + if (!RestoreFromBackup().has_value()) { + NEARBY_LOGS(ERROR) << "Failed to restore preferences file."; + return false; + } + } + } catch (const std::exception& e) { + NEARBY_LOGS(ERROR) << "Failed to save preferences file: " << e.what(); + return false; + } + + return true; +} + +std::optional PreferencesRepository::AttemptLoad() { + std::filesystem::path path = path_; + std::filesystem::path full_name = path / kPreferencesFileName; + if (!std::filesystem::exists(path) || !std::filesystem::exists(full_name)) { + return std::nullopt; + } + + try { + std::ifstream preferences_file(full_name.c_str()); + if (!preferences_file.good()) { + return std::nullopt; + } + + json preferences = json::parse(preferences_file, nullptr, false); + preferences_file.close(); + + if (preferences.is_discarded()) { + NEARBY_LOGS(ERROR) << "Preferences file corrupted."; + return std::nullopt; + } + + return preferences; + } catch (const std::exception& e) { + NEARBY_LOGS(ERROR) << "Exception while loading preferences: " << e.what(); + return std::nullopt; + } +} + +std::optional PreferencesRepository::RestoreFromBackup() { + std::filesystem::path path = path_; + std::filesystem::path full_name = path / kPreferencesFileName; + std::filesystem::path full_name_backup = path / kPreferencesBackupFileName; + + if (!std::filesystem::exists(full_name_backup)) { + NEARBY_LOGS(WARNING) + << "Backup requested but no backup preferences file found."; + return std::nullopt; + } + + std::filesystem::rename(full_name_backup, full_name); + + NEARBY_LOGS(INFO) << "Attempting load from backup preferences."; + return AttemptLoad(); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_repository.h b/internal/platform/implementation/linux/preferences_repository.h new file mode 100644 index 00000000..b0b23345 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_repository.h @@ -0,0 +1,48 @@ +// 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_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_ +#define PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_ + +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace linux { + +class PreferencesRepository { + public: + explicit PreferencesRepository(absl::string_view path) : path_(path) {} + + nlohmann::json LoadPreferences() ABSL_LOCKS_EXCLUDED(&mutex_); + bool SavePreferences(nlohmann::json preferences) ABSL_LOCKS_EXCLUDED(&mutex_); + + std::optional AttemptLoad(); + std::optional RestoreFromBackup(); + + private: + absl::Mutex mutex_; + const std::string path_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_ diff --git a/internal/platform/implementation/linux/preferences_repository_test.cc b/internal/platform/implementation/linux/preferences_repository_test.cc new file mode 100644 index 00000000..3e380bb9 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_repository_test.cc @@ -0,0 +1,161 @@ +// Copyright 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/preferences_repository.h" + +#include // NOLINT(build/c++17) +#include +#include + +#include "gtest/gtest.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/device_info.h" +#include "internal/platform/implementation/platform.h" + +namespace nearby { +namespace linux { +namespace { + +using json = ::nlohmann::json; + +constexpr char kPreferencesFileName[] = "preferences.json"; +constexpr char kPreferencesBackupFileName[] = "preferences_bak.json"; +constexpr char kPreferencesPath[] = "Google/Nearby/Sharing"; + +TEST(PreferencesRepository, LoadWithBadPath) { + PreferencesRepository preferences_repository{"c:\\users\\a\\b\\c\\d\\e\\f"}; + json result = preferences_repository.LoadPreferences(); + EXPECT_TRUE(result.empty()); +} + +TEST(PreferencesRepository, RecoverFromBadPreferences) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + std::ofstream pref_file(full_name.c_str()); + pref_file << "\"Bad top level object\""; + pref_file.close(); + + PreferencesRepository preferences_repository{full_path.string()}; + EXPECT_EQ(preferences_repository.LoadPreferences(), json::object()); +} + +TEST(PreferencesRepository, SaveAndLoadPreferences) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + PreferencesRepository preferences_repository{full_path.string()}; + json data; + data["key1"] = "value1"; + data["key2"] = "value2"; + EXPECT_TRUE(preferences_repository.SavePreferences(data)); + json result = preferences_repository.LoadPreferences(); + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result["key1"], "value1"); + EXPECT_EQ(result["key2"], "value2"); + std::filesystem::remove(full_name); +} + +TEST(PreferencesRepository, LoadFromBackup) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + std::filesystem::path full_name_backup = + full_path / kPreferencesBackupFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + if (std::filesystem::exists(full_name_backup)) { + std::filesystem::remove(full_name_backup); + } + + PreferencesRepository preferences_repository{full_path.string()}; + json data; + data["key1"] = "value1"; + data["key2"] = "value2"; + + std::ofstream backup_file(full_name_backup.c_str()); + backup_file << data; + backup_file.close(); + + std::optional result; + result = preferences_repository.AttemptLoad(); + EXPECT_FALSE(result.has_value()); + result = preferences_repository.RestoreFromBackup(); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(result.value()["key1"], "value1"); + EXPECT_EQ(result.value()["key2"], "value2"); + std::filesystem::remove(full_name); + EXPECT_FALSE(std::filesystem::exists(full_name_backup)); +} + +TEST(PreferencesRepository, RecoverFromCorruption) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + std::filesystem::path full_name_backup = + full_path / kPreferencesBackupFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + if (std::filesystem::exists(full_name_backup)) { + std::filesystem::remove(full_name_backup); + } + + PreferencesRepository preferences_repository{full_path.string()}; + json data; + data["key1"] = "value1"; + data["key2"] = "value2"; + + std::ofstream preferences_file(full_name_backup.c_str()); + preferences_file << data; + preferences_file.close(); + + std::ofstream backup_file(full_name.c_str()); + backup_file << "[BAD JSON FILE]"; + backup_file.close(); + + std::optional result = preferences_repository.LoadPreferences(); + EXPECT_EQ(result.value()["key1"], "value1"); + EXPECT_EQ(result.value()["key2"], "value2"); + std::filesystem::remove(full_name); + EXPECT_FALSE(std::filesystem::exists(full_name_backup)); +} + +} // namespace +} // namespace linux +} // namespace nearby From fa197d2036614301ff79d6816ebbd4d65e19f28a Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Mon, 7 Aug 2023 15:16:43 -0500 Subject: [PATCH 10/13] Implemented submittable_executor and some other missed files A file was named wrong. It was changed. --- .../implementation/linux/device_info_test.cc | 129 +++++++++ .../linux/{mutext_test.cc => mutex_test.cc} | 0 .../linux/scheduled_executor_test.cc | 179 +++++++++++++ .../linux/submittable_executor.cc | 63 +++++ .../linux/submittable_executor.h | 53 ++++ .../linux/submittable_executor_test.cc | 250 ++++++++++++++++++ .../implementation/linux/thread_pool_test.cc | 71 +++++ 7 files changed, 745 insertions(+) create mode 100644 internal/platform/implementation/linux/device_info_test.cc rename internal/platform/implementation/linux/{mutext_test.cc => mutex_test.cc} (100%) create mode 100644 internal/platform/implementation/linux/scheduled_executor_test.cc create mode 100644 internal/platform/implementation/linux/submittable_executor.cc create mode 100644 internal/platform/implementation/linux/submittable_executor.h create mode 100644 internal/platform/implementation/linux/submittable_executor_test.cc create mode 100644 internal/platform/implementation/linux/thread_pool_test.cc diff --git a/internal/platform/implementation/linux/device_info_test.cc b/internal/platform/implementation/linux/device_info_test.cc new file mode 100644 index 00000000..9168a7b4 --- /dev/null +++ b/internal/platform/implementation/linux/device_info_test.cc @@ -0,0 +1,129 @@ +// 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" + +#include +#include + +#include "gtest/gtest.h" +#include "absl/synchronization/notification.h" +#include "internal/platform/implementation/device_info.h" + +namespace nearby { +namespace linux { +namespace { + +TEST(DeviceInfo, DISABLED_GetComputerName) { + EXPECT_TRUE(DeviceInfo().GetOsDeviceName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetDeviceType) { + EXPECT_EQ(DeviceInfo().GetDeviceType(), api::DeviceInfo::DeviceType::kLaptop); +} + +TEST(DeviceInfo, GetOsType) { + EXPECT_EQ(DeviceInfo().GetOsType(), api::DeviceInfo::OsType::kLinux); +} + +TEST(DeviceInfo, DISABLED_GetFullName) { + EXPECT_TRUE(DeviceInfo().GetFullName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetGivenName) { + EXPECT_TRUE(DeviceInfo().GetGivenName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetLastName) { + EXPECT_TRUE(DeviceInfo().GetLastName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetProfileUserName) { + EXPECT_TRUE(DeviceInfo().GetProfileUserName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetLocalAppDataPath) { + EXPECT_TRUE(DeviceInfo().GetLocalAppDataPath().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetDownloadPath) { + EXPECT_TRUE(DeviceInfo().GetDownloadPath().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetTemporaryPath) { + EXPECT_TRUE(DeviceInfo().GetTemporaryPath().has_value()); +} + +TEST(DeviceInfo, DISABLED_IsScreenLocked) { + EXPECT_FALSE(DeviceInfo().IsScreenLocked()); +} + +TEST(DeviceInfo, DISABLED_RegisterScreenLockedListener) { + std::function listener_1 = + [](api::DeviceInfo::ScreenStatus) {}; + std::function listener_2 = + [](api::DeviceInfo::ScreenStatus) {}; + + DeviceInfo device_info; + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0); + + device_info.RegisterScreenLockedListener("listener_1", listener_1); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1); + + device_info.RegisterScreenLockedListener("listener_2", listener_2); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2); +} + +TEST(DeviceInfo, DISABLED_UnregisterScreenLockedListener) { + std::function listener_1 = + [](api::DeviceInfo::ScreenStatus) {}; + std::function listener_2 = + [](api::DeviceInfo::ScreenStatus) {}; + + DeviceInfo device_info; + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0); + + device_info.RegisterScreenLockedListener("listener_1", listener_1); + device_info.RegisterScreenLockedListener("listener_2", listener_2); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2); + + device_info.UnregisterScreenLockedListener("listener_1"); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1); + + device_info.UnregisterScreenLockedListener("listener_2"); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0); +} + +TEST(DeviceInfo, DISABLED_UpdateScreenLockedListener) { + absl::Notification notification; + + api::DeviceInfo::ScreenStatus screen_locked_tracker = + api::DeviceInfo::ScreenStatus::kUndetermined; + + std::function listener = + [&screen_locked_tracker, + ¬ification](api::DeviceInfo::ScreenStatus status) { + screen_locked_tracker = api::DeviceInfo::ScreenStatus::kLocked; + notification.Notify(); + }; + + DeviceInfo device_info; + device_info.RegisterScreenLockedListener("listener", listener); + EXPECT_TRUE(notification.WaitForNotificationWithTimeout(absl::Seconds(5))); + EXPECT_EQ(screen_locked_tracker, api::DeviceInfo::ScreenStatus::kLocked); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/mutext_test.cc b/internal/platform/implementation/linux/mutex_test.cc similarity index 100% rename from internal/platform/implementation/linux/mutext_test.cc rename to internal/platform/implementation/linux/mutex_test.cc diff --git a/internal/platform/implementation/linux/scheduled_executor_test.cc b/internal/platform/implementation/linux/scheduled_executor_test.cc new file mode 100644 index 00000000..2c9e804f --- /dev/null +++ b/internal/platform/implementation/linux/scheduled_executor_test.cc @@ -0,0 +1,179 @@ +// 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/scheduled_executor.h" + +#include +#include + +#include "gtest/gtest.h" +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "internal/platform/implementation/linux/test_data.h" + +namespace nearby { +namespace linux { +namespace { + +TEST(ScheduledExecutorTests, ExecuteSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = 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 + submittableExecutor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(200))); + submittableExecutor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 1 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(ScheduledExecutorTests, ScheduleSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = 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()); + + std::chrono::system_clock::time_point timeNow = + std::chrono::system_clock::now(); + std::chrono::system_clock::time_point timeExecuted; + + // Act + submittableExecutor->Schedule( + [&]() { + timeExecuted = std::chrono::system_clock::now(); + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }, + absl::Milliseconds(50)); + + ASSERT_TRUE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(200))); + submittableExecutor->Shutdown(); + + 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(ScheduledExecutorTests, CancelSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(""); + + auto submittableExecutor = 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 + auto cancelable = submittableExecutor->Schedule( + [&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }, + absl::Milliseconds(1000)); + + auto actual = cancelable->Cancel(); + + EXPECT_FALSE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(2000))); + submittableExecutor->Shutdown(); + + // Assert + ASSERT_TRUE(actual); + 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, expected); +} + +TEST(ScheduledExecutorTests, CancelAfterStartedFails) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = 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 + auto cancelable = submittableExecutor->Schedule( + [&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }, + absl::Milliseconds(100)); + + absl::SleepFor(absl::Milliseconds(200)); + auto actual = cancelable->Cancel(); + + ASSERT_TRUE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(2000))); + submittableExecutor->Shutdown(); + + // Assert + ASSERT_FALSE(actual); + 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); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/submittable_executor.cc b/internal/platform/implementation/linux/submittable_executor.cc new file mode 100644 index 00000000..3b721522 --- /dev/null +++ b/internal/platform/implementation/linux/submittable_executor.cc @@ -0,0 +1,63 @@ +// 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/submittable_executor.h" + +#include "internal/platform/implementation/linux/executor.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +SubmittableExecutor::SubmittableExecutor() : SubmittableExecutor(1) {} + +SubmittableExecutor::SubmittableExecutor(int32_t max_concurrancy) + : executor_(std::make_unique(max_concurrancy)), + shut_down_(false) {} + +bool SubmittableExecutor::DoSubmit(Runnable&& wrapped_callable) { + if (!shut_down_) { + executor_->Execute(std::move(wrapped_callable)); + return true; + } + + NEARBY_LOGS(ERROR) << "Error: " << __func__ + << ": Attempt to DoSubmit on a shutdown executor."; + + return false; +} + +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- +void SubmittableExecutor::Execute(Runnable&& runnable) { + if (!shut_down_) { + executor_->Execute(std::move(runnable)); + } else { + NEARBY_LOGS(ERROR) << "Error: " << __func__ + << ": Attempt to Execute on a shutdown executor."; + } +} + +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- +void SubmittableExecutor::Shutdown() { + if (!shut_down_) { + executor_->Shutdown(); + shut_down_ = true; + } + + NEARBY_LOGS(ERROR) << "Error: " << __func__ + << ": Attempt to Shutdown on a shutdown executor."; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/submittable_executor.h b/internal/platform/implementation/linux/submittable_executor.h new file mode 100644 index 00000000..63b1ef0b --- /dev/null +++ b/internal/platform/implementation/linux/submittable_executor.h @@ -0,0 +1,53 @@ +// 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_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_SUBMITTABLE_EXECUTOR_H_ + +#include "internal/platform/implementation/submittable_executor.h" +#include "internal/platform/implementation/linux/executor.h" + +namespace nearby { +namespace linux { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(absl::AnyInvocable) method. +class SubmittableExecutor : public api::SubmittableExecutor { + public: + SubmittableExecutor(); + SubmittableExecutor(int32_t maxConcurrancy); + ~SubmittableExecutor() override = default; + + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + bool DoSubmit(Runnable&& wrapped_callable) override; + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- + void Execute(Runnable&& runnable) override; + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- + void Shutdown() override; + + private: + std::unique_ptr executor_; + std::atomic_bool shut_down_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SUBMITTABLE_EXECUTOR_H_ diff --git a/internal/platform/implementation/linux/submittable_executor_test.cc b/internal/platform/implementation/linux/submittable_executor_test.cc new file mode 100644 index 00000000..b3cf11d6 --- /dev/null +++ b/internal/platform/implementation/linux/submittable_executor_test.cc @@ -0,0 +1,250 @@ +// 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/submittable_executor.h" + +#include +#include + +#include "gtest/gtest.h" +#include "absl/synchronization/blocking_counter.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(SubmittableExecutorTests, SingleThreadedExecuteSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + submittableExecutor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + submittableExecutor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 1 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(SubmittableExecutorTests, SingleThreadedExecuteAfterShutdownFails) { + // Arrange + std::string expected(""); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + submittableExecutor->Shutdown(); + + // Act + submittableExecutor->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 0 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, expected); +} + +TEST(SubmittableExecutorTests, SingleThreadedDoSubmitSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + auto result = submittableExecutor->DoSubmit([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + submittableExecutor->Shutdown(); + + // Assert + // We should've said we were going to run this one + ASSERT_TRUE(result); + // We should've run 1 time on the main thread, and 1 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(SubmittableExecutorTests, + SingleThreadedDoSubmitAfterShutdownReturnsFalse) { + // Arrange + std::string expected(""); + + auto submittableExecutor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + submittableExecutor->Shutdown(); + + // Act + auto result = submittableExecutor->DoSubmit([&output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've said we were going to run this one + ASSERT_FALSE(result); + // We should've run 1 time on the main thread, and 1 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(SubmittableExecutorTests, SingleThreadedExecuteMultipleTasksSucceeds) { + absl::BlockingCounter blocking_counter(5); + + // Arrange + std::string expected(RUNNABLE_ALL_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + for (int index = 0; index < 5; index++) { + submittableExecutor->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)); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + submittableExecutor->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)); + // We should've run all runnables on the worker thread + auto workerThreadId = threadIds->at(1); + for (int index = 1; index < threadIds->size(); index++) { + ASSERT_EQ(threadIds->at(index), workerThreadId); + } + + // We should of run them in the order submitted + ASSERT_EQ(*output.get(), expected); +} + +TEST(SubmittableExecutorTests, SingleThreadedDoSubmitMultipleTasksSucceeds) { + absl::BlockingCounter blocking_counter(5); + + // Arrange + std::string expected(RUNNABLE_ALL_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + bool result = true; + for (int index = 0; index < 5; index++) { + result &= submittableExecutor->DoSubmit([&, 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)); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + submittableExecutor->Shutdown(); + + // Assert + // All of these should have submitted + ASSERT_TRUE(result); + // 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)); + // We should've run all runnables on the worker thread + auto workerThreadId = threadIds->at(1); + for (int index = 1; index < threadIds->size(); index++) { + ASSERT_EQ(threadIds->at(index), workerThreadId); + } + + // We should of run them in the order submitted + ASSERT_EQ(*output.get(), expected); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/thread_pool_test.cc b/internal/platform/implementation/linux/thread_pool_test.cc new file mode 100644 index 00000000..f2c8327a --- /dev/null +++ b/internal/platform/implementation/linux/thread_pool_test.cc @@ -0,0 +1,71 @@ +// Copyright 2022 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 "gtest/gtest.h" +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr int kTaskCount = 10; + +TEST(ThreadPool, TasksInSingleThreadRunInSequence) { + absl::BlockingCounter blocking_counter(kTaskCount); + auto pool = ThreadPool::Create(1); + std::vector completed_tasks; + std::vector expected_tasks; + + for (int i = 0; i < kTaskCount; ++i) { + expected_tasks.push_back(i); + pool->Run([&, i]() { + absl::SleepFor(absl::Milliseconds(200)); + completed_tasks.push_back(i); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + EXPECT_EQ(completed_tasks, expected_tasks); + pool->ShutDown(); +} + +TEST(ThreadPool, TasksInMultipleThreadsRunInParallel) { + absl::BlockingCounter blocking_counter(kTaskCount); + absl::Time start_time = absl::Now(); + + auto pool = ThreadPool::Create(2); + + for (int i = 0; i < kTaskCount; ++i) { + pool->Run([&]() { + absl::SleepFor(absl::Milliseconds(200)); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + EXPECT_TRUE(absl::Now() - start_time < absl::Milliseconds(1500)); + pool->ShutDown(); +} + +} // namespace +} // namespace linux +} // namespace nearby From 90d8e208eb9c31b6943d919fadaacd9293c0a657 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Wed, 16 Aug 2023 21:31:31 -0500 Subject: [PATCH 11/13] Implemented a TimerQueue class and a Timer class The Windows implementation of the Timer class (in timer.h) used Timer Queues. This brings in implementation for a mostly Windows complient TimerQueue class to work with the Timer class. --- .../platform/implementation/linux/timer.cc | 120 +++++++++++++ .../platform/implementation/linux/timer.h | 56 +++++++ .../implementation/linux/timer_queue.cc | 157 ++++++++++++++++++ .../implementation/linux/timer_queue.h | 124 ++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 internal/platform/implementation/linux/timer.cc create mode 100644 internal/platform/implementation/linux/timer.h create mode 100644 internal/platform/implementation/linux/timer_queue.cc create mode 100644 internal/platform/implementation/linux/timer_queue.h diff --git a/internal/platform/implementation/linux/timer.cc b/internal/platform/implementation/linux/timer.cc new file mode 100644 index 00000000..2468a009 --- /dev/null +++ b/internal/platform/implementation/linux/timer.cc @@ -0,0 +1,120 @@ +// 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/timer.h" +#include "internal/platform/implementation/linux/timer_queue.h" + +#include "absl/synchronization/mutex.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +Timer::~Timer() { Stop(); } + +bool Timer::Create(int delay, int interval, + absl::AnyInvocable callback) { + absl::MutexLock lock(&mutex_); + + if ((delay < 0) || (interval < 0)) { + NEARBY_LOGS(WARNING) << "Delay and interval shouldn\'t be negative value."; + return false; + } + + if (timer_queue_handle_) { + return false; + } + + timer_queue_handle_ = TimerQueue::CreateTimerQueue(); + if (!timer_queue_handle_) { + NEARBY_LOGS(ERROR) << "Failed to create timer queue."; + return false; + } + + delay_ = delay; + interval_ = interval; + callback_ = std::move(callback); + + absl::StatusOr createStatus = timer_queue_handle_->CreateTimerQueueTimer(TimerRoutine, + &callback_, std::chrono::milliseconds(delay), std::chrono::milliseconds(interval), TimerQueue::WT_EXECUTEDEFAULT); + + if (!createStatus.ok()) { + if (!timer_queue_handle_->DeleteTimerQueueEx(TimerQueue::CE_IMEDIATERETURN).ok()) { + NEARBY_LOGS(ERROR) << "Failed to create timer in timer queue."; + } + delete timer_queue_handle_.release(); + return false; + } + handle_ = createStatus.value(); + return true; +} + +bool Timer::Stop() { + absl::MutexLock lock(&mutex_); + + if (!timer_queue_handle_) { + return true; + } + + absl::Status deleteStatus = timer_queue_handle_->DeleteTimerQueueTimer(handle_, TimerQueue::CE_IMEDIATERETURN); + + if (!deleteStatus.ok()) { + NEARBY_LOGS(ERROR) << "Failed to delete timer from queue: " << deleteStatus.message(); + } + + handle_ = 0; + + deleteStatus = timer_queue_handle_->DeleteTimerQueueEx(TimerQueue::CE_IMEDIATERETURN); + + if (!deleteStatus.ok()) { + NEARBY_LOGS(ERROR) << "Failed to delete timer queue: " << deleteStatus.message(); + return false; + } + + timer_queue_handle_ = nullptr; + return true; +} + +bool Timer::FireNow() { + absl::MutexLock lock(&mutex_); + + if (!timer_queue_handle_ || !callback_) { + return false; + } + + if (task_executor_ == nullptr) { + task_executor_ = std::make_unique(); + } + + if (task_executor_ == nullptr) { + NEARBY_LOGS(ERROR) + << "Failed to fire the task due to cannot create executor."; + return false; + } + + task_executor_->Execute([&]() { callback_(); }); + + return true; +} + +void Timer::TimerRoutine(void *lpParam) { + absl::AnyInvocable* callback = + reinterpret_cast*>(lpParam); + if (*callback != nullptr) { + (*callback)(); + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/timer.h b/internal/platform/implementation/linux/timer.h new file mode 100644 index 00000000..8a75163c --- /dev/null +++ b/internal/platform/implementation/linux/timer.h @@ -0,0 +1,56 @@ +// 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_TIMER_H_ +#define PLATFORM_IMPL_LINUX_TIMER_H_ + +#include + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/timer.h" +#include "internal/platform/implementation/linux/submittable_executor.h" +#include "internal/platform/implementation/linux/timer_queue.h" + +namespace nearby { +namespace linux { + +class Timer : public api::Timer { + public: + Timer() = default; + ~Timer() override; + + bool Create(int delay, int interval, + absl::AnyInvocable callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool Stop() override ABSL_LOCKS_EXCLUDED(mutex_); + bool FireNow() override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + static void TimerRoutine(void *lpParam); + + mutable absl::Mutex mutex_; + int delay_ ABSL_GUARDED_BY(mutex_); + int interval_ ABSL_GUARDED_BY(mutex_); + absl::AnyInvocable callback_; + uint16_t handle_ ABSL_GUARDED_BY(mutex_) = 0; + std::unique_ptr timer_queue_handle_; + std::unique_ptr task_executor_ ABSL_GUARDED_BY(mutex_) = + nullptr; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_TIMER_H_ diff --git a/internal/platform/implementation/linux/timer_queue.cc b/internal/platform/implementation/linux/timer_queue.cc new file mode 100644 index 00000000..5048008c --- /dev/null +++ b/internal/platform/implementation/linux/timer_queue.cc @@ -0,0 +1,157 @@ +// 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/timer_queue.h" + +#include "internal/platform/implementation/linux/utils.h" + +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +std::unique_ptr TimerQueue::CreateTimerQueue() { + return std::unique_ptr(new TimerQueue); +} + +TimerQueue::TimerQueue() { + thread_ = std::thread([this]() {Run();}); +} + +TimerQueue::~TimerQueue() { + DeleteTimerQueueEx(CE_WAITFORCALLBACKS); // NOLINT + cancelled_ = true; + thread_.join(); +} + +void TimerQueue::Run() { + while(!cancelled_ || !work_.empty()) { + absl::MutexLock lk(&mutex_); + empty_ = (work_.empty() ? true : false); + for (auto &workItem : work_) { + if (workItem.Finished) { + if (workItem.Due < std::chrono::steady_clock::now()) { + work_.erase(workItem); + break; + } + } + if (workItem.Cancelled) { + if (workItem.Worker.valid()) { + if (workItem.Worker.wait_for(std::chrono_literals::operator""ms(0)) == std::future_status::ready) { + work_.erase(workItem); + break; + } + } + } + if (workItem.Due <= std::chrono::steady_clock::now()) { + if ((workItem.Flags & WT_EXECUTEDEFAULT) == WT_EXECUTEDEFAULT) { + if ((workItem.Flags & WT_EXECUTEONLYONCE) == WT_EXECUTEONLYONCE) { + workItem.Worker = std::async(workItem.Callback, workItem.Parameter); + workItem.Finished = true; + } + else { + if (workItem.Period > std::chrono_literals::operator""ms(0)) { + workItem.Worker = std::async(workItem.Callback, workItem.Parameter); + workItem.Finished = true; + workItem.Due = std::chrono::steady_clock::now() + workItem.Period; + } + else { + workItem.Worker = std::async(workItem.Callback, workItem.Parameter); + workItem.Finished = true; + } + } + } + else if ((workItem.Flags & WT_EXECUTEINTIMERTHREAD) == WT_EXECUTEINTIMERTHREAD) { + if ((workItem.Flags & WT_EXECUTEONLYONCE) == WT_EXECUTEONLYONCE) { + workItem.Callback(workItem.Parameter); + workItem.Finished = true; + } + else { + if (workItem.Period > std::chrono_literals::operator""ms(0)) { + workItem.Callback(workItem.Parameter); + workItem.Finished = true; + workItem.Due = std::chrono::steady_clock::now() + workItem.Period; + } + else { + workItem.Callback(workItem.Parameter); + workItem.Finished = true; + } + } + } + } + } + } +} + +absl::StatusOr TimerQueue::CreateTimerQueueTimer(absl::AnyInvocable Callback, void *Parameter, std::chrono::milliseconds DueTime, std::chrono::milliseconds Period, unsigned long int Flags) { + TimerWork work; + + work.Callback = std::move(Callback); + work.WorkId = next_id_; + work.Flags = Flags; + next_id_++; + work.Due = std::chrono::steady_clock::now() + DueTime; + work.Period = Period; + work.Parameter = Parameter; + absl::MutexLock lk(&mutex_); + work_.insert(std::move(work)); + return next_id_ - 1; +} + +absl::Status TimerQueue::DeleteTimerQueueTimer(uint16_t WorkId, unsigned long int CompletionEvent) { + for (auto &workItem : work_) { + absl::MutexLock lk(&mutex_); + if (workItem.WorkId == WorkId) { + workItem.Cancelled = true; + } + } + return absl::OkStatus(); +} + +namespace { + bool check(bool *arg) { + return *arg; + } +} // namespace + +absl::Status TimerQueue::DeleteTimerQueueEx(unsigned int long CompletionEvent) { + switch (CompletionEvent) { + case CE_IMEDIATERETURN: { + absl::MutexLock lk(&mutex_); + for (auto workItem = work_.begin(); workItem != work_.end();) { + if (workItem->Worker.valid()) { + workItem->Worker.wait(); + } + workItem = work_.erase(workItem); + } + return absl::OkStatus(); + } + case CE_WAITFORCALLBACKS: { + mutex_.Lock(); + for (auto &workItem : work_) { + workItem.Cancelled = true; + } + clearing_ = true; + mutex_.Await(absl::Condition(check, &empty_)); + mutex_.Unlock(); + return absl::OkStatus(); + } + default: { + return absl::InvalidArgumentError("Invalid Completion Event value."); + } + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/timer_queue.h b/internal/platform/implementation/linux/timer_queue.h new file mode 100644 index 00000000..db745324 --- /dev/null +++ b/internal/platform/implementation/linux/timer_queue.h @@ -0,0 +1,124 @@ +// 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_TIMER_QUEUE_H_ +#define PLATFORM_IMPL_LINUX_TIMER_QUEUE_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/functional/any_invocable.h" +#include "absl/functional/function_ref.h" +#include "absl/synchronization/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace nearby { +namespace linux { + +/* + * This class represents a a que for timers. This class is built to try to replicate the Windows Timer-Queue functionality. + * https://learn.microsoft.com/en-us/windows/win32/api/threadpoollegacyapiset/nf-threadpoollegacyapiset-createtimerqueue + */ +class TimerQueue { +public: + /* + * Parameters to be used with the Timer Queue Timer. + * See https://learn.microsoft.com/en-us/windows/win32/api/threadpoollegacyapiset/nf-threadpoollegacyapiset-createtimerqueuetimer + */ + static constexpr unsigned long int WT_EXECUTEDEFAULT = 1 << 0; + static constexpr unsigned long int WT_EXECUTEINTIMERTHREAD = 1 << 1; + static constexpr unsigned long int WT_EXECUTEINIOTHREAD = 1 << 2; // Unused + static constexpr unsigned long int WT_EXECUTEINPERSISTENTTHREAD = 1 << 3; // Unused + static constexpr unsigned long int WT_EXECUTELONGFUNCTION = 1 << 4; // Unused + static constexpr unsigned long int WT_EXECUTEONLYONCE = 1 << 5; + static constexpr unsigned long int WT_TRANSFER_IMPERSONATION = 1 << 6; // Unused + static constexpr unsigned long int CE_WAITFORCALLBACKS = 1 << 0; // Equivalent to `INVALID_HANDLE_VALUE` when passing for a CompletionEvent + static constexpr unsigned long int CE_IMEDIATERETURN = 1 << 1; // Equivalent to `NULL` when passing for a CompletionEvent + // Creates a new timer queue for the user to use. + static std::unique_ptr CreateTimerQueue(); + + // Returns a thread unique identifier that the timer is running on if it is successful. Status if not. + absl::StatusOr CreateTimerQueueTimer(absl::AnyInvocable Callback, void *Parameter, std::chrono::milliseconds DueTime, std::chrono::milliseconds Period, unsigned long int Flags = WT_EXECUTEDEFAULT); + + // Removes a timer based on the thread id the timer is running on. + absl::Status DeleteTimerQueueTimer(uint16_t WorkId, unsigned long int CompletionEvent = CE_WAITFORCALLBACKS); + + // Removes all timers in the timer queue + absl::Status DeleteTimerQueueEx(unsigned long int CompletionEvent = CE_WAITFORCALLBACKS); + + ~TimerQueue(); + +private: + TimerQueue(); + + void Run(); + + uint16_t next_id_; + + // A struct that represents a timer of work that needs to be done. + struct TimerWork { + mutable std::chrono::time_point Due; + mutable absl::AnyInvocable Callback; + mutable std::chrono::milliseconds Period; + mutable void *Parameter = nullptr; + mutable long int Flags; + mutable std::future Worker; + mutable std::atomic_bool Cancelled = false; + mutable std::atomic_bool Finished = false; + uint16_t WorkId = 0; + + // Useful for time comparisons on other work items + bool operator<(const TimerWork &other) const { + return Due < other.Due; + } + bool operator>(const TimerWork &other) const { + return Due > other.Due; + } + bool operator==(const TimerWork &other) const { + return Due == other.Due; + } + bool operator!=(const TimerWork &other) const { + return !operator==(other); + } + bool operator<=(const TimerWork &other) const { + return (operator<(other) || operator==(other)); + } + bool operator>=(const TimerWork &other) const { + return (operator>(other) || operator==(other)); + } + }; + + std::set work_; + std::vector threads_; + + std::thread thread_; + + bool cancelled_; + bool clearing_; + bool empty_; + + absl::CondVar condvar_; + absl::Mutex mutex_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_TIMER_QUEUE_H_ From 17786cbec353caad16875f57ab4ddada1a21555e Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Wed, 16 Aug 2023 21:36:04 -0500 Subject: [PATCH 12/13] Added base implementation for input_file and output_file This are unimplemented currently, but will be in the future. --- .../implementation/linux/input_file.h | 48 +++++++ .../implementation/linux/input_file_test.cc | 135 ++++++++++++++++++ .../implementation/linux/output_file.h | 45 ++++++ .../implementation/linux/output_file_test.cc | 80 +++++++++++ 4 files changed, 308 insertions(+) create mode 100644 internal/platform/implementation/linux/input_file.h create mode 100644 internal/platform/implementation/linux/input_file_test.cc create mode 100644 internal/platform/implementation/linux/output_file.h create mode 100644 internal/platform/implementation/linux/output_file_test.cc diff --git a/internal/platform/implementation/linux/input_file.h b/internal/platform/implementation/linux/input_file.h new file mode 100644 index 00000000..7d4df48a --- /dev/null +++ b/internal/platform/implementation/linux/input_file.h @@ -0,0 +1,48 @@ +// 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_INPUT_FILE_H_ +#define PLATFORM_IMPL_LINUX_INPUT_FILE_H_ + +#include "internal/platform/implementation/input_file.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" + +namespace nearby { +namespace linux { + +// An InputFile represents a readable file on the system. +class InputFile : public api::InputFile { + public: + // TODO(b/184975123): replace with real implementation. + ~InputFile() override = default; + // TODO(b/184975123): replace with real implementation. + std::string GetFilePath() const override { return "Un-implemented"; } + // TODO(b/184975123): replace with real implementation. + std::int64_t GetTotalSize() const override { return 0; } + + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + ExceptionOr Read(std::int64_t size) override { + return ExceptionOr(Exception::kFailed); + } + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Close() override { return Exception{}; } +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_INPUT_FILE_H_ diff --git a/internal/platform/implementation/linux/input_file_test.cc b/internal/platform/implementation/linux/input_file_test.cc new file mode 100644 index 00000000..63268b0c --- /dev/null +++ b/internal/platform/implementation/linux/input_file_test.cc @@ -0,0 +1,135 @@ +// 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/input_file.h" + +#include + +#include "gtest/gtest.h" +#include "internal/platform/exception.h" +#include "internal/platform/payload_id.h" +#include "internal/platform/implementation/linux/test_utils.h" +#include "internal/platform/logging.h" + +class InputFileTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + void SetUp() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + auto path = test_utils::GetPayloadPath(payloadId); + + file_.open(path, std::ios::out); + + if (!file_) { + NEARBY_LOG(ERROR, + "Failed to create OutputFile with payloadId: %s and error: %d", + test_utils::GetPayloadPath(payloadId).c_str(), std::strerror(errno)); + } + + const char* buffer = TEST_STRING; + + file_.write(buffer, std::strlen(buffer)); + + file_.close(); + } + + // You can define per-test tear-down logic as usual. + void TearDown() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + if (std::filesystem::exists(test_utils::GetPayloadPath(payloadId))) { + std::filesystem::remove(test_utils::GetPayloadPath(payloadId)); + } + } + + private: + std::fstream file_; +}; + +TEST_F(InputFileTests, SuccessfulCreation) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + EXPECT_NE(inputFile, nullptr); + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); +} + +TEST_F(InputFileTests, SuccessfulGetFilePath) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + std::string fileName; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + fileName = inputFile->GetFilePath(); + + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); + + EXPECT_EQ(fileName, test_utils::GetPayloadPath(payloadId).c_str()); +} + +TEST_F(InputFileTests, SuccessfulGetTotalSize) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + int64_t size = -1; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + size = inputFile->GetTotalSize(); + + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); + + EXPECT_EQ(size, strlen(TEST_STRING)); +} + +TEST_F(InputFileTests, SuccessfulRead) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + auto fileSize = inputFile->GetTotalSize(); + auto dataRead = inputFile->Read(fileSize); + + EXPECT_TRUE(dataRead.ok()); + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); + + EXPECT_STREQ(std::string(dataRead.result()).c_str(), TEST_STRING); +} + +TEST_F(InputFileTests, FailedRead) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + auto fileSize = inputFile->GetTotalSize(); + EXPECT_NE(fileSize, -1); + + auto dataRead = inputFile->Read(fileSize); + EXPECT_TRUE(dataRead.ok()); + + dataRead = inputFile->Read(fileSize); + std::string data = std::string(dataRead.result()); + + inputFile->Close(); + + EXPECT_STREQ(data.c_str(), ""); +} diff --git a/internal/platform/implementation/linux/output_file.h b/internal/platform/implementation/linux/output_file.h new file mode 100644 index 00000000..53bcbddb --- /dev/null +++ b/internal/platform/implementation/linux/output_file.h @@ -0,0 +1,45 @@ +// 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_OUTPUT_FILE_H_ +#define PLATFORM_IMPL_LINUX_OUTPUT_FILE_H_ + +#include "internal/platform/implementation/output_file.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" + +namespace nearby { +namespace linux { + +// An OutputFile represents a writable file on the system. +class OutputFile : public api::OutputFile { + public: + // TODO(b/184975123): replace with real implementation. + ~OutputFile() override = default; + + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Write(const ByteArray& data) override { return Exception{}; } + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Flush() override { return Exception{}; } + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Close() override { return Exception{}; } +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_OUTPUT_FILE_H_ diff --git a/internal/platform/implementation/linux/output_file_test.cc b/internal/platform/implementation/linux/output_file_test.cc new file mode 100644 index 00000000..bf490d27 --- /dev/null +++ b/internal/platform/implementation/linux/output_file_test.cc @@ -0,0 +1,80 @@ +// 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/output_file.h" + +#include "gtest/gtest.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/exception.h" +#include "internal/platform/payload_id.h" +#include "internal/platform/implementation/windows/test_utils.h" + +class OutputFileTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + void SetUp() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + if (std::filesystem::exists(test_utils::GetPayloadPath(payloadId))) { + std::filesystem::remove(test_utils::GetPayloadPath(payloadId)); + } + } + + // You can define per-test tear-down logic as usual. + void TearDown() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + if (std::filesystem::exists(test_utils::GetPayloadPath(payloadId).c_str())) { + std::filesystem::remove(test_utils::GetPayloadPath(payloadId).c_str()); + } + } +}; + +TEST_F(OutputFileTests, SuccessfulCreation) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr outputFile = nullptr; + + EXPECT_NO_THROW( + outputFile = + nearby::api::ImplementationPlatform::CreateOutputFile(payloadId)); + + EXPECT_NE(outputFile, nullptr); + EXPECT_NO_THROW(outputFile->Close()); +} + +TEST_F(OutputFileTests, SuccessfulClose) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr outputFile = nullptr; + + EXPECT_NO_THROW( + outputFile = + nearby::api::ImplementationPlatform::CreateOutputFile(payloadId)); + + EXPECT_NO_THROW(outputFile->Close()); + + std::filesystem::remove(test_utils::GetPayloadPath(payloadId).c_str()); +} + +TEST_F(OutputFileTests, SuccessfulWrite) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + nearby::ByteArray data(std::string(TEST_STRING)); + std::unique_ptr outputFile = nullptr; + + EXPECT_NO_THROW( + outputFile = + nearby::api::ImplementationPlatform::CreateOutputFile(payloadId)); + + EXPECT_NO_THROW(outputFile->Write(data)); + EXPECT_NO_THROW(outputFile->Close()); + + std::filesystem::remove(test_utils::GetPayloadPath(payloadId).c_str()); +} From 4a4056694a94e55cb309e01568537c9e37489125 Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Wed, 16 Aug 2023 21:37:52 -0500 Subject: [PATCH 13/13] Implemented missing implementation files I missed some implementation when I was looking over them. These are them. --- .../implementation/linux/server_sync.h | 88 +++++++++++++++++++ .../implementation/linux/system_clock.cc | 41 +++++++++ .../implementation/linux/timer_test.cc | 68 ++++++++++++++ .../implementation/linux/utils_test.cc | 85 ++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 internal/platform/implementation/linux/server_sync.h create mode 100644 internal/platform/implementation/linux/system_clock.cc create mode 100644 internal/platform/implementation/linux/timer_test.cc create mode 100644 internal/platform/implementation/linux/utils_test.cc diff --git a/internal/platform/implementation/linux/server_sync.h b/internal/platform/implementation/linux/server_sync.h new file mode 100644 index 00000000..813aeefb --- /dev/null +++ b/internal/platform/implementation/linux/server_sync.h @@ -0,0 +1,88 @@ +// 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_SERVER_SYNC_H_ +#define PLATFORM_IMPL_LINUX_SERVER_SYNC_H_ + +#include "internal/platform/implementation/server_sync.h" + +namespace nearby { +namespace linux { + +// Abstraction that represents a Nearby endpoint exchanging data through +// ServerSync Medium. +class ServerSyncDevice : public api::ServerSyncDevice { + public: + // TODO(b/184975123): replace with real implementation. + ~ServerSyncDevice() override = default; + + // TODO(b/184975123): replace with real implementation. + std::string GetName() const override { return "Un-implemented"; } + // TODO(b/184975123): replace with real implementation. + std::string GetGuid() const override { return "Un-implemented"; } + // TODO(b/184975123): replace with real implementation. + std::string GetOwnGuid() const override { return "Un-implemented"; } +}; + +// Container of operations that can be performed over the Chrome Sync medium. +class ServerSyncMedium : public api::ServerSyncMedium { + public: + // TODO(b/184975123): replace with real implementation. + ~ServerSyncMedium() override = default; + + // TODO(b/184975123): replace with real implementation. + bool StartAdvertising(absl::string_view service_id, + absl::string_view endpoint_id, + const ByteArray& endpoint_info) override { + return false; + } + // TODO(b/184975123): replace with real implementation. + void StopAdvertising(absl::string_view service_id) override {} + + class DiscoveredDeviceCallback + : public api::ServerSyncMedium::DiscoveredDeviceCallback { + public: + // TODO(b/184975123): replace with real implementation. + ~DiscoveredDeviceCallback() override = default; + + // Called on a new ServerSyncDevice discovery. + // TODO(b/184975123): replace with real implementation. + void OnDeviceDiscovered(api::ServerSyncDevice* device, + absl::string_view service_id, + absl::string_view endpoint_id, + const ByteArray& endpoint_info) override {} + // Called when ServerSyncDevice is no longer reachable. + // TODO(b/184975123): replace with real implementation. + void OnDeviceLost(api::ServerSyncDevice* device, + absl::string_view service_id) override {} + }; + + // Returns true once the Chrome Sync scan has been initiated. + // TODO(b/184975123): replace with real implementation. + bool StartDiscovery(absl::string_view service_id, + const api::ServerSyncMedium::DiscoveredDeviceCallback& + discovered_device_callback) override { + return false; + } + // Returns true once Chrome Sync scan for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredDeviceCallback passed in to startScanning() for service_id. + // TODO(b/184975123): replace with real implementation. + void StopDiscovery(absl::string_view service_id) override {} +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SERVER_SYNC_H_ diff --git a/internal/platform/implementation/linux/system_clock.cc b/internal/platform/implementation/linux/system_clock.cc new file mode 100644 index 00000000..ea1edd85 --- /dev/null +++ b/internal/platform/implementation/linux/system_clock.cc @@ -0,0 +1,41 @@ +// 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_SYSTEM_CLOCK_H_ +#define PLATFORM_IMPL_LINUX_SYSTEM_CLOCK_H_ + +#include "internal/platform/implementation/system_clock.h" + +namespace nearby { + +// Initialize global system state. +void SystemClock::Init() { } + +// Returns current absolute time. It is guaranteed to be monotonic. +absl::Time SystemClock::ElapsedRealtime() { + return absl::FromUnixNanos( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count()); +} + +// Pauses current thread for the specified duration. +Exception SystemClock::Sleep(absl::Duration duration) { + absl::SleepFor(duration); + return {Exception::kSuccess}; +} + +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SYSTEM_CLOCK_H_ diff --git a/internal/platform/implementation/linux/timer_test.cc b/internal/platform/implementation/linux/timer_test.cc new file mode 100644 index 00000000..60308d24 --- /dev/null +++ b/internal/platform/implementation/linux/timer_test.cc @@ -0,0 +1,68 @@ +// 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/timer.h" + +#include // NOLINT +// NOLINT +#include +#include // NOLINT + +#include "gtest/gtest.h" +#include "internal/platform/implementation/platform.h" + +namespace nearby { +namespace linux { +namespace { + +TEST(Timer, TestCreateTimer) { + int count = 0; + + std::unique_ptr timer = + nearby::api::ImplementationPlatform::CreateTimer(); + + ASSERT_TRUE(timer != nullptr); + EXPECT_FALSE(timer->Create(-100, 0, [&]() { ++count; })); + EXPECT_TRUE(timer->Stop()); +} + +// This test case cannot run on Google3 +TEST(Timer, DISABLED_TestRepeatTimer) { + int count = 0; + + std::unique_ptr timer = + nearby::api::ImplementationPlatform::CreateTimer(); + + ASSERT_TRUE(timer != nullptr); + EXPECT_TRUE(timer->Create(300, 300, [&]() { ++count; })); + std::this_thread::sleep_for(std::chrono::seconds(1)); + EXPECT_TRUE(timer->Stop()); + EXPECT_EQ(count, 3); +} + +TEST(Timer, DISABLED_TestFireNow) { + int count = 0; + + auto timer = nearby::api::ImplementationPlatform::CreateTimer(); + + EXPECT_TRUE(timer != nullptr); + EXPECT_TRUE(timer->Create(3000, 3000, [&]() { ++count; })); + EXPECT_TRUE(timer->FireNow()); + EXPECT_TRUE(timer->Stop()); + EXPECT_EQ(count, 1); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/utils_test.cc b/internal/platform/implementation/linux/utils_test.cc new file mode 100644 index 00000000..228252f9 --- /dev/null +++ b/internal/platform/implementation/linux/utils_test.cc @@ -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. + +#include "internal/platform/implementation/linux/utils.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace linux { + +TEST(UtilsTests, MacAddressToString) { + // Arrange + const uint64_t input = 0x000034363bc70c71; + std::string expected = "34:36:3B:C7:0C:71"; + + // Act + std::string result = uint64_to_mac_address_string(input); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(UtilsTests, StringToMacAddress) { + // Arrange + std::string input = "34:36:3B:C7:8C:71"; + const uint64_t expected = 0x000034363bc78c71; + + // Act + uint64_t result = mac_address_string_to_uint64(input); + + // Assert + EXPECT_EQ(result, expected); +} + +constexpr absl::string_view kIpDotdecimal{"192.168.1.37"}; + +constexpr char kIp4Bytes[] = {(char)192, (char)168, (char)1, (char)37}; + +TEST(UtilsTests, Ip4BytesToDotdecimal) { + std::string result = + ipaddr_4bytes_to_dotdecimal_string(absl::string_view(kIp4Bytes)); + + EXPECT_EQ(result, kIpDotdecimal); +} + +TEST(UtilsTests, IpDotdecimalTo4Bytes) { + std::string result = + ipaddr_dotdecimal_to_4bytes_string(std::string(kIpDotdecimal)); + + EXPECT_EQ(result, std::string(kIp4Bytes, 4)); +} + +/* +TEST(UtilsTests, ConvertBetweenWinrtGuidAndNearbyUuidSuccessfully) { + Uuid uuid(0x123e4567e89b12d3, 0xa456426614174000); + winrt::guid guid("{123e4567-e89b-12d3-a456-426614174000}"); + + EXPECT_EQ(uuid, winrt_guid_to_nearby_uuid(guid)); + EXPECT_EQ(nearby_uuid_to_winrt_guid(uuid), guid); + EXPECT_TRUE(is_nearby_uuid_equal_to_winrt_guid(uuid, guid)); +} + +TEST(UtilsTests, CompareWinrtGuidAndNearbyUuidSuccessfully) { + Uuid uuid(0x123e4567e89b12d3, 0xa456426614174000); + winrt::guid guid("123e4567-e89b-12d3-a456-426614074000"); + + EXPECT_NE(uuid, winrt_guid_to_nearby_uuid(guid)); +} +*/ + +} // namespace linux +} // namespace nearby