diff --git a/include/vcpkg/base/parse.h b/include/vcpkg/base/parse.h index c4995786a3..37263813c4 100644 --- a/include/vcpkg/base/parse.h +++ b/include/vcpkg/base/parse.h @@ -56,11 +56,8 @@ namespace vcpkg { return is_lower_alpha(ch) || is_ascii_digit(ch) || ch == '-'; } - - static constexpr bool is_hex_digit(char32_t ch) - { - return is_ascii_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); - } + static constexpr bool is_hex_digit_lower(char32_t ch) { return is_ascii_digit(ch) || (ch >= 'a' && ch <= 'f'); } + static constexpr bool is_hex_digit(char32_t ch) { return is_hex_digit_lower(ch) || (ch >= 'A' && ch <= 'F'); } static constexpr bool is_word_char(char32_t ch) { return is_alphanum(ch) || ch == '_'; } StringView skip_whitespace(); diff --git a/include/vcpkg/base/system.deviceid.h b/include/vcpkg/base/system.deviceid.h new file mode 100644 index 0000000000..945eb73afe --- /dev/null +++ b/include/vcpkg/base/system.deviceid.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#include + +namespace vcpkg +{ + bool validate_device_id(StringView uuid); + + std::string get_device_id(const vcpkg::Filesystem& fs); +} diff --git a/include/vcpkg/base/system.h b/include/vcpkg/base/system.h index 6531546af1..7456664728 100644 --- a/include/vcpkg/base/system.h +++ b/include/vcpkg/base/system.h @@ -18,6 +18,8 @@ namespace vcpkg const ExpectedL& get_home_dir() noexcept; + const ExpectedL& get_platform_cache_root() noexcept; + const ExpectedL& get_platform_cache_vcpkg() noexcept; const ExpectedL& get_user_configuration_home() noexcept; diff --git a/include/vcpkg/metrics.h b/include/vcpkg/metrics.h index c128099f20..b18ccf5c6e 100644 --- a/include/vcpkg/metrics.h +++ b/include/vcpkg/metrics.h @@ -60,6 +60,7 @@ namespace vcpkg CommandName, DeploymentKind, DetectedCiEnvironment, + DevDeviceId, CiProjectId, CiOwnerId, InstallPlan_1, diff --git a/src/vcpkg-test/metrics.cpp b/src/vcpkg-test/metrics.cpp index 309abee672..728b33f4b7 100644 --- a/src/vcpkg-test/metrics.cpp +++ b/src/vcpkg-test/metrics.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include @@ -88,6 +90,22 @@ TEST_CASE ("user config parses multiple paragraphs ", "[metrics]") CHECK(result.last_completed_survey == "survey"); } +TEST_CASE ("device id", "[metrics]") +{ + CHECK(validate_device_id("c5337d65-1e69-46e1-af76-bffc7b9ff40a")); + + CHECK_FALSE(validate_device_id("")); + CHECK_FALSE(validate_device_id("nope")); + CHECK_FALSE(validate_device_id("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")); + CHECK_FALSE(validate_device_id("c5337d65-1e69-46e1-af76-bffc7b9ff40a ")); + CHECK_FALSE(validate_device_id("c5337d6--1e6--46e--af76--ffc7b9ff40a")); + CHECK_FALSE(validate_device_id("c5337d65-1e69-46e1-af76-bffc7b9ff4\r\n")); + CHECK_FALSE(validate_device_id("c5337d65-1e69-46e1-af76-bffc7b9ff4\0")); + CHECK_FALSE(validate_device_id("C5337D65-1E69-46E1-AF76-BFFC7b9ff40A")); + CHECK_FALSE(validate_device_id("{c5337d65-1e69-46e1-af76-bffc7b9ff40a}")); + CHECK_FALSE(validate_device_id("c5337d65:1e69:46e1:af76:bffc7b9ff40a")); +} + TEST_CASE ("user config to string", "[metrics]") { MetricsUserConfig uut; diff --git a/src/vcpkg/base/system.cpp b/src/vcpkg/base/system.cpp index ac4d4b8d83..21943e6c1c 100644 --- a/src/vcpkg/base/system.cpp +++ b/src/vcpkg/base/system.cpp @@ -1,10 +1,12 @@ #include #include #include +#include #include #include #include #include +#include #if defined(__APPLE__) #include @@ -504,15 +506,21 @@ namespace vcpkg } #endif - const ExpectedL& get_platform_cache_vcpkg() noexcept + const ExpectedL& get_platform_cache_root() noexcept { - static ExpectedL s_vcpkg = -#ifdef _WIN32 + static ExpectedL s_home = +#if defined(_WIN32) get_appdata_local() #else get_xdg_cache_home() #endif - .map([](const Path& p) { return p / "vcpkg"; }); + ; + return s_home; + } + + const ExpectedL& get_platform_cache_vcpkg() noexcept + { + static ExpectedL s_vcpkg = get_platform_cache_root().map([](const Path& p) { return p / "vcpkg"; }); return s_vcpkg; } @@ -555,17 +563,16 @@ namespace vcpkg { case REG_SZ: case REG_EXPAND_SZ: - // remove trailing nulls - while (!value->data.empty() && !value->data.back()) - { - value->data.pop_back(); - } - + { + auto length_in_wchar_ts = value->data.size() >> 1; + auto as_utf8 = + Strings::to_utf8(reinterpret_cast(value->data.data()), length_in_wchar_ts); + while (!as_utf8.empty() && as_utf8.back() == 0) { - auto length_in_wchar_ts = value->data.size() >> 1; - return Strings::to_utf8(reinterpret_cast(value->data.data()), - length_in_wchar_ts); + as_utf8.pop_back(); } + return as_utf8; + } default: return msg::format_error(msgRegistryValueWrongType, msg::path = format_registry_value_name(base_hkey, sub_key, valuename)); diff --git a/src/vcpkg/base/system.deviceid.cpp b/src/vcpkg/base/system.deviceid.cpp new file mode 100644 index 0000000000..0188dd3770 --- /dev/null +++ b/src/vcpkg/base/system.deviceid.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vcpkg +{ + // To ensure consistency, the device ID must follow the format specified below. + // - The value follows the 8-4-4-4-12 format(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + // - The value is all lowercase and only contain hyphens. No braces or brackets. + bool validate_device_id(StringView uuid) + { + static constexpr size_t UUID_LENGTH = 36; + static constexpr char format[] = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + if (uuid.size() != UUID_LENGTH) return false; + for (size_t i = 0; i < UUID_LENGTH; ++i) + { + if (format[i] == '-' && uuid[i] != '-') return false; + if (format[i] == 'x' && !ParserBase::is_hex_digit_lower(uuid[i])) return false; + } + return true; + } + +#if defined(_WIN32) + std::string get_device_id(const vcpkg::Filesystem&) + { + // The value is cached in the 64-bit Windows Registry under HKeyCurrentUser\SOFTWARE\Microsoft\DeveloperTools. + // The key is named 'deviceid' and its type REG_SZ(String value). + // The value is stored in plain text. + auto maybe_registry_value = + get_registry_string(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\DeveloperTools", "deviceid"); + if (auto registry_value = maybe_registry_value.get()) + { + auto& device_id = *registry_value; + return validate_device_id(device_id) ? device_id : std::string{}; + } + + auto new_device_id = Strings::ascii_to_lowercase(vcpkg::generate_random_UUID()); + const auto as_utf16 = Strings::to_utf16(new_device_id); + + const auto status = RegSetKeyValueW(HKEY_CURRENT_USER, + L"SOFTWARE\\Microsoft\\DeveloperTools", + L"deviceid", + REG_SZ, + as_utf16.c_str(), + static_cast((1 + as_utf16.size()) * sizeof(wchar_t))); + return (status != ERROR_SUCCESS) ? std::string{} : new_device_id; + } +#else + std::string get_device_id(const vcpkg::Filesystem& fs) + { + /* On Linux: + * - Use $XDG_CACHE_HOME if it is set and not empty, else use $HOME/.cache + * - The folder subpath is "/Microsoft/DeveloperTools" + * - The file is named 'deviceid' + * - The value is stored in UTF-8 plain text + * + * On MacOS: + * - Store the device id in the user's home directory ($HOME). + * - The folder subpath is "$HOME\Library\Application Support\Microsoft\DeveloperTools" + * - The file is named 'deviceid' + * - The value is stored in UTF-8 plain text + */ + const auto maybe_home_path = vcpkg::get_platform_cache_root(); + if (!maybe_home_path) + { + return {}; + } + + auto home_path = maybe_home_path.get(); + const auto container_path = +#if defined(__APPLE__) + *home_path / "Library/Application Support/Microsoft/DeveloperTools" +#else + *home_path / "Microsoft/DeveloperTools" +#endif + ; + const auto id_file_path = container_path / "deviceid"; + + std::error_code ec; + auto maybe_file = fs.exists(id_file_path, ec); + if (ec) + { + return {}; + } + + if (maybe_file) + { + auto contents = fs.read_contents(id_file_path, ec); + if (ec || !validate_device_id(contents)) + { + return {}; + } + return contents; + } + + auto new_device_id = Strings::ascii_to_lowercase(vcpkg::generate_random_UUID()); + fs.create_directories(container_path, ec); + if (ec) + { + return {}; + } + + fs.write_contents(id_file_path, new_device_id, ec); + if (ec) + { + return {}; + } + + return new_device_id; + } +#endif +} diff --git a/src/vcpkg/metrics.cpp b/src/vcpkg/metrics.cpp index 8f2b4453e9..db13a59da5 100644 --- a/src/vcpkg/metrics.cpp +++ b/src/vcpkg/metrics.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -131,6 +132,7 @@ namespace vcpkg {StringMetric::CommandName, "command_name", "z-preregister-telemetry"}, {StringMetric::DeploymentKind, "deployment_kind", "Git"}, {StringMetric::DetectedCiEnvironment, "detected_ci_environment", "Generic"}, + {StringMetric::DevDeviceId, "devdeviceid", "00000000-0000-0000-0000-000000000000"}, {StringMetric::CiProjectId, "ci_project_id", "0"}, {StringMetric::CiOwnerId, "ci_owner_id", "0"}, // spec:triplet:version,... @@ -578,6 +580,10 @@ namespace vcpkg auto session = MetricsSessionData::from_system(); auto submission = get_global_metrics_collector().get_submission(); + + auto deviceid = get_device_id(fs); + submission.track_string(StringMetric::DevDeviceId, deviceid); + const std::string payload = format_metrics_payload(user, session, submission); if (g_should_print_metrics.load()) {