Move registry accessor code to nearby platform.

PiperOrigin-RevId: 852969284
This commit is contained in:
Francis Tsui
2026-01-06 15:58:19 -08:00
committed by Copybara-Service
parent 74c921f911
commit d302e8459d
6 changed files with 977 additions and 0 deletions
@@ -199,6 +199,48 @@ cc_library(
],
)
cc_library(
name = "registry_accessor",
hdrs = ["registry_accessor.h"],
tags = ["windows"],
)
cc_library(
name = "registry",
srcs = [
"registry.cc",
],
hdrs = [
"registry.h",
],
linkopts = [
"shlwapi.lib",
],
tags = ["windows"],
visibility = [
"//internal/platform/implementation/windows:__subpackages__",
"//location/nearby:__subpackages__",
"//sharing/internal/impl/windows:__subpackages__",
],
deps = [
":registry_accessor",
"//internal/platform:logging",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "mock_registry_accessor",
testonly = True,
hdrs = ["mock_registry_accessor.h"],
tags = ["windows"],
visibility = ["//visibility:public"],
deps = [
":registry_accessor",
],
)
cc_library(
name = "windows",
srcs = [
@@ -564,3 +606,18 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "registry_test",
size = "small",
timeout = "short",
srcs = [
"registry_test.cc",
],
deps = [
":mock_registry_accessor",
":registry",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_googletest//:gtest_main",
],
)
@@ -0,0 +1,58 @@
// 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.
#ifndef THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_MOCK_REGISTRY_ACCESSOR_H_
#define THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_MOCK_REGISTRY_ACCESSOR_H_
#include "gmock/gmock.h"
#include "absl/container/flat_hash_map.h"
#include "internal/platform/implementation/windows/registry_accessor.h"
namespace nearby::platform::windows {
class MockRegistryAccessor : public RegistryAccessor {
public:
MockRegistryAccessor() = default;
MockRegistryAccessor(const MockRegistryAccessor&) = delete;
MockRegistryAccessor& operator=(const MockRegistryAccessor&) = delete;
~MockRegistryAccessor() override = default;
MOCK_METHOD(LSTATUS, ReadDWordValue,
(HKEY key, const std::string& sub_key,
const std::string& value_name, DWORD& value_data),
(override));
MOCK_METHOD(LSTATUS, ReadStringValue,
(HKEY key, const std::string& sub_key,
const std::string& value_name, std::string& value_data),
(override));
MOCK_METHOD(LSTATUS, WriteDWordValue,
(HKEY key, const std::string& sub_key,
const std::string& value_name, DWORD value_data,
bool create_sub_key),
(override));
MOCK_METHOD(LSTATUS, WriteStringValue,
(HKEY key, const std::string& sub_key,
const std::string& value_name, const std::string& value_data,
bool create_sub_key),
(override));
MOCK_METHOD(LSTATUS, EnumValues,
(HKEY key, const std::string& sub_key,
(absl::flat_hash_map<std::string, std::string>)& string_values,
(absl::flat_hash_map<std::string, DWORD>)& dword_values),
(override));
};
} // namespace nearby::platform::windows
#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_MOCK_REGISTRY_ACCESSOR_H_
@@ -0,0 +1,447 @@
// 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/windows/registry.h"
#define _WIN32_WINNT _WIN32_WINNT_WIN10
#include <windows.h> // NOLINT
#include <winreg.h> // NOLINT
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/windows/registry_accessor.h"
#include "internal/platform/logging.h"
namespace nearby::platform::windows {
namespace {
constexpr int kMaxRegistryStringValueBufferSize = 1024;
constexpr absl::string_view kGoogleUpdateClientsKey =
R"(Google\Update\Clients\)";
constexpr absl::string_view kGoogleUpdateClientStateKey =
R"(Google\Update\ClientState\)";
constexpr absl::string_view kGoogleUpdateClientStateMediumKey =
R"(Google\Update\ClientStateMedium\)";
constexpr absl::string_view kGoogleNearbyShareKey = R"(Google\NearbyShare)";
constexpr absl::string_view kGoogleNearbyShareFlagsKey =
R"(Google\NearbyShare\Flags)";
constexpr absl::string_view kWindowsKey =
R"(Microsoft\Windows NT\CurrentVersion)";
constexpr absl::string_view kHiveRootSoftware = "SOFTWARE";
constexpr absl::string_view kHiveRootSoftwareWow6432Node =
"SOFTWARE\\WOW6432Node";
constexpr absl::string_view kHiveRootSystem = "SYSTEM";
constexpr absl::string_view kQuickShareAppProductId =
"{232066FE-FF4D-4C25-83B4-3F8747CF7E3A}";
// RegistryAccessor for actual Win API implementation.
class WindowsRegistryAccessor : public RegistryAccessor {
public:
WindowsRegistryAccessor() = default;
~WindowsRegistryAccessor() override = default;
WindowsRegistryAccessor(const WindowsRegistryAccessor&) = delete;
WindowsRegistryAccessor& operator=(const WindowsRegistryAccessor&) = delete;
LSTATUS ReadDWordValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
DWORD& value_data) override {
DWORD value_size = sizeof(DWORD);
return RegGetValueA(
key, sub_key.data(), value_name.data(),
RRF_RT_DWORD | RRF_ZEROONFAILURE | RRF_SUBKEY_WOW6432KEY,
/*pdwType=*/nullptr, static_cast<void*>(&value_data), &value_size);
};
LSTATUS ReadStringValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
std::string& value_data) override {
char value_buffer[kMaxRegistryStringValueBufferSize];
DWORD value_size = kMaxRegistryStringValueBufferSize;
LSTATUS status = RegGetValueA(
key, sub_key.data(), value_name.data(),
RRF_RT_REG_SZ | RRF_ZEROONFAILURE | RRF_SUBKEY_WOW6432KEY,
/*pdwType=*/nullptr, static_cast<void*>(&value_buffer), &value_size);
if (status == ERROR_SUCCESS) {
value_data = value_buffer;
}
return status;
};
LSTATUS WriteDWordValue(HKEY key, const std::string& sub_key,
const std::string& value_name, DWORD value_data,
bool create_sub_key) override {
HKEY h_sub_key;
LSTATUS status = RegOpenKeyExA(key, sub_key.data(), /*ulOptions=*/0,
KEY_WRITE | KEY_WOW64_32KEY, &h_sub_key);
if (create_sub_key && status == ERROR_FILE_NOT_FOUND) {
status = RegCreateKeyExA(key, sub_key.data(), /*reserved*/ 0,
/*lpClass=*/nullptr, REG_OPTION_NON_VOLATILE,
KEY_WRITE | KEY_WOW64_32KEY,
/*lpSecurityAttributes=*/nullptr, &h_sub_key,
/*lpdwDisposition=*/nullptr);
}
if (status != ERROR_SUCCESS) {
return status;
}
return RegSetKeyValueA(h_sub_key, /*lpSubKey=*/nullptr, value_name.data(),
REG_DWORD, static_cast<const void*>(&value_data),
sizeof(DWORD));
}
LSTATUS WriteStringValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
const std::string& value_data,
bool create_sub_key) override {
HKEY h_sub_key;
LSTATUS status = RegOpenKeyExA(key, sub_key.data(), /*ulOptions=*/0,
KEY_WRITE | KEY_WOW64_32KEY, &h_sub_key);
if (create_sub_key && status == ERROR_FILE_NOT_FOUND) {
status = RegCreateKeyExA(key, sub_key.data(), /*reserved*/ 0,
/*lpClass=*/nullptr, REG_OPTION_NON_VOLATILE,
KEY_WRITE | KEY_WOW64_32KEY,
/*lpSecurityAttributes=*/nullptr, &h_sub_key,
/*lpdwDisposition=*/nullptr);
}
if (status != ERROR_SUCCESS) {
return status;
}
return RegSetKeyValueA(h_sub_key, /*lpSubKey=*/nullptr, value_name.data(),
REG_SZ, static_cast<const void*>(value_data.data()),
value_data.size() + 1);
}
LSTATUS EnumValues(
HKEY key, const std::string& sub_key,
absl::flat_hash_map<std::string, std::string>& string_values,
absl::flat_hash_map<std::string, DWORD>& dword_values) override {
HKEY h_sub_key;
LSTATUS status =
RegOpenKeyExA(key, sub_key.data(), /*ulOptions=*/0,
KEY_QUERY_VALUE | KEY_WOW64_32KEY, &h_sub_key);
if (status != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to open key: " << sub_key << ", status: " << status;
return status;
}
DWORD index = 0;
std::string value_name;
value_name.reserve(500);
do {
DWORD type = 0;
DWORD name_size = value_name.capacity();
DWORD value_size = 0;
status = RegEnumValueA(h_sub_key, index, value_name.data(), &name_size,
/*lpReserved=*/nullptr, &type, /*lpData=*/nullptr,
&value_size);
if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
break;
}
if (name_size >= value_name.capacity()) {
value_name.reserve(name_size + 1);
}
name_size = value_name.capacity();
if (type == REG_SZ) {
std::string value(value_size, '\0');
value_size = value.size();
status =
RegEnumValueA(h_sub_key, index, value_name.data(), &name_size,
/*lpReserved=*/nullptr, &type,
reinterpret_cast<LPBYTE>(value.data()), &value_size);
if (status != ERROR_SUCCESS) {
break;
}
// value_size may or may not include null terminator. If it does we
// need to remove it from the string.
if (value_size > 0 && value[value_size - 1] == '\0') {
value.resize(value_size - 1);
}
string_values.emplace(std::string(value_name.data(), name_size),
std::move(value));
} else if (type == REG_DWORD) {
DWORD value = 0;
value_size = sizeof(DWORD);
status = RegEnumValueA(h_sub_key, index, value_name.data(), &name_size,
/*lpReserved=*/nullptr, &type,
reinterpret_cast<LPBYTE>(&value), &value_size);
if (status != ERROR_SUCCESS) {
break;
}
dword_values.emplace(std::string(value_name.data(), name_size), value);
}
++index;
} while (status == ERROR_SUCCESS);
RegCloseKey(h_sub_key);
if (status == ERROR_NO_MORE_ITEMS) {
return ERROR_SUCCESS;
}
return status;
}
};
std::optional<std::string> GetKey(Registry::Key key,
absl::string_view product_id) {
switch (key) {
case Registry::Key::kClients:
return absl::StrCat(kGoogleUpdateClientsKey, product_id);
case Registry::Key::kClientState:
return absl::StrCat(kGoogleUpdateClientStateKey, product_id);
case Registry::Key::kClientStateMedium:
return absl::StrCat(kGoogleUpdateClientStateMediumKey, product_id);
case Registry::Key::kNearbyShare:
return std::string(kGoogleNearbyShareKey);
case Registry::Key::kNearbyShareFlags:
return std::string(kGoogleNearbyShareFlagsKey);
case Registry::Key::kWindows:
return std::string(kWindowsKey);
}
return std::nullopt;
}
std::optional<HKEY> GetHiveKey(Registry::Hive hive) {
switch (hive) {
case Registry::Hive::kCurrentConfig:
return HKEY_CURRENT_CONFIG;
case Registry::Hive::kCurrentUser:
return HKEY_CURRENT_USER;
case Registry::Hive::kSoftware:
case Registry::Hive::kSystem:
return HKEY_LOCAL_MACHINE;
}
return std::nullopt;
}
absl::string_view GetHiveRoot(Registry::Hive hive) {
switch (hive) {
case Registry::Hive::kCurrentConfig:
case Registry::Hive::kCurrentUser:
return kHiveRootSoftware;
case Registry::Hive::kSoftware:
#ifndef _WIN64
return kHiveRootSoftware;
#else
return kHiveRootSoftwareWow6432Node;
#endif
case Registry::Hive::kSystem:
return kHiveRootSystem;
default:
return "";
}
}
const char* GetHiveNameForLog(Registry::Hive hive) {
switch (hive) {
case Registry::Hive::kCurrentConfig:
return "HKEY_CURRENT_CONFIG";
case Registry::Hive::kCurrentUser:
return "HKEY_CURRENT_USER";
case Registry::Hive::kSoftware:
case Registry::Hive::kSystem:
return "HKEY_LOCAL_MACHINE";
}
return "[REQUESTED HIVE OUT OF ALLOW LIST RANGE]";
}
std::optional<std::pair<HKEY, std::string>> LookupRegistryPath(
Registry::Hive hive, Registry::Key key, absl::string_view product_id) {
std::optional<HKEY> hKey = GetHiveKey(hive);
if (!hKey.has_value()) {
LOG(ERROR) << "Requested registry hive is out of allow list range: "
<< static_cast<int>(hive);
return std::nullopt;
}
std::optional<std::string> keyPath = GetKey(key, product_id);
if (!keyPath.has_value()) {
LOG(ERROR) << "Requested registry key is out of allow list range: "
<< static_cast<int>(key);
return std::nullopt;
}
return std::make_pair(hKey.value(),
absl::StrCat(GetHiveRoot(hive), "\\", keyPath.value()));
}
// WindowsRegistryAccessor has trivial destructor.
WindowsRegistryAccessor windows_accessor;
RegistryAccessor* test_accessor = nullptr;
RegistryAccessor* getRegistryAccessor() {
if (test_accessor == nullptr) {
return &windows_accessor;
}
return test_accessor;
}
absl::string_view (*product_id_func)() = nullptr;
absl::string_view GetProductId() {
if (product_id_func == nullptr) {
return kQuickShareAppProductId;
}
return product_id_func();
}
#define REG_LOG(severity, message, hive, key, val, pretty_name, status) \
LOG(severity) << message << " " << GetHiveNameForLog(hive) << "\\" << key \
<< "\\" << val \
<< (pretty_name.has_value() \
? " (" + std::string(pretty_name.value()) + ")" \
: "") \
<< ", status: " << status;
} // namespace
std::optional<uint32_t> Registry::ReadDword(
Registry::Hive hive, Registry::Key key, const std::string& value,
std::optional<absl::string_view> pretty_name) {
DWORD result = 0;
std::optional<std::pair<HKEY, std::string>> path =
LookupRegistryPath(hive, key, GetProductId());
if (!path.has_value()) {
return std::nullopt;
}
HKEY hKey = path.value().first;
const std::string& qualified_key = path.value().second;
LSTATUS status =
getRegistryAccessor()->ReadDWordValue(hKey, qualified_key, value, result);
if (status != ERROR_SUCCESS) {
REG_LOG(WARNING, "Unable to read registry value", hive, qualified_key,
value, pretty_name, status);
return std::nullopt;
}
REG_LOG(INFO, "Successfully read registry value", hive, qualified_key, value,
pretty_name, status);
return static_cast<uint32_t>(result);
}
std::optional<std::string> Registry::ReadString(
Registry::Hive hive, Registry::Key key, const std::string& value,
std::optional<absl::string_view> pretty_name) {
std::optional<std::tuple<HKEY, std::string>> path =
LookupRegistryPath(hive, key, GetProductId());
if (!path.has_value()) {
return std::nullopt;
}
HKEY hKey = std::get<0>(path.value());
const std::string& qualified_key = std::get<1>(path.value());
std::string result;
LSTATUS status = getRegistryAccessor()->ReadStringValue(hKey, qualified_key,
value, result);
if (status != ERROR_SUCCESS) {
REG_LOG(WARNING, "Unable to read registry value", hive, qualified_key,
value, pretty_name, status);
return std::nullopt;
}
REG_LOG(INFO, "Successfully read registry value", hive, qualified_key, value,
pretty_name, status);
return result;
}
bool Registry::SetDword(Registry::Hive hive, Registry::Key key,
const std::string& value, uint32_t data,
bool create_sub_key) {
std::optional<std::tuple<HKEY, std::string>> path =
LookupRegistryPath(hive, key, GetProductId());
if (!path.has_value()) {
return false;
}
HKEY hKey = std::get<0>(path.value());
const std::string& qualified_key = std::get<1>(path.value());
LSTATUS status = getRegistryAccessor()->WriteDWordValue(
hKey, qualified_key, value, data, create_sub_key);
std::optional<absl::string_view> empty = std::nullopt;
if (status == ERROR_SUCCESS) {
REG_LOG(INFO, "Successfully wrote " << data << " to", hive, qualified_key,
value, empty, status);
return true;
}
REG_LOG(WARNING, "Failed to write " << data << " to", hive, qualified_key,
value, empty, status);
return false;
}
bool Registry::SetString(Hive hive, Key key, const std::string& value,
const std::string& data, bool create_sub_key) {
std::optional<std::tuple<HKEY, std::string>> path =
LookupRegistryPath(hive, key, GetProductId());
if (!path.has_value()) {
return false;
}
HKEY hKey = std::get<0>(path.value());
const std::string& qualified_key = std::get<1>(path.value());
LSTATUS status = getRegistryAccessor()->WriteStringValue(
hKey, qualified_key, value, data, create_sub_key);
std::optional<absl::string_view> empty = std::nullopt;
if (status == ERROR_SUCCESS) {
REG_LOG(INFO, "Successfully wrote " << data << " to", hive, qualified_key,
value, empty, status);
return true;
}
REG_LOG(WARNING, "Failed to write " << data << " to", hive, qualified_key,
value, empty, status);
return false;
}
bool Registry::EnumValues(
Hive hive, Key key,
absl::flat_hash_map<std::string, std::string>& string_values,
absl::flat_hash_map<std::string, DWORD>& dword_values) {
std::optional<std::tuple<HKEY, std::string>> path =
LookupRegistryPath(hive, key, GetProductId());
if (!path.has_value()) {
return false;
}
HKEY hKey = std::get<0>(path.value());
const std::string& qualified_key = std::get<1>(path.value());
LSTATUS status = getRegistryAccessor()->EnumValues(
hKey, qualified_key, string_values, dword_values);
if (status != ERROR_SUCCESS) {
REG_LOG(WARNING, "Failed to enumerate values", hive, qualified_key,
/*value=*/"", /*pretty_name=*/std::optional<absl::string_view>(),
status);
return false;
}
return true;
}
void Registry::SetRegistryAccessorForTest(RegistryAccessor* registry_accessor) {
test_accessor = registry_accessor;
}
void Registry::SetProductIdGetter(absl::string_view (*product_id_getter)()) {
product_id_func = product_id_getter;
}
} // namespace nearby::platform::windows
@@ -0,0 +1,87 @@
// 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_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_H_
#define THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_H_
#include <cstdint>
#include <optional>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/windows/registry_accessor.h"
namespace nearby::platform::windows {
class Registry {
public:
enum class Hive {
kCurrentConfig,
kCurrentUser,
// Local machine hives
// kSAM, Blocked
// kSecurity, Blocked
kSoftware,
kSystem
};
enum class Key {
kClients, // Read from Google\Update\Clients.
kClientState, // Read from Google\Update\ClientState.
kClientStateMedium, // Read from Google\Update\ClientStateMedium.
kNearbyShare, // Read from Google\NearbyShare.
kNearbyShareFlags, // Read from Google\NearbyShare\Flags.
kWindows // Read from Microsoft\Windows NT\CurrentVersion, for testing.
};
static std::optional<uint32_t> ReadDword(
Hive hive, Key key, const std::string& value,
std::optional<absl::string_view> pretty_name = std::nullopt);
static std::optional<std::string> ReadString(
Hive hive, Key key, const std::string& value,
std::optional<absl::string_view> pretty_name = std::nullopt);
// If |create_sub_key| is true, the sub_key will be created if it does not
// exist.
// NOTE: do not set create_sub_key to true unless you know the current user
// has the permission to create the sub key otherwise elevation will be
// required.
static bool SetDword(Hive hive, Key key, const std::string& value,
uint32_t data, bool create_sub_key = false);
// If |create_sub_key| is true, the sub_key will be created if it does not
// exist.
// NOTE: do not set create_sub_key to true unless you know the current user
// has the permission to create the sub key otherwise elevation will be
// required.
static bool SetString(Hive hive, Key key, const std::string& value,
const std::string& data, bool create_sub_key = false);
// Enumerate all string and dword values in the registry key.
static bool EnumValues(
Hive hive, Key key,
absl::flat_hash_map<std::string, std::string>& string_values,
absl::flat_hash_map<std::string, DWORD>& dword_values);
// Use provided RegistryAccessor for testing.
static void SetRegistryAccessorForTest(RegistryAccessor* registry_accessor);
// Set a function that is used to provide a Omaha product Id.
static void SetProductIdGetter(absl::string_view (*product_id_getter)());
};
} // namespace nearby::platform::windows
#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_H_
@@ -0,0 +1,59 @@
// 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.
#ifndef THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_ACCESSOR_H_
#define THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_ACCESSOR_H_
#define _WIN32_WINNT _WIN32_WINNT_WIN10
#include <windows.h>
#include <string>
#include "absl/container/flat_hash_map.h"
namespace nearby::platform::windows {
// Interface wrapping windows registry access to facilitate testing.
class RegistryAccessor {
public:
virtual ~RegistryAccessor() = default;
virtual LSTATUS ReadDWordValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
DWORD& value_data) = 0;
virtual LSTATUS ReadStringValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
std::string& value_data) = 0;
// If |create_sub_key| is true, the sub_key will be created if it does not
// exist.
virtual LSTATUS WriteDWordValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
DWORD value_data, bool create_sub_key) = 0;
// If |create_sub_key| is true, the sub_key will be created if it does not
// exist.
virtual LSTATUS WriteStringValue(HKEY key, const std::string& sub_key,
const std::string& value_name,
const std::string& value_data,
bool create_sub_key) = 0;
virtual LSTATUS EnumValues(
HKEY key, const std::string& sub_key,
absl::flat_hash_map<std::string, std::string>& string_values,
absl::flat_hash_map<std::string, DWORD>& dword_values) = 0;
};
} // namespace nearby::platform::windows
#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_ACCESSOR_H_
@@ -0,0 +1,269 @@
// 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/windows/registry.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/windows/mock_registry_accessor.h"
namespace nearby::platform::windows {
namespace {
using ::testing::_;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::IsFalse;
using ::testing::IsTrue;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Return;
constexpr absl::string_view kClientsRegKeyPath =
R"(SOFTWARE\Google\Update\Clients\{232066FE-FF4D-4C25-83B4-3F8747CF7E3A})";
constexpr absl::string_view kClientStateRegKeyPath =
R"(SOFTWARE\Google\Update\ClientState\{232066FE-FF4D-4C25-83B4-3F8747CF7E3A})";
constexpr absl::string_view kClientStateMediumRegKeyPath =
R"(SOFTWARE\Google\Update\ClientStateMedium\{232066FE-FF4D-4C25-83B4-3F8747CF7E3A})";
constexpr absl::string_view kTestProductId = "{test_product_id}";
constexpr absl::string_view kTestClientsRegKeyPath =
R"(SOFTWARE\Google\Update\Clients\{test_product_id})";
constexpr absl::string_view kTestClientStateRegKeyPath =
R"(SOFTWARE\Google\Update\ClientState\{test_product_id})";
constexpr absl::string_view kTestClientStateMediumRegKeyPath =
R"(SOFTWARE\Google\Update\ClientStateMedium\{test_product_id})";
absl::string_view GetTestProductId() { return kTestProductId; }
// Enable only for local runs that have the client state key created
// TODO(mattkaes): create the entire registry key chain if it's missing
TEST(DISABLED_Registry, CreateAndRead) {
const int test_level = 4;
bool set_success =
Registry::SetDword(Registry::Hive::kCurrentUser,
Registry::Key::kClientState, "log_level", test_level);
ASSERT_TRUE(set_success);
auto log_level = Registry::ReadDword(
Registry::Hive::kCurrentUser, Registry::Key::kClientState, "log_level");
ASSERT_TRUE(log_level.has_value());
EXPECT_EQ(test_level, log_level.value());
}
TEST(Registry, ReadDwordNonexistentValue) {
auto reg_value = Registry::ReadDword(
Registry::Hive::kCurrentUser, Registry::Key::kClientState,
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1");
EXPECT_THAT(reg_value.has_value(), IsFalse());
}
TEST(Registry, BlockBadHiveReadDword) {
auto reg_value = Registry::ReadDword(
static_cast<Registry::Hive>(891), Registry::Key::kClientState,
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1");
EXPECT_THAT(reg_value.has_value(), IsFalse());
}
TEST(Registry, BlockBadKeyReadDword) {
auto reg_value = Registry::ReadDword(
Registry::Hive::kCurrentUser, static_cast<Registry::Key>(891),
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1");
EXPECT_THAT(reg_value.has_value(), IsFalse());
}
TEST(Registry, ReadStringNonexistentValue) {
auto reg_value = Registry::ReadString(
Registry::Hive::kCurrentUser, Registry::Key::kNearbyShare,
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1");
EXPECT_THAT(reg_value.has_value(), IsFalse());
}
TEST(Registry, BlockBadHiveReadString) {
auto reg_value = Registry::ReadString(
static_cast<Registry::Hive>(891), Registry::Key::kClientState,
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1");
EXPECT_THAT(reg_value.has_value(), IsFalse());
}
TEST(Registry, BlockBadKeyReadString) {
auto reg_value = Registry::ReadString(
Registry::Hive::kCurrentUser, static_cast<Registry::Key>(891),
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1");
EXPECT_THAT(reg_value.has_value(), IsFalse());
}
TEST(Registry, BlockBadHiveWrite) {
auto success = Registry::SetDword(
static_cast<Registry::Hive>(891), Registry::Key::kClientState,
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1", 891);
EXPECT_THAT(success, IsFalse());
}
TEST(Registry, BlockBadKeyWrite) {
auto success = Registry::SetDword(
Registry::Hive::kCurrentUser, static_cast<Registry::Key>(891),
"NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1", 891);
EXPECT_THAT(success, IsFalse());
}
TEST(Registry, ReadStringSucceeds) {
std::optional<std::string> result = Registry::ReadString(
Registry::Hive::kSoftware, Registry::Key::kWindows, "ProductName");
EXPECT_THAT(result.has_value(), IsTrue());
EXPECT_THAT(result.value(), Not(IsEmpty()));
}
TEST(Registry, ReadDWORDSucceeds) {
std::optional<unsigned> result =
Registry::ReadDword(Registry::Hive::kSoftware, Registry::Key::kWindows,
"CurrentMajorVersionNumber");
EXPECT_THAT(result.has_value(), IsTrue());
EXPECT_THAT(result.value(), Not(Eq(0)));
}
// Enable only for local runs that have the client state key created
// This test requires you run with Admin privileges in order to write to
// HKEY_LOCAL_MACHINE.
TEST(Registry, DISABLED_ValidateKeyWrites) {
const int test_value = 10;
const std::pair<platform::windows::Registry::Hive,
platform::windows::Registry::Key>
test_hives[] = {{
platform::windows::Registry::Hive::kCurrentUser,
platform::windows::Registry::Key::kClientState,
},
{
platform::windows::Registry::Hive::kCurrentUser,
platform::windows::Registry::Key::kClients,
},
{
platform::windows::Registry::Hive::kSoftware,
platform::windows::Registry::Key::kClients,
}};
for (auto& key_set : test_hives) {
auto success = platform::windows::Registry::SetDword(
key_set.first, key_set.second, "test_key", test_value);
EXPECT_THAT(success, IsTrue());
if (!success) {
std::cout << "failed to write to hive=" << static_cast<int>(key_set.first)
<< ", key=" << static_cast<int>(key_set.second) << std::endl;
}
}
}
TEST(Registry, ValidateReadClientState) {
auto mock_registry_accessor =
std::make_unique<NiceMock<MockRegistryAccessor>>();
EXPECT_CALL(*mock_registry_accessor,
ReadDWordValue(_, std::string(kClientStateRegKeyPath), _, _))
.WillOnce(Return(ERROR_SUCCESS));
Registry::SetRegistryAccessorForTest(mock_registry_accessor.get());
(void)Registry::ReadDword(Registry::Hive::kCurrentUser,
Registry::Key::kClientState, "log_level");
}
TEST(Registry, ValidateReadClients) {
auto mock_registry_accessor =
std::make_unique<NiceMock<MockRegistryAccessor>>();
EXPECT_CALL(*mock_registry_accessor,
ReadStringValue(_, std::string(kClientsRegKeyPath), _, _))
.WillOnce(Return(ERROR_SUCCESS));
Registry::SetRegistryAccessorForTest(mock_registry_accessor.get());
std::optional<std::string> result = Registry::ReadString(
Registry::Hive::kCurrentUser, Registry::Key::kClients, "log_level");
}
TEST(Registry, ValidateReadClientStateMedium) {
auto mock_registry_accessor =
std::make_unique<NiceMock<MockRegistryAccessor>>();
EXPECT_CALL(
*mock_registry_accessor,
ReadStringValue(_, std::string(kClientStateMediumRegKeyPath), _, _))
.WillOnce(Return(ERROR_SUCCESS));
Registry::SetRegistryAccessorForTest(mock_registry_accessor.get());
std::optional<std::string> result =
Registry::ReadString(Registry::Hive::kCurrentUser,
Registry::Key::kClientStateMedium, "log_level");
}
TEST(Registry, ValidateReadClientStateWithProductIdOverride) {
auto mock_registry_accessor =
std::make_unique<NiceMock<MockRegistryAccessor>>();
EXPECT_CALL(*mock_registry_accessor,
ReadDWordValue(_, std::string(kTestClientStateRegKeyPath), _, _))
.WillOnce(Return(ERROR_SUCCESS));
Registry::SetProductIdGetter(GetTestProductId);
Registry::SetRegistryAccessorForTest(mock_registry_accessor.get());
(void)Registry::ReadDword(Registry::Hive::kCurrentUser,
Registry::Key::kClientState, "log_level");
}
TEST(Registry, ValidateReadClientsWithProductIdOverride) {
auto mock_registry_accessor =
std::make_unique<NiceMock<MockRegistryAccessor>>();
EXPECT_CALL(*mock_registry_accessor,
ReadStringValue(_, std::string(kTestClientsRegKeyPath), _, _))
.WillOnce(Return(ERROR_SUCCESS));
Registry::SetProductIdGetter(GetTestProductId);
Registry::SetRegistryAccessorForTest(mock_registry_accessor.get());
std::optional<std::string> result = Registry::ReadString(
Registry::Hive::kCurrentUser, Registry::Key::kClients, "log_level");
}
TEST(Registry, ValidateReadClientStateMediumWithProductIdOverride) {
auto mock_registry_accessor =
std::make_unique<NiceMock<MockRegistryAccessor>>();
EXPECT_CALL(
*mock_registry_accessor,
ReadStringValue(_, std::string(kTestClientStateMediumRegKeyPath), _, _))
.WillOnce(Return(ERROR_SUCCESS));
Registry::SetProductIdGetter(GetTestProductId);
Registry::SetRegistryAccessorForTest(mock_registry_accessor.get());
std::optional<std::string> result =
Registry::ReadString(Registry::Hive::kCurrentUser,
Registry::Key::kClientStateMedium, "log_level");
}
TEST(Registry, ValidateEnumValues) {
Registry::SetRegistryAccessorForTest(nullptr);
EXPECT_TRUE(Registry::SetDword(Registry::Hive::kCurrentUser,
Registry::Key::kNearbyShareFlags, "12345",
12345, /*create_sub_key=*/true));
EXPECT_TRUE(Registry::SetString(Registry::Hive::kCurrentUser,
Registry::Key::kNearbyShareFlags, "98786",
"9876", /*create_sub_key=*/true));
absl::flat_hash_map<std::string, std::string> string_values;
absl::flat_hash_map<std::string, DWORD> dword_values;
Registry::EnumValues(Registry::Hive::kCurrentUser,
Registry::Key::kNearbyShareFlags, string_values,
dword_values);
EXPECT_THAT(string_values.size(), Eq(1));
EXPECT_THAT(dword_values.size(), Eq(1));
EXPECT_THAT(string_values["98786"], Eq("9876"));
EXPECT_THAT(dword_values["12345"], Eq(12345));
::RegDeleteKeyA(HKEY_CURRENT_USER, "Google\\NearbyShare\\Flags");
}
} // namespace
} // namespace nearby::platform::windows