mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Merge remote-tracking branch 'proatgram/linux-platform'
This commit is contained in:
@@ -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,
|
||||
¬ification](api::DeviceInfo::ScreenStatus status) {
|
||||
screen_locked_tracker = api::DeviceInfo::ScreenStatus::kLocked;
|
||||
notification.Notify();
|
||||
};
|
||||
|
||||
DeviceInfo device_info;
|
||||
device_info.RegisterScreenLockedListener("listener", listener);
|
||||
EXPECT_TRUE(notification.WaitForNotificationWithTimeout(absl::Seconds(5)));
|
||||
EXPECT_EQ(screen_locked_tracker, api::DeviceInfo::ScreenStatus::kLocked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user