Merge remote-tracking branch 'proatgram/linux-platform'

This commit is contained in:
Vibhav Pant
2023-08-23 17:41:43 +05:30
56 changed files with 6810 additions and 12 deletions
+114 -8
View File
@@ -1,16 +1,16 @@
licenses(["notice"])
cc_library(
name = "types",
name = "types",
hdrs = [
"atomic_boolean.h",
"device_info.h",
],
srcs = [
"device_info.cc",
srcs = [
"device_info.cc",
"log_message.cc",
"timer.cc"
],
],
visibility = ["//third_party/nearby/sharing/internal/impl/linux:__pkg__"],
deps = [
":comm",
@@ -82,6 +82,19 @@ cc_library(
visibility = ["//visibility:private"],
)
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 = [
@@ -97,14 +110,107 @@ cc_library(
"//fastpair:__subpackages__",
"//location/nearby:__subpackages__",
"//presence:__subpackages__",
"//third_party/nearby/sharing:__subpackages__",
],
deps = [
":comm",
"//internal/platform/implementation:types",
":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",
"@sdbus_cpp//:lib",
],
)
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",
"@libsystemd//:lib",
]
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest_main",
"@nlohmann_json//:json",
],
)
@@ -20,8 +20,8 @@ public:
private:
std::atomic_bool atomic_boolean_ = false;
};
} // namespace linux
} // namespace nearby
#endif
#endif // PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_
@@ -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);
}
@@ -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 <atomic>
#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_
@@ -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);
}
@@ -9,7 +9,7 @@
namespace nearby {
namespace linux {
class ConditionVariable : public api::ConditionVariable {
public:
public:
explicit ConditionVariable(api::Mutex *mutex)
: mutex_(static_cast<Mutex *>(mutex)->GetRegularMutex()) {}
~ConditionVariable() = default;
@@ -33,4 +33,4 @@ private:
} // namespace linux
} // namespace nearby
#endif
#endif // PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_
@@ -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 <future> // 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<bool> 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
}
@@ -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 <atomic>
#include <thread>
#include <vector>
class CountDownLatchTests : public testing::Test {
public:
class TestData {
public:
std::unique_ptr<nearby::api::CountDownLatch>& countDownLatch;
long volatile& count;
};
class CountDownLatchTest {
public:
static unsigned int ThreadProcCountDown(void *lpParam) {
TestData* testData = static_cast<TestData*>(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<TestData*>(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<nearby::api::CountDownLatch> countDownLatch =
nearby::api::ImplementationPlatform::CreateCountDownLatch(3);
std::vector<std::thread> 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<nearby::api::CountDownLatch> countDownLatch =
nearby::api::ImplementationPlatform::CreateCountDownLatch(3);
// Act
nearby::ExceptionOr<bool> 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<nearby::api::CountDownLatch> countDownLatch =
nearby::api::ImplementationPlatform::CreateCountDownLatch(3);
TestData testData{countDownLatch, count};
std::vector<std::thread> 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<bool> 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<nearby::api::CountDownLatch> 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);
}
@@ -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 <cstdint>
#include <string>
#include "absl/strings/string_view.h"
#include "internal/platform/byte_array.h"
#include <openssl/evp.h>
// 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<char*>(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
@@ -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 <string>
#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
@@ -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 <functional>
#include <string>
#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<void(api::DeviceInfo::ScreenStatus)> listener_1 =
[](api::DeviceInfo::ScreenStatus) {};
std::function<void(api::DeviceInfo::ScreenStatus)> 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<void(api::DeviceInfo::ScreenStatus)> listener_1 =
[](api::DeviceInfo::ScreenStatus) {};
std::function<void(api::DeviceInfo::ScreenStatus)> 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<void(api::DeviceInfo::ScreenStatus)> listener =
[&screen_locked_tracker,
&notification](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
@@ -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 <cassert>
#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
@@ -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 <atomic>
#include <memory>
#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<linux::ThreadPool> thread_pool_ = nullptr;
std::atomic<bool> shut_down_ = false;
int32_t max_concurrency_;
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPL_LINUX_EXECUTOR_H_
@@ -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 <algorithm>
#include <utility>
#include <thread>
#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<Executor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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> executor = std::make_unique<Executor>();
std::unique_ptr<std::string> output = std::make_unique<std::string>();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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<Executor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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<Executor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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<Executor>(2);
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
std::shared_ptr<std::string> output = std::make_shared<std::string>();
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<Executor>(2);
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
std::shared_ptr<std::string> output = std::make_shared<std::string>();
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<Executor>(2);
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
std::shared_ptr<std::string> output = std::make_shared<std::string>();
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<Executor>(32);
// Container to note threads that ran
std::vector<std::thread::id> threadIds = std::vector<std::thread::id>();
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
@@ -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 <algorithm>
#include <codecvt>
#include <cstddef>
#include <ios>
#include <locale>
#include <memory>
#include <string>
#include <filesystem>
#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> 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> IOFile::CreateOutputFile(const absl::string_view path) {
return std::unique_ptr<IOFile>(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<std::codecvt_utf8_utf16<wchar_t>> converter;
file_.open(std::filesystem::path(converter.from_bytes(path_)), std::ios::binary | std::ios::out);
}
ExceptionOr<ByteArray> IOFile::Read(std::int64_t size) {
if (!file_.is_open()) {
return ExceptionOr<ByteArray>{Exception::kIo};
}
if (file_.peek() == EOF) {
return ExceptionOr<ByteArray>{ByteArray{}};
}
if (!file_.good()) {
return ExceptionOr<ByteArray>{Exception::kIo};
}
ByteArray bytes(size);
std::unique_ptr<char[]> read_bytes{new char[size]};
file_.read(read_bytes.get(), static_cast<ptrdiff_t>(size));
auto num_bytes_read = file_.gcount();
if (num_bytes_read == 0) {
return ExceptionOr<ByteArray>{Exception::kIo};
}
return ExceptionOr<ByteArray>(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
@@ -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 <cstdint>
#include <fstream>
#include <memory>
#include <string>
#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<IOFile> CreateInputFile(
const absl::string_view file_path, size_t size);
static std::unique_ptr<IOFile> CreateOutputFile(const absl::string_view path);
ExceptionOr<ByteArray> 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_
@@ -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 <algorithm>
#include <cctype>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#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<std::filesystem::path> 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
@@ -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 <string>
#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_
@@ -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 <algorithm>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include <internal/platform/implementation/linux/device_info.h>
#include <internal/platform/implementation/linux/utils.h>
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"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_IllegalFileNameCharacterGreaterThan\
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_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
@@ -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 <typename T>
class Future : public api::Future<T> {
public:
// TODO(b/184975123): replace with real implementation.
~Future() override = default;
// throws Exception::kInterrupted, Exception::kExecution
// TODO(b/184975123): replace with real implementation.
ExceptionOr<T> Get() override { return ExceptionOr<T>{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<T> Get(absl::Duration timeout) override {
return ExceptionOr<T>{Exception::kFailed};
}
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPL_LINUX_FUTURE_H_
@@ -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 <iostream>
#include <string>
#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<WebResponse> 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<WebResponse> result = ProcessResponse();
if (!result.ok()) {
return result;
}
DisconnectWebServer();
return result;
}
absl::StatusOr<int> 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<std::string> HttpLoader::QueryStatusText(
CURL *request_handle) {
absl::StatusOr<int> 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<std::multimap<std::string, std::string>>
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<std::string, std::string> 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<HttpLoader*>(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<CURLcode> 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<long>(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<long>::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<WebResponse> 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
@@ -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 <string>
#include <curl/curl.h>
#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<nearby::api::WebResponse> 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<nearby::api::WebResponse> ProcessResponse();
void DisconnectWebServer();
absl::StatusOr<int> QueryStatusCode(CURL *file_handle);
absl::StatusOr<std::string> QueryStatusText(CURL *request_handle);
absl::StatusOr<std::multimap<std::string, std::string>> 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_
@@ -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 <string>
#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
@@ -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<ByteArray> Read(std::int64_t size) override {
return ExceptionOr<ByteArray>(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_
@@ -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 <fstream>
#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<nearby::api::InputFile> 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<nearby::api::InputFile> 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<nearby::api::InputFile> 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<nearby::api::InputFile> 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<nearby::api::InputFile> 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(), "");
}
@@ -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 <future> // NOLINT
#include "gtest/gtest.h"
class MutexTests : public testing::Test {
public:
class MutexTest {
public:
MutexTest(nearby::linux::Mutex& mutex) : mutex_(mutex) {}
std::future<bool> 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());
}
@@ -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_
@@ -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<nearby::api::OutputFile> 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<nearby::api::OutputFile> 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<nearby::api::OutputFile> 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());
}
@@ -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 <filesystem> // NOLINT(build/c++17)
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <vector>
#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<std::filesystem::path> 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<PreferencesRepository>(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<const bool> value) {
absl::MutexLock lock(&mutex_);
return SetArrayValue(key, value);
}
bool PreferencesManager::SetIntegerArray(absl::string_view key,
absl::Span<const int> value) {
absl::MutexLock lock(&mutex_);
return SetArrayValue(key, value);
}
bool PreferencesManager::SetInt64Array(absl::string_view key,
absl::Span<const int64_t> value) {
absl::MutexLock lock(&mutex_);
return SetArrayValue(key, value);
}
bool PreferencesManager::SetStringArray(absl::string_view key,
absl::Span<const std::string> 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<bool> PreferencesManager::GetBooleanArray(
absl::string_view key, absl::Span<const bool> default_value) const {
absl::MutexLock lock(&mutex_);
return GetArrayValue(key, default_value);
}
std::vector<int> PreferencesManager::GetIntegerArray(
absl::string_view key, absl::Span<const int> default_value) const {
absl::MutexLock lock(&mutex_);
return GetArrayValue(key, default_value);
}
std::vector<int64_t> PreferencesManager::GetInt64Array(
absl::string_view key, absl::Span<const int64_t> default_value) const {
absl::MutexLock lock(&mutex_);
return GetArrayValue(key, default_value);
}
std::vector<std::string> PreferencesManager::GetStringArray(
absl::string_view key, absl::Span<const std::string> 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<int64_t>());
}
// 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 <typename T>
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<T>();
}
template <typename T>
bool PreferencesManager::SetArrayValue(absl::string_view key,
absl::Span<const T> 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 <typename T>
std::vector<T> PreferencesManager::GetArrayValue(
absl::string_view key, absl::Span<const T> default_value) const {
std::vector<T> 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<T>());
++it;
}
return result;
}
} // namespace linux
} // namespace nearby
@@ -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 <stdint.h>
#include <memory>
#include <string>
#include <vector>
#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<const bool> value) override
ABSL_LOCKS_EXCLUDED(mutex_);
bool SetIntegerArray(absl::string_view key,
absl::Span<const int> value) override
ABSL_LOCKS_EXCLUDED(mutex_);
bool SetInt64Array(absl::string_view key,
absl::Span<const int64_t> value) override
ABSL_LOCKS_EXCLUDED(mutex_);
bool SetStringArray(absl::string_view key,
absl::Span<const std::string> 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<bool> GetBooleanArray(absl::string_view key,
absl::Span<const bool> default_value)
const override ABSL_LOCKS_EXCLUDED(mutex_);
std::vector<int> GetIntegerArray(
absl::string_view key, absl::Span<const int> default_value) const override
ABSL_LOCKS_EXCLUDED(mutex_);
std::vector<int64_t> GetInt64Array(absl::string_view key,
absl::Span<const int64_t> default_value)
const override ABSL_LOCKS_EXCLUDED(mutex_);
std::vector<std::string> GetStringArray(
absl::string_view key,
absl::Span<const std::string> 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 <typename T>
T GetValue(absl::string_view key, const T& default_value) const
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
template <typename T>
bool SetArrayValue(absl::string_view key, absl::Span<const T> value)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
template <typename T>
std::vector<T> GetArrayValue(absl::string_view key,
absl::Span<const T> default_value) const
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
nlohmann::json value_ ABSL_GUARDED_BY(mutex_);
std::unique_ptr<PreferencesRepository> preferences_repository_
ABSL_GUARDED_BY(mutex_);
mutable absl::Mutex mutex_;
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_
@@ -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 <stdint.h>
#include <codecvt>
#include <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include <locale>
#include <ostream>
#include <string>
#include <vector>
#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<std::string>(), "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<const bool>({true}));
EXPECT_EQ(default_result[0], true);
pm.SetBooleanArray(bool_array_key,
absl::Span<const bool>({true, false, false, true, true}));
auto result =
pm.GetBooleanArray(bool_array_key, absl::Span<const bool>({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<int>{5, 6});
EXPECT_EQ(result[1], 6);
pm.SetIntegerArray(int_array_key, std::vector<int>{1, 7, 4, 10, 12});
result = pm.GetIntegerArray(int_array_key, std::vector<int>{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<int64_t>{99});
EXPECT_EQ(result[0], 99);
pm.SetInt64Array(int64_array_key, std::vector<int64_t>{16, 7, 64, 100, 12});
result = pm.GetInt64Array(int64_array_key, std::vector<int64_t>{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<std::string>{"value", "morning"});
EXPECT_EQ(result[1], "morning");
pm.SetStringArray(
string_array_key,
std::vector<std::string>{"one", "two", "three", "four", "five"});
result = pm.GetStringArray(string_array_key,
std::vector<std::string>{"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
@@ -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 <exception>
#include <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include <optional>
#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<json> 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<json> 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<json> 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
@@ -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 <optional>
#include <string>
#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<nlohmann::json> AttemptLoad();
std::optional<nlohmann::json> RestoreFromBackup();
private:
absl::Mutex mutex_;
const std::string path_;
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_
@@ -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 <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include <optional>
#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<std::filesystem::path> 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<std::filesystem::path> 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<std::filesystem::path> 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<json> 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<std::filesystem::path> 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<json> 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
@@ -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 <algorithm>
#include <memory>
#include <utility>
#include "absl/time/time.h"
#include "internal/platform/logging.h"
namespace nearby {
namespace linux {
ScheduledExecutor::ScheduledExecutor()
: executor_(std::make_unique<nearby::linux::Executor>()),
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<api::Cancelable> 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<ScheduledTask>& task) { return task->IsDone(); });
std::shared_ptr<ScheduledTask> task =
std::make_shared<ScheduledTask>(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
@@ -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 <memory>
#include <utility>
#include <vector>
#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<api::Cancelable> 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<nearby::linux::Executor> executor_ = nullptr;
std::vector<std::shared_ptr<ScheduledTask>> scheduled_tasks_;
std::atomic_bool shut_down_ = false;
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_
@@ -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 <memory>
#include <utility>
#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<ScheduledExecutor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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<ScheduledExecutor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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<ScheduledExecutor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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<ScheduledExecutor>();
std::string output = std::string();
// Container to note threads that ran
std::unique_ptr<std::vector<std::thread::id>> threadIds =
std::make_unique<std::vector<std::thread::id>>();
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
@@ -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_
@@ -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<nearby::linux::Executor>(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
@@ -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<void()>) 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<nearby::linux::Executor> executor_;
std::atomic_bool shut_down_ = false;
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPL_LINUX_SUBMITTABLE_EXECUTOR_H_
@@ -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 <utility>
#include <thread>
#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<SubmittableExecutor>();
std::string output = std::string();
// Container to note threads that ran
auto threadIds = std::make_unique<std::vector<std::thread::id>>();
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<SubmittableExecutor>();
std::string output = std::string();
// Container to note threads that ran
auto threadIds = std::make_unique<std::vector<std::thread::id>>();
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<SubmittableExecutor>();
std::string output = std::string();
// Container to note threads that ran
auto threadIds = std::make_unique<std::vector<std::thread::id>>();
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<SubmittableExecutor>();
std::unique_ptr<std::string> output = std::make_unique<std::string>();
// Container to note threads that ran
auto threadIds = std::make_unique<std::vector<std::thread::id>>();
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<SubmittableExecutor>();
std::unique_ptr<std::string> output = std::make_unique<std::string>();
// Container to note threads that ran
auto threadIds = std::make_unique<std::vector<std::thread::id>>();
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<SubmittableExecutor>();
std::unique_ptr<std::string> output = std::make_unique<std::string>();
// Container to note threads that ran
auto threadIds = std::make_unique<std::vector<std::thread::id>>();
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
@@ -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::nanoseconds>(
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_
@@ -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_
@@ -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 <algorithm>
#include <sstream>
#include <codecvt>
#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<std::codecvt_utf8_utf16<wchar_t>> 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
@@ -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 <string>
#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_
@@ -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 <queue>
#include <utility>
#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> 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<std::vector<std::thread>> thread_pool = std::make_unique<std::vector<std::thread>>();
// 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<std::vector<std::thread>> &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
@@ -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 <memory>
#include <queue>
#include <utility>
#include <vector>
#include <thread>
#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<ThreadPool> 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<std::vector<std::thread>> &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<Runnable> tasks_ ABSL_GUARDED_BY(mutex_);
// All the threads run in the pool
std::unique_ptr<std::vector<std::thread>> 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_
@@ -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 <vector>
#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<int> completed_tasks;
std::vector<int> 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
@@ -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<void()> 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<uint16_t> 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<SubmittableExecutor>();
}
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<void()>* callback =
reinterpret_cast<absl::AnyInvocable<void()>*>(lpParam);
if (*callback != nullptr) {
(*callback)();
}
}
} // namespace linux
} // namespace nearby
@@ -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 <memory>
#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<void()> 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<void()> callback_;
uint16_t handle_ ABSL_GUARDED_BY(mutex_) = 0;
std::unique_ptr<TimerQueue> timer_queue_handle_;
std::unique_ptr<SubmittableExecutor> task_executor_ ABSL_GUARDED_BY(mutex_) =
nullptr;
};
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPL_LINUX_TIMER_H_
@@ -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> TimerQueue::CreateTimerQueue() {
return std::unique_ptr<TimerQueue>(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<uint16_t> TimerQueue::CreateTimerQueueTimer(absl::AnyInvocable<void(void *lpParameter)> 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
@@ -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 <memory>
#include <functional>
#include <thread>
#include <chrono>
#include <set>
#include <future>
#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<TimerQueue> CreateTimerQueue();
// Returns a thread unique identifier that the timer is running on if it is successful. Status if not.
absl::StatusOr<uint16_t> CreateTimerQueueTimer(absl::AnyInvocable<void(void *lpParameter)> 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<std::chrono::steady_clock> Due;
mutable absl::AnyInvocable<void(void *lpParameter)> Callback;
mutable std::chrono::milliseconds Period;
mutable void *Parameter = nullptr;
mutable long int Flags;
mutable std::future<void> 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<TimerWork> work_;
std::vector<std::thread> 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_
@@ -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 <chrono> // NOLINT
// NOLINT
#include <memory>
#include <thread> // 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<nearby::api::Timer> 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<nearby::api::Timer> 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
@@ -0,0 +1,363 @@
// 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 <cerrno>
#include <codecvt>
#include <cstdint>
#include <exception>
#include <stdexcept>
#include <string>
#include <vector>
// 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 <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h>
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<uint8_t>(
static_cast<unsigned char>(*(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<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(str);
}
std::string wstring_to_string(std::wstring wstr) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(wstr);
}
std::vector<std::string> GetIpv4Addresses() {
std::vector<std::string> 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;
}
}
freeifaddrs(interface);
return result;
}
std::vector<std::string> Get4BytesIpv4Addresses() {
std::vector<std::string> result;
std::vector<std::string> 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<winrt::Windows::Foundation::IPropertyValue>();
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<winrt::Windows::Foundation::IPropertyValue>();
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<winrt::Windows::Foundation::IPropertyValue>();
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<winrt::Windows::Foundation::IPropertyValue>();
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<std::string> InspectableReader::ReadStringArray(
IInspectable inspectable) {
std::vector<std::string> result;
if (inspectable == nullptr) {
return result;
}
auto property_value =
inspectable.try_as<winrt::Windows::Foundation::IPropertyValue>();
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<winrt::hstring> strings;
property_value.GetStringArray(strings);
for (winrt::hstring str : strings) {
result.push_back(winrt::to_string(str));
}
return result;
}
*/
}
} // namespace linux
} // namespace nearby
@@ -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 <cstdint>
#include <string>
#include <vector>
#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<std::string> GetIpv4Addresses();
std::vector<std::string> 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<std::string> ReadStringArray(IInspectable inspectable);
};
*/
} // namespace linux
} // namespace nearby
#endif // PLATFORM_IMPL_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.
#include "internal/platform/implementation/linux/utils.h"
#include <string>
#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