From 6f2ef285eaba78ad4614ea0ce049c135606401f8 Mon Sep 17 00:00:00 2001 From: kdysput Date: Fri, 8 Jul 2022 15:11:01 +0200 Subject: [PATCH 1/2] Empty machine ID --- .../GuidHelper.cs} | 8 +- .../GuidHelper.cs.meta} | 0 .../Attributes/MachineAttributeProvider.cs | 35 +----- Runtime/Model/MachineIdStorage.cs | 107 ++++++++++++++++++ Runtime/Model/MachineIdStorage.cs.meta | 11 ++ .../BacktraceAttributeMachineIdTests.cs | 74 ++++++++++++ .../BacktraceAttributeMachineIdTests.cs.meta | 11 ++ Tests/Runtime/Client/Mocks.meta | 8 ++ .../Client/Mocks/MachineIdStorageMock.cs | 39 +++++++ .../Client/Mocks/MachineIdStorageMock.cs.meta | 11 ++ 10 files changed, 270 insertions(+), 34 deletions(-) rename Runtime/{Extensions/GuidExtensions.cs => Common/GuidHelper.cs} (66%) rename Runtime/{Extensions/GuidExtensions.cs.meta => Common/GuidHelper.cs.meta} (100%) create mode 100644 Runtime/Model/MachineIdStorage.cs create mode 100644 Runtime/Model/MachineIdStorage.cs.meta create mode 100644 Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs create mode 100644 Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs.meta create mode 100644 Tests/Runtime/Client/Mocks.meta create mode 100644 Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs create mode 100644 Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs.meta diff --git a/Runtime/Extensions/GuidExtensions.cs b/Runtime/Common/GuidHelper.cs similarity index 66% rename from Runtime/Extensions/GuidExtensions.cs rename to Runtime/Common/GuidHelper.cs index 2e67b560..8e610b8f 100644 --- a/Runtime/Extensions/GuidExtensions.cs +++ b/Runtime/Common/GuidHelper.cs @@ -5,7 +5,7 @@ namespace Backtrace.Unity.Extensions /// /// Extension for Guid class /// - public static class GuidExtensions + public static class GuidHelper { /// /// Convert long to Guid @@ -17,5 +17,11 @@ public static Guid FromLong(long source) Array.Copy(BitConverter.GetBytes(source), guidData, 8); return new Guid(guidData); } + + public static bool IsEmptyGuid(string guid) + { + const string emptyGuid = "00000000-0000-0000-0000-000000000000"; + return string.IsNullOrEmpty(guid) || guid == emptyGuid; + } } } diff --git a/Runtime/Extensions/GuidExtensions.cs.meta b/Runtime/Common/GuidHelper.cs.meta similarity index 100% rename from Runtime/Extensions/GuidExtensions.cs.meta rename to Runtime/Common/GuidHelper.cs.meta diff --git a/Runtime/Model/Attributes/MachineAttributeProvider.cs b/Runtime/Model/Attributes/MachineAttributeProvider.cs index f15d9313..ab7f2315 100644 --- a/Runtime/Model/Attributes/MachineAttributeProvider.cs +++ b/Runtime/Model/Attributes/MachineAttributeProvider.cs @@ -3,21 +3,20 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Net.NetworkInformation; using UnityEngine; namespace Backtrace.Unity.Model.Attributes { internal sealed class MachineAttributeProvider : IScopeAttributeProvider { + private readonly MachineIdStorage _machineIdStorage = new MachineIdStorage(); public void GetAttributes(IDictionary attributes) { if (attributes == null) { return; } - attributes["guid"] = GenerateMachineId(); + attributes["guid"] = _machineIdStorage.GenerateMachineId(); IncludeGraphicCardInformation(attributes); IncludeOsInformation(attributes); } @@ -83,35 +82,5 @@ private void IncludeGraphicCardInformation(IDictionary attribute attributes["graphic.shader"] = SystemInfo.graphicsShaderLevel.ToString(CultureInfo.InvariantCulture); attributes["graphic.topUv"] = SystemInfo.graphicsUVStartsAtTop.ToString(CultureInfo.InvariantCulture); } - - private string GenerateMachineId() - { -#if !UNITY_WEBGL && !UNITY_SWITCH - // DeviceUniqueIdentifier will return "Switch" on Nintendo Switch - // try to generate random guid instead - if (SystemInfo.deviceUniqueIdentifier != SystemInfo.unsupportedIdentifier) - { - return SystemInfo.deviceUniqueIdentifier; - } - var networkInterface = - NetworkInterface.GetAllNetworkInterfaces() - .FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up); - - PhysicalAddress physicalAddr = null; - string macAddress = null; - if (networkInterface == null - || (physicalAddr = networkInterface.GetPhysicalAddress()) == null - || string.IsNullOrEmpty(macAddress = physicalAddr.ToString())) - { - return Guid.NewGuid().ToString(); - } - - string hex = macAddress.Replace(":", string.Empty); - var value = Convert.ToInt64(hex, 16); - return GuidExtensions.FromLong(value).ToString(); -#else - return Guid.NewGuid().ToString(); -#endif - } } } diff --git a/Runtime/Model/MachineIdStorage.cs b/Runtime/Model/MachineIdStorage.cs new file mode 100644 index 00000000..845c81e9 --- /dev/null +++ b/Runtime/Model/MachineIdStorage.cs @@ -0,0 +1,107 @@ +using Backtrace.Unity.Extensions; +using System; +using System.Linq; +using System.Net.NetworkInformation; +using UnityEngine; + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")] +namespace Backtrace.Unity.Model +{ + /// + /// Backtrace Machine Id storage + /// + internal class MachineIdStorage + { + /// + /// Player prefs machine identifier key + /// + internal const string MachineIdentifierKey = "backtrace-machine-id"; + + /// + /// Generate unique machine id. + /// + /// Unique machine id Guid in a string format + internal string GenerateMachineId() + { + var storageMachineId = FetchMachineIdFromStorage(); + if (!string.IsNullOrEmpty(storageMachineId)) + { + return storageMachineId; + } +#if !UNITY_WEBGL && !UNITY_SWITCH + var unityIdentifier = UseUnityIdentifier(); + if (!GuidHelper.IsEmptyGuid(unityIdentifier)) + { + StoreMachineId(unityIdentifier); + return unityIdentifier; + } + var networkIdentifier = UseNetworkingIdentifier(); + if (!GuidHelper.IsEmptyGuid(networkIdentifier)) + { + StoreMachineId(networkIdentifier); + return networkIdentifier; + } +#endif + var backtraceRandomIdentifier = Guid.NewGuid().ToString(); + StoreMachineId(backtraceRandomIdentifier); + return backtraceRandomIdentifier; + } + + + /// + /// Fetch a machine id in the internal storage + /// + /// machine identifier in the GUID string format + private string FetchMachineIdFromStorage() + { + return PlayerPrefs.GetString(MachineIdentifierKey); + } + + /// + /// Set a machine id in the internal storage + /// + /// machine identifier + private void StoreMachineId(string machineId) + { + PlayerPrefs.SetString(MachineIdentifierKey, machineId); + } + + /// + /// Use Unity device identifier to generate machine identifier + /// + /// Unity machine identifier if the device identifier is supported. Otherwise null + protected virtual string UseUnityIdentifier() + { + // DeviceUniqueIdentifier will return "Switch" on Nintendo Switch + // try to generate random guid instead + if (SystemInfo.deviceUniqueIdentifier == SystemInfo.unsupportedIdentifier) + { + return null; + } + return SystemInfo.deviceUniqueIdentifier; + } + + /// + /// Use Networking interface to generate machine identifier - MAC number from the networking interface. + /// + /// Machine id - MAC in a GUID format. If the networking interface is not available then it returns empty string. + protected virtual string UseNetworkingIdentifier() + { + var networkInterface = NetworkInterface.GetAllNetworkInterfaces() + .FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up); + + PhysicalAddress physicalAddr = null; + string macAddress = null; + if (networkInterface == null + || (physicalAddr = networkInterface.GetPhysicalAddress()) == null + || string.IsNullOrEmpty(macAddress = physicalAddr.ToString())) + { + return null; + } + + string hex = macAddress.Replace(":", string.Empty); + var value = Convert.ToInt64(hex, 16); + return GuidHelper.FromLong(value).ToString(); + } + } +} diff --git a/Runtime/Model/MachineIdStorage.cs.meta b/Runtime/Model/MachineIdStorage.cs.meta new file mode 100644 index 00000000..42a898c5 --- /dev/null +++ b/Runtime/Model/MachineIdStorage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50e9d7c1eceeffe4891e61ccc4eff2e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs new file mode 100644 index 00000000..5ef04b65 --- /dev/null +++ b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs @@ -0,0 +1,74 @@ +using Backtrace.Unity.Extensions; +using Backtrace.Unity.Model; +using Backtrace.Unity.Tests.Runtime.Client.Mocks; +using NUnit.Framework; +using UnityEngine; + +namespace Backtrace.Unity.Tests.Runtime.Client +{ + class BacktraceAttributeMachineIdTests + { + [TearDown] + public void Cleanup() + { + PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); + } + + [Test] + public void TestMachineAttributes_ShouldUseUnityIdentifier_ShouldReturnUnityIdentitfier() + { + var machineIdStorage = new MachineIdStorageMock(); + + var machineId = machineIdStorage.GenerateMachineId(); + + Assert.IsFalse(GuidHelper.IsEmptyGuid(machineId)); + } + + [Test] + public void TestMachineAttributes_ShouldUseMac_ShouldReturnNetowrkingIdentifier() + { + var machineIdStorage = new MachineIdStorageMock(false); + + var machineId = machineIdStorage.GenerateMachineId(); + + Assert.IsFalse(GuidHelper.IsEmptyGuid(machineId)); + } + + [Test] + public void TestMachineAttributes_ShouldUseRandomMachineId_ShouldReturnRandomMachineId() + { + var machineIdStorage = new MachineIdStorageMock(false, false); + + var machineId = machineIdStorage.GenerateMachineId(); + + Assert.IsFalse(GuidHelper.IsEmptyGuid(machineId)); + } + + [Test] + public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueUnityId_IdentifierAreTheSame() + { + var firstMachineIdStorage = new MachineIdStorageMock().GenerateMachineId(); + var secGenerationOfMachineIdStorage = new MachineIdStorageMock().GenerateMachineId(); + + Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); + } + + [Test] + public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueMacId_IdentifierAreTheSame() + { + var firstMachineIdStorage = new MachineIdStorageMock(false).GenerateMachineId(); + var secGenerationOfMachineIdStorage = new MachineIdStorageMock(false).GenerateMachineId(); + + Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); + } + + [Test] + public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueRandomId_IdentifierAreTheSame() + { + var firstMachineIdStorage = new MachineIdStorageMock(false, false).GenerateMachineId(); + var secGenerationOfMachineIdStorage = new MachineIdStorageMock(false, false).GenerateMachineId(); + + Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); + } + } +} diff --git a/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs.meta b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs.meta new file mode 100644 index 00000000..2fd66370 --- /dev/null +++ b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2cec8ad525f2b842b12d11f7bde85d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Client/Mocks.meta b/Tests/Runtime/Client/Mocks.meta new file mode 100644 index 00000000..a4d1551e --- /dev/null +++ b/Tests/Runtime/Client/Mocks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fd956ac43783a6b4bbc9603939fa6a8b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs b/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs new file mode 100644 index 00000000..4a5760a4 --- /dev/null +++ b/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs @@ -0,0 +1,39 @@ +using Backtrace.Unity.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Backtrace.Unity.Tests.Runtime.Client.Mocks +{ + internal class MachineIdStorageMock : MachineIdStorage + { + private readonly bool _allowUnityIdentifier; + private readonly bool _allowNetworking; + public MachineIdStorageMock(bool allowUnityIdentifier = true, bool allowNetworking = true) : base() + { + _allowUnityIdentifier = allowUnityIdentifier; + _allowNetworking = allowNetworking; + } + + + protected override string UseNetworkingIdentifier() + { + if (!_allowNetworking) + { + return null; + } + return base.UseNetworkingIdentifier(); + } + + protected override string UseUnityIdentifier() + { + if (!_allowUnityIdentifier) + { + return null; + } + return base.UseUnityIdentifier(); + } + } +} diff --git a/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs.meta b/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs.meta new file mode 100644 index 00000000..fda396ef --- /dev/null +++ b/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5e8f08a85cb6094ab6e27c6a018641e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 5733dcdbffb44013eaf9152a27c7082425f06286 Mon Sep 17 00:00:00 2001 From: kdysput Date: Mon, 11 Jul 2022 15:14:10 +0200 Subject: [PATCH 2/2] Code review adjustements + more tests + fixed setup --- Runtime/Common/GuidHelper.cs | 2 +- Runtime/Model/MachineIdStorage.cs | 37 +++++++++++-------- .../BacktraceAttributeMachineIdTests.cs | 32 ++++++++++++++-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Runtime/Common/GuidHelper.cs b/Runtime/Common/GuidHelper.cs index 8e610b8f..4338cbb5 100644 --- a/Runtime/Common/GuidHelper.cs +++ b/Runtime/Common/GuidHelper.cs @@ -18,7 +18,7 @@ public static Guid FromLong(long source) return new Guid(guidData); } - public static bool IsEmptyGuid(string guid) + public static bool IsNullOrEmpty(string guid) { const string emptyGuid = "00000000-0000-0000-0000-000000000000"; return string.IsNullOrEmpty(guid) || guid == emptyGuid; diff --git a/Runtime/Model/MachineIdStorage.cs b/Runtime/Model/MachineIdStorage.cs index 845c81e9..07dfc973 100644 --- a/Runtime/Model/MachineIdStorage.cs +++ b/Runtime/Model/MachineIdStorage.cs @@ -28,15 +28,16 @@ internal string GenerateMachineId() { return storageMachineId; } + #if !UNITY_WEBGL && !UNITY_SWITCH var unityIdentifier = UseUnityIdentifier(); - if (!GuidHelper.IsEmptyGuid(unityIdentifier)) + if (!GuidHelper.IsNullOrEmpty(unityIdentifier)) { StoreMachineId(unityIdentifier); return unityIdentifier; } var networkIdentifier = UseNetworkingIdentifier(); - if (!GuidHelper.IsEmptyGuid(networkIdentifier)) + if (!GuidHelper.IsNullOrEmpty(networkIdentifier)) { StoreMachineId(networkIdentifier); return networkIdentifier; @@ -72,8 +73,6 @@ private void StoreMachineId(string machineId) /// Unity machine identifier if the device identifier is supported. Otherwise null protected virtual string UseUnityIdentifier() { - // DeviceUniqueIdentifier will return "Switch" on Nintendo Switch - // try to generate random guid instead if (SystemInfo.deviceUniqueIdentifier == SystemInfo.unsupportedIdentifier) { return null; @@ -84,24 +83,30 @@ protected virtual string UseUnityIdentifier() /// /// Use Networking interface to generate machine identifier - MAC number from the networking interface. /// - /// Machine id - MAC in a GUID format. If the networking interface is not available then it returns empty string. + /// Machine id - MAC in a GUID format. If the networking interface is not available then it returns null. protected virtual string UseNetworkingIdentifier() { - var networkInterface = NetworkInterface.GetAllNetworkInterfaces() - .FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up); + var interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up); - PhysicalAddress physicalAddr = null; - string macAddress = null; - if (networkInterface == null - || (physicalAddr = networkInterface.GetPhysicalAddress()) == null - || string.IsNullOrEmpty(macAddress = physicalAddr.ToString())) + foreach (var @interface in interfaces) { - return null; + var physicalAddress = @interface.GetPhysicalAddress(); + if (physicalAddress == null) + { + continue; + } + var macAddress = physicalAddress.ToString(); + if (string.IsNullOrEmpty(macAddress)) + { + continue; + } + string hex = macAddress.Replace(":", string.Empty); + var value = Convert.ToInt64(hex, 16); + return GuidHelper.FromLong(value).ToString(); } - string hex = macAddress.Replace(":", string.Empty); - var value = Convert.ToInt64(hex, 16); - return GuidHelper.FromLong(value).ToString(); + return null; } } } diff --git a/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs index 5ef04b65..db7aac69 100644 --- a/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs +++ b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs @@ -8,7 +8,7 @@ namespace Backtrace.Unity.Tests.Runtime.Client { class BacktraceAttributeMachineIdTests { - [TearDown] + [SetUp] public void Cleanup() { PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); @@ -21,7 +21,7 @@ public void TestMachineAttributes_ShouldUseUnityIdentifier_ShouldReturnUnityIden var machineId = machineIdStorage.GenerateMachineId(); - Assert.IsFalse(GuidHelper.IsEmptyGuid(machineId)); + Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId)); } [Test] @@ -31,7 +31,7 @@ public void TestMachineAttributes_ShouldUseMac_ShouldReturnNetowrkingIdentifier( var machineId = machineIdStorage.GenerateMachineId(); - Assert.IsFalse(GuidHelper.IsEmptyGuid(machineId)); + Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId)); } [Test] @@ -41,7 +41,7 @@ public void TestMachineAttributes_ShouldUseRandomMachineId_ShouldReturnRandomMac var machineId = machineIdStorage.GenerateMachineId(); - Assert.IsFalse(GuidHelper.IsEmptyGuid(machineId)); + Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId)); } [Test] @@ -70,5 +70,29 @@ public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueRandomId_Identif Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); } + + [Test] + public void TestMachineAttributes_ShouldAlwaysGenerateTheSameUntiyAttribute_ShouldReturnTheSameUnityIdentitfier() + { + var machineIdStorage = new MachineIdStorageMock(); + + var machineId = machineIdStorage.GenerateMachineId(); + PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); + var machineIdAfterCleanup = machineIdStorage.GenerateMachineId(); + + Assert.AreEqual(machineId, machineIdAfterCleanup); + } + + [Test] + public void TestMachineAttributes_ShouldAlwaysGenerateTheSameMacAttribute_ShouldReturnTheSameMacIdentitfier() + { + var machineIdStorage = new MachineIdStorageMock(false); + + var machineId = machineIdStorage.GenerateMachineId(); + PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); + var machineIdAfterCleanup = machineIdStorage.GenerateMachineId(); + + Assert.AreEqual(machineId, machineIdAfterCleanup); + } } }