diff --git a/modules/juce_core/native/juce_android_SystemStats.cpp b/modules/juce_core/native/juce_android_SystemStats.cpp index 8c3e416df8..4000613c69 100644 --- a/modules/juce_core/native/juce_android_SystemStats.cpp +++ b/modules/juce_core/native/juce_android_SystemStats.cpp @@ -47,6 +47,22 @@ namespace AndroidStatsHelpers javaString (name).get()))); } + static String getAndroidID() + { + auto* env = getEnv(); + + if (auto settings = (jclass) env->FindClass ("android/provider/Settings$Secure")) + { + if (auto fId = env->GetStaticFieldID (settings, "ANDROID_ID", "Ljava/lang/String;")) + { + auto androidID = (jstring) env->GetStaticObjectField (settings, fId); + return juceString (LocalRef (androidID)); + } + } + + return ""; + } + static String getLocaleValue (bool isRegion) { auto* env = getEnv(); @@ -170,6 +186,15 @@ String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocale String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } +String SystemStats::getUniqueDeviceID() +{ + auto id = String ((uint64_t) AndroidStatsHelpers::getAndroidID().hashCode64()); + + // Please tell someone at JUCE if this occurs + jassert (id.isNotEmpty()); + return id; +} + //============================================================================== void CPUInformation::initialise() noexcept { diff --git a/modules/juce_core/native/juce_linux_SystemStats.cpp b/modules/juce_core/native/juce_linux_SystemStats.cpp index 84abf88eab..5366995f12 100644 --- a/modules/juce_core/native/juce_linux_SystemStats.cpp +++ b/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -304,6 +304,64 @@ void CPUInformation::initialise() noexcept #endif } +String SystemStats::getUniqueDeviceID() +{ + static const auto deviceId = []() + { + const auto call = [] (auto command) -> String + { + ChildProcess proc; + + if (proc.start (command, ChildProcess::wantStdOut)) + return proc.readAllProcessOutput(); + + return {}; + }; + + auto data = call ("cat /sys/class/dmi/id/board_serial"); + + // 'board_serial' is enough on its own, fallback to bios stuff if we can't find it. + if (data.isEmpty()) + { + data = call ("cat /sys/class/dmi/id/bios_date") + + call ("cat /sys/class/dmi/id/bios_release") + + call ("cat /sys/class/dmi/id/bios_vendor") + + call ("cat /sys/class/dmi/id/bios_version"); + } + + auto cpuData = call ("lscpu"); + + if (cpuData.isNotEmpty()) + { + auto getCpuInfo = [&cpuData] (auto key) -> String + { + auto index = cpuData.indexOf (key); + + if (index >= 0) + { + auto start = cpuData.indexOf (index, ":"); + auto end = cpuData.indexOf (start, "\n"); + + return cpuData.substring (start + 1, end).trim(); + } + + return {}; + }; + + data += getCpuInfo ("CPU family:"); + data += getCpuInfo ("Model:"); + data += getCpuInfo ("Model name:"); + data += getCpuInfo ("Vendor ID:"); + } + + return String ((uint64_t) data.hashCode64()); + }(); + + // Please tell someone at JUCE if this occurs + jassert (deviceId.isNotEmpty()); + return deviceId; +} + //============================================================================== uint32 juce_millisecondsSinceStartup() noexcept { diff --git a/modules/juce_core/native/juce_mac_SystemStats.mm b/modules/juce_core/native/juce_mac_SystemStats.mm index 077cce685d..0f8c48ef6a 100644 --- a/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/modules/juce_core/native/juce_mac_SystemStats.mm @@ -351,4 +351,34 @@ int SystemStats::getPageSize() return (int) NSPageSize(); } +String SystemStats::getUniqueDeviceID() +{ + static const auto deviceId = [] + { + ChildProcess proc; + + if (proc.start ("ioreg -rd1 -c IOPlatformExpertDevice", ChildProcess::wantStdOut)) + { + constexpr const char key[] = "\"IOPlatformUUID\""; + constexpr const auto keyLen = (int) sizeof (key); + + auto output = proc.readAllProcessOutput(); + auto index = output.indexOf (key); + + if (index >= 0) + { + auto start = output.indexOf (index + keyLen, "\""); + auto end = output.indexOf (start + 1, "\""); + return output.substring (start + 1, end).replace("-", ""); + } + } + + return String(); + }(); + + // Please tell someone at JUCE if this occurs + jassert (deviceId.isNotEmpty()); + return deviceId; +} + } // namespace juce diff --git a/modules/juce_core/native/juce_win32_SystemStats.cpp b/modules/juce_core/native/juce_win32_SystemStats.cpp index 2fa3befbe5..2af887eb54 100644 --- a/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -592,4 +592,33 @@ String SystemStats::getDisplayLanguage() return languagesBuffer.data(); } +String SystemStats::getUniqueDeviceID() +{ + #define PROVIDER(string) (DWORD) (string[0] << 24 | string[1] << 16 | string[2] << 8 | string[3]) + + auto bufLen = GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), nullptr, 0); + + if (bufLen > 0) + { + HeapBlock buffer { bufLen }; + GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), (void*) buffer.getData(), bufLen); + + return [&] + { + uint64_t hash = 0; + const auto start = buffer.getData(); + const auto end = start + jmin (1024, (int) bufLen); + + for (auto dataPtr = start; dataPtr != end; ++dataPtr) + hash = hash * (uint64_t) 101 + *dataPtr; + + return String (hash); + }(); + } + + // Please tell someone at JUCE if this occurs + jassertfalse; + return {}; +} + } // namespace juce diff --git a/modules/juce_core/system/juce_SystemStats.cpp b/modules/juce_core/system/juce_SystemStats.cpp index 3fe9ac7fa6..8601bc0849 100644 --- a/modules/juce_core/system/juce_SystemStats.cpp +++ b/modules/juce_core/system/juce_SystemStats.cpp @@ -253,4 +253,25 @@ bool SystemStats::isRunningInAppExtensionSandbox() noexcept #endif } +#if JUCE_UNIT_TESTS + +class UniqueHardwareIDTest : public UnitTest +{ +public: + //============================================================================== + UniqueHardwareIDTest() : UnitTest ("UniqueHardwareID", UnitTestCategories::analytics) {} + + void runTest() override + { + beginTest ("getUniqueDeviceID returns usable data."); + { + expect (SystemStats::getUniqueDeviceID().isNotEmpty()); + } + } +}; + +static UniqueHardwareIDTest uniqueHardwareIDTest; + +#endif + } // namespace juce diff --git a/modules/juce_core/system/juce_SystemStats.h b/modules/juce_core/system/juce_SystemStats.h index 37924f0857..24b64c0947 100644 --- a/modules/juce_core/system/juce_SystemStats.h +++ b/modules/juce_core/system/juce_SystemStats.h @@ -145,8 +145,17 @@ public: The first choice for an ID is a filesystem ID for the user's home folder or windows directory. If that fails then this function returns the MAC addresses. */ + [[deprecated ("The identifiers produced by this function are not reliable. Use getUniqueDeviceID() instead.")]] static StringArray getDeviceIdentifiers(); + /** This method returns a machine unique ID unaffected by storage or peripheral + changes. + + This ID will be invalidated by changes to the motherboard and CPU on non-mobile + platforms, or resetting an Android device. + */ + static String getUniqueDeviceID(); + //============================================================================== // CPU and memory information.. diff --git a/modules/juce_core/unit_tests/juce_UnitTestCategories.h b/modules/juce_core/unit_tests/juce_UnitTestCategories.h index 4846fb315b..c4fa4b5dfc 100644 --- a/modules/juce_core/unit_tests/juce_UnitTestCategories.h +++ b/modules/juce_core/unit_tests/juce_UnitTestCategories.h @@ -35,7 +35,6 @@ namespace UnitTestCategories static const String cryptography { "Cryptography" }; static const String dsp { "DSP" }; static const String files { "Files" }; - static const String function { "Function" }; static const String graphics { "Graphics" }; static const String gui { "GUI" }; static const String json { "JSON" }; diff --git a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp index f2d527ee4f..0114483b9b 100644 --- a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp +++ b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp @@ -314,6 +314,14 @@ void OnlineUnlockStatus::MachineIDUtilities::addMACAddressesToList (StringArray& ids.add (getEncodedIDString (address.toString())); } +String OnlineUnlockStatus::MachineIDUtilities::getUniqueMachineID() +{ + return getEncodedIDString (SystemStats::getUniqueDeviceID()); +} + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + StringArray OnlineUnlockStatus::MachineIDUtilities::getLocalMachineIDs() { auto identifiers = SystemStats::getDeviceIdentifiers(); @@ -329,6 +337,9 @@ StringArray OnlineUnlockStatus::getLocalMachineIDs() return MachineIDUtilities::getLocalMachineIDs(); } +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_WARNINGS_MSVC + void OnlineUnlockStatus::userCancelled() { } @@ -378,22 +389,19 @@ bool OnlineUnlockStatus::applyKeyFile (String keyFileContent) return false; } -static bool canConnectToWebsite (const URL& url) -{ - return url.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress) - .withConnectionTimeoutMs (2000)) != nullptr; -} - static bool areMajorWebsitesAvailable() { - const char* urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com", - "https://google.com", "https://bing.com", "https://amazon.com", nullptr}; + static constexpr const char* const urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com", + "https://google.com", "https://bing.com", "https://amazon.com" }; + const auto canConnectToWebsite = [] (auto url) + { + return URL (url).createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress) + .withConnectionTimeoutMs (2000)) != nullptr; + }; - for (const char** url = urlsToTry; *url != nullptr; ++url) - if (canConnectToWebsite (URL (*url))) - return true; - - return false; + return std::any_of (std::begin (urlsToTry), + std::end (urlsToTry), + canConnectToWebsite); } OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleXmlReply (XmlElement xml) diff --git a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.h b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.h index 819830d24e..cdf9a2cfb3 100644 --- a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.h +++ b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.h @@ -242,6 +242,7 @@ public: /** Utility function that you may want to use in your machine-ID generation code. This adds some ID strings to the given array which represent each MAC address of the machine. */ + [[deprecated ("MAC addresses are no longer reliable methods of ID generation. You should use getUniqueMachineID() instead, You can still get a list of this device's MAC addresses with MACAddress::findAllAddresses().")]] static void addMACAddressesToList (StringArray& result); /** This method calculates some machine IDs based on things like network @@ -259,7 +260,14 @@ public: registration on machines which have had hardware added/removed since the product was first registered. */ + [[deprecated ("The identifiers generated by this function are no longer reliable. Use getUniqueMachineID() instead.")]] static StringArray getLocalMachineIDs(); + + /** Returns an encoded unique machine ID. + + @see SystemStats::getUniqueDeviceID + */ + static String getUniqueMachineID(); }; private: