diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index 7c69de93..c81a505d 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -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", + ], +) diff --git a/internal/platform/implementation/windows/mock_registry_accessor.h b/internal/platform/implementation/windows/mock_registry_accessor.h new file mode 100644 index 00000000..10ceac74 --- /dev/null +++ b/internal/platform/implementation/windows/mock_registry_accessor.h @@ -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)& string_values, + (absl::flat_hash_map)& dword_values), + (override)); +}; + +} // namespace nearby::platform::windows + +#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_MOCK_REGISTRY_ACCESSOR_H_ diff --git a/internal/platform/implementation/windows/registry.cc b/internal/platform/implementation/windows/registry.cc new file mode 100644 index 00000000..167e5b37 --- /dev/null +++ b/internal/platform/implementation/windows/registry.cc @@ -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 // NOLINT +#include // NOLINT + +#include +#include +#include +#include + +#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(&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(&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(&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(value_data.data()), + value_data.size() + 1); + } + + LSTATUS EnumValues( + HKEY key, const std::string& sub_key, + absl::flat_hash_map& string_values, + absl::flat_hash_map& 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(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(&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 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 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> LookupRegistryPath( + Registry::Hive hive, Registry::Key key, absl::string_view product_id) { + std::optional hKey = GetHiveKey(hive); + if (!hKey.has_value()) { + LOG(ERROR) << "Requested registry hive is out of allow list range: " + << static_cast(hive); + return std::nullopt; + } + + std::optional keyPath = GetKey(key, product_id); + if (!keyPath.has_value()) { + LOG(ERROR) << "Requested registry key is out of allow list range: " + << static_cast(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 Registry::ReadDword( + Registry::Hive hive, Registry::Key key, const std::string& value, + std::optional pretty_name) { + DWORD result = 0; + + std::optional> 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(result); +} + +std::optional Registry::ReadString( + Registry::Hive hive, Registry::Key key, const std::string& value, + std::optional pretty_name) { + std::optional> 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> 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 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> 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 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& string_values, + absl::flat_hash_map& dword_values) { + std::optional> 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(), + 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 diff --git a/internal/platform/implementation/windows/registry.h b/internal/platform/implementation/windows/registry.h new file mode 100644 index 00000000..6793d241 --- /dev/null +++ b/internal/platform/implementation/windows/registry.h @@ -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 +#include +#include + +#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 ReadDword( + Hive hive, Key key, const std::string& value, + std::optional pretty_name = std::nullopt); + static std::optional ReadString( + Hive hive, Key key, const std::string& value, + std::optional 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& string_values, + absl::flat_hash_map& 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_ diff --git a/internal/platform/implementation/windows/registry_accessor.h b/internal/platform/implementation/windows/registry_accessor.h new file mode 100644 index 00000000..a8bd7205 --- /dev/null +++ b/internal/platform/implementation/windows/registry_accessor.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 + +#include + +#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& string_values, + absl::flat_hash_map& dword_values) = 0; +}; + +} // namespace nearby::platform::windows + +#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_IMPL_WINDOWS_REGISTRY_ACCESSOR_H_ diff --git a/internal/platform/implementation/windows/registry_test.cc b/internal/platform/implementation/windows/registry_test.cc new file mode 100644 index 00000000..b657500a --- /dev/null +++ b/internal/platform/implementation/windows/registry_test.cc @@ -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 +#include +#include +#include + +#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(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(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(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(891), + "NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1"); + EXPECT_THAT(reg_value.has_value(), IsFalse()); +} + +TEST(Registry, BlockBadHiveWrite) { + auto success = Registry::SetDword( + static_cast(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(891), + "NA-bc1adb34-493f-4952-8ce6-bcf2acddfbe1", 891); + EXPECT_THAT(success, IsFalse()); +} + +TEST(Registry, ReadStringSucceeds) { + std::optional 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 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 + 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(key_set.first) + << ", key=" << static_cast(key_set.second) << std::endl; + } + } +} + +TEST(Registry, ValidateReadClientState) { + auto mock_registry_accessor = + std::make_unique>(); + 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>(); + EXPECT_CALL(*mock_registry_accessor, + ReadStringValue(_, std::string(kClientsRegKeyPath), _, _)) + .WillOnce(Return(ERROR_SUCCESS)); + Registry::SetRegistryAccessorForTest(mock_registry_accessor.get()); + std::optional result = Registry::ReadString( + Registry::Hive::kCurrentUser, Registry::Key::kClients, "log_level"); +} + +TEST(Registry, ValidateReadClientStateMedium) { + auto mock_registry_accessor = + std::make_unique>(); + EXPECT_CALL( + *mock_registry_accessor, + ReadStringValue(_, std::string(kClientStateMediumRegKeyPath), _, _)) + .WillOnce(Return(ERROR_SUCCESS)); + Registry::SetRegistryAccessorForTest(mock_registry_accessor.get()); + std::optional result = + Registry::ReadString(Registry::Hive::kCurrentUser, + Registry::Key::kClientStateMedium, "log_level"); +} + +TEST(Registry, ValidateReadClientStateWithProductIdOverride) { + auto mock_registry_accessor = + std::make_unique>(); + 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>(); + EXPECT_CALL(*mock_registry_accessor, + ReadStringValue(_, std::string(kTestClientsRegKeyPath), _, _)) + .WillOnce(Return(ERROR_SUCCESS)); + Registry::SetProductIdGetter(GetTestProductId); + Registry::SetRegistryAccessorForTest(mock_registry_accessor.get()); + std::optional result = Registry::ReadString( + Registry::Hive::kCurrentUser, Registry::Key::kClients, "log_level"); +} + +TEST(Registry, ValidateReadClientStateMediumWithProductIdOverride) { + auto mock_registry_accessor = + std::make_unique>(); + EXPECT_CALL( + *mock_registry_accessor, + ReadStringValue(_, std::string(kTestClientStateMediumRegKeyPath), _, _)) + .WillOnce(Return(ERROR_SUCCESS)); + Registry::SetProductIdGetter(GetTestProductId); + Registry::SetRegistryAccessorForTest(mock_registry_accessor.get()); + std::optional 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 string_values; + absl::flat_hash_map 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