diff --git a/Runtime/Common/GuidHelper.cs b/Runtime/Common/GuidHelper.cs index 4338cbb5..8472e419 100644 --- a/Runtime/Common/GuidHelper.cs +++ b/Runtime/Common/GuidHelper.cs @@ -1,4 +1,6 @@ using System; +using System.Security.Cryptography; +using System.Text; namespace Backtrace.Unity.Extensions { @@ -23,5 +25,20 @@ public static bool IsNullOrEmpty(string guid) const string emptyGuid = "00000000-0000-0000-0000-000000000000"; return string.IsNullOrEmpty(guid) || guid == emptyGuid; } + + /// + /// Converts a random string into a guid representation. + /// + public static Guid FromString(string value) + { + if (string.IsNullOrEmpty(value)) + { + return Guid.Empty; + } + // to make sure we're supporting old version of Unity that can use .NET 3.5 + // we're using an older API to generate a GUID. + MD5 md5 = new MD5CryptoServiceProvider(); + return new Guid(md5.ComputeHash(Encoding.UTF8.GetBytes(value))); + } } } diff --git a/Runtime/Model/Attributes/MachineAttributeProvider.cs b/Runtime/Model/Attributes/MachineAttributeProvider.cs index 80426746..7c5dd9dd 100644 --- a/Runtime/Model/Attributes/MachineAttributeProvider.cs +++ b/Runtime/Model/Attributes/MachineAttributeProvider.cs @@ -1,5 +1,4 @@ using Backtrace.Unity.Common; -using Backtrace.Unity.Extensions; using System; using System.Collections.Generic; using System.Globalization; diff --git a/Tests/Runtime/Client/Mocks.meta b/Runtime/Model/DataProvider.meta similarity index 77% rename from Tests/Runtime/Client/Mocks.meta rename to Runtime/Model/DataProvider.meta index a4d1551e..29d34515 100644 --- a/Tests/Runtime/Client/Mocks.meta +++ b/Runtime/Model/DataProvider.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: fd956ac43783a6b4bbc9603939fa6a8b +guid: 2925498833be55643891f75e96ad9ded folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/Model/DataProvider/IMachineIdentifierProvider.cs b/Runtime/Model/DataProvider/IMachineIdentifierProvider.cs new file mode 100644 index 00000000..b438c581 --- /dev/null +++ b/Runtime/Model/DataProvider/IMachineIdentifierProvider.cs @@ -0,0 +1,8 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")] +namespace Backtrace.Unity.Model.DataProvider +{ + internal interface IMachineIdentifierProvider + { + string Get(); + } +} diff --git a/Runtime/Model/DataProvider/IMachineIdentifierProvider.cs.meta b/Runtime/Model/DataProvider/IMachineIdentifierProvider.cs.meta new file mode 100644 index 00000000..123ed6b9 --- /dev/null +++ b/Runtime/Model/DataProvider/IMachineIdentifierProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e94db95360eabe94a8f901670f630ab9 \ No newline at end of file diff --git a/Runtime/Model/DataProvider/ISessionStorageDataProvider.cs b/Runtime/Model/DataProvider/ISessionStorageDataProvider.cs new file mode 100644 index 00000000..eeec0974 --- /dev/null +++ b/Runtime/Model/DataProvider/ISessionStorageDataProvider.cs @@ -0,0 +1,9 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")] +namespace Backtrace.Unity.Model.DataProvider +{ + internal interface ISessionStorageDataProvider + { + void SetString(string key, string value); + string GetString(string key); + } +} diff --git a/Runtime/Model/DataProvider/ISessionStorageDataProvider.cs.meta b/Runtime/Model/DataProvider/ISessionStorageDataProvider.cs.meta new file mode 100644 index 00000000..672e4599 --- /dev/null +++ b/Runtime/Model/DataProvider/ISessionStorageDataProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 79226154fe224d84b9f0014d5c8a4206 \ No newline at end of file diff --git a/Runtime/Model/DataProvider/NetworkIdentifierProvider.cs b/Runtime/Model/DataProvider/NetworkIdentifierProvider.cs new file mode 100644 index 00000000..4ac56a69 --- /dev/null +++ b/Runtime/Model/DataProvider/NetworkIdentifierProvider.cs @@ -0,0 +1,35 @@ +using Backtrace.Unity.Extensions; +using System; +using System.Linq; +using System.Net.NetworkInformation; + +namespace Backtrace.Unity.Model.DataProvider +{ + internal class NetworkIdentifierProvider : IMachineIdentifierProvider + { + public string Get() + { + var interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up); + + foreach (var @interface in interfaces) + { + 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(); + } + + return null; + } + } +} diff --git a/Runtime/Model/DataProvider/NetworkIdentifierProvider.cs.meta b/Runtime/Model/DataProvider/NetworkIdentifierProvider.cs.meta new file mode 100644 index 00000000..288aecb1 --- /dev/null +++ b/Runtime/Model/DataProvider/NetworkIdentifierProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 92eddf6f5fcf68d489027eabf6b0d935 \ No newline at end of file diff --git a/Runtime/Model/DataProvider/SessionStorageDataProvider.cs b/Runtime/Model/DataProvider/SessionStorageDataProvider.cs new file mode 100644 index 00000000..6ac95287 --- /dev/null +++ b/Runtime/Model/DataProvider/SessionStorageDataProvider.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Backtrace.Unity.Model.DataProvider +{ + internal class SessionStorageDataProvider : ISessionStorageDataProvider + { + public string GetString(string key) + { + return PlayerPrefs.GetString(key); + } + + public void SetString(string key, string value) + { + PlayerPrefs.SetString(key, value); + } + } +} diff --git a/Runtime/Model/DataProvider/SessionStorageDataProvider.cs.meta b/Runtime/Model/DataProvider/SessionStorageDataProvider.cs.meta new file mode 100644 index 00000000..411c7d00 --- /dev/null +++ b/Runtime/Model/DataProvider/SessionStorageDataProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 61658e4a3b261f940a5497ad0125d81f \ No newline at end of file diff --git a/Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs b/Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs new file mode 100644 index 00000000..df1f70e8 --- /dev/null +++ b/Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs @@ -0,0 +1,35 @@ +using Backtrace.Unity.Extensions; +using System; +using UnityEngine; + +namespace Backtrace.Unity.Model.DataProvider +{ + internal class UnityMachineIdentifierProvider : IMachineIdentifierProvider + { + private readonly string _deviceUniqueIdentifier; + internal UnityMachineIdentifierProvider() : this(SystemInfo.deviceUniqueIdentifier) { } + + internal UnityMachineIdentifierProvider(string machineIdentifier) + { + _deviceUniqueIdentifier = machineIdentifier; + } + public string Get() + { + if (!IsValidIdentifier()) + { + return null; + } + + if (Guid.TryParse(_deviceUniqueIdentifier, out Guid unityUuidGuid)) + { + return unityUuidGuid.ToString(); + } + return GuidHelper.FromString(_deviceUniqueIdentifier).ToString(); + } + + private bool IsValidIdentifier() + { + return _deviceUniqueIdentifier != SystemInfo.unsupportedIdentifier && !string.IsNullOrEmpty(_deviceUniqueIdentifier); + } + } +} diff --git a/Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs.meta b/Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs.meta new file mode 100644 index 00000000..2e862da4 --- /dev/null +++ b/Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ed39481d15e8b2249875498830f31c6f \ No newline at end of file diff --git a/Runtime/Model/MachineIdStorage.cs b/Runtime/Model/MachineIdStorage.cs index 07dfc973..2a199314 100644 --- a/Runtime/Model/MachineIdStorage.cs +++ b/Runtime/Model/MachineIdStorage.cs @@ -1,8 +1,6 @@ using Backtrace.Unity.Extensions; +using Backtrace.Unity.Model.DataProvider; using System; -using System.Linq; -using System.Net.NetworkInformation; -using UnityEngine; [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")] namespace Backtrace.Unity.Model @@ -17,6 +15,19 @@ internal class MachineIdStorage /// internal const string MachineIdentifierKey = "backtrace-machine-id"; + private readonly ISessionStorageDataProvider _sessionStorageDataProvider; + private readonly IMachineIdentifierProvider[] _machineIdentifierDataProviders; + + internal MachineIdStorage() : this( + new IMachineIdentifierProvider[] { new UnityMachineIdentifierProvider(), new NetworkIdentifierProvider() }, + new SessionStorageDataProvider()) + { } + internal MachineIdStorage(IMachineIdentifierProvider[] machineIdentifierDataProviders, ISessionStorageDataProvider sessionStorageDataProvider) + { + _machineIdentifierDataProviders = machineIdentifierDataProviders; + _sessionStorageDataProvider = sessionStorageDataProvider; + } + /// /// Generate unique machine id. /// @@ -30,17 +41,14 @@ internal string GenerateMachineId() } #if !UNITY_WEBGL && !UNITY_SWITCH - var unityIdentifier = UseUnityIdentifier(); - if (!GuidHelper.IsNullOrEmpty(unityIdentifier)) + foreach (var machineIdentifierProvider in _machineIdentifierDataProviders) { - StoreMachineId(unityIdentifier); - return unityIdentifier; - } - var networkIdentifier = UseNetworkingIdentifier(); - if (!GuidHelper.IsNullOrEmpty(networkIdentifier)) - { - StoreMachineId(networkIdentifier); - return networkIdentifier; + var identifier = machineIdentifierProvider.Get(); + if (!GuidHelper.IsNullOrEmpty(identifier)) + { + StoreMachineId(identifier); + return identifier; + } } #endif var backtraceRandomIdentifier = Guid.NewGuid().ToString(); @@ -55,7 +63,17 @@ internal string GenerateMachineId() /// machine identifier in the GUID string format private string FetchMachineIdFromStorage() { - return PlayerPrefs.GetString(MachineIdentifierKey); + var storedMachineId = _sessionStorageDataProvider.GetString(MachineIdentifierKey); + // in the previous version of the SDK, the stored machine id could be invalid + // to fix the problem, we want to verify if the id is valid and if isn't, fix it. + if (string.IsNullOrEmpty(storedMachineId) || Guid.TryParse(storedMachineId, out Guid _)) + { + return storedMachineId; + } + + var machineId = GuidHelper.FromString(storedMachineId).ToString(); + StoreMachineId(machineId); + return machineId; } /// @@ -64,49 +82,7 @@ private string FetchMachineIdFromStorage() /// 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() - { - 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 null. - protected virtual string UseNetworkingIdentifier() - { - var interfaces = NetworkInterface.GetAllNetworkInterfaces() - .Where(n => n.OperationalStatus == OperationalStatus.Up); - - foreach (var @interface in interfaces) - { - 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(); - } - - return null; + _sessionStorageDataProvider.SetString(MachineIdentifierKey, machineId); } } } diff --git a/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs index db7aac69..e0ffa2fa 100644 --- a/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs +++ b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs @@ -1,6 +1,6 @@ using Backtrace.Unity.Extensions; using Backtrace.Unity.Model; -using Backtrace.Unity.Tests.Runtime.Client.Mocks; +using Backtrace.Unity.Model.DataProvider; using NUnit.Framework; using UnityEngine; @@ -17,7 +17,7 @@ public void Cleanup() [Test] public void TestMachineAttributes_ShouldUseUnityIdentifier_ShouldReturnUnityIdentitfier() { - var machineIdStorage = new MachineIdStorageMock(); + var machineIdStorage = new MachineIdStorage(); var machineId = machineIdStorage.GenerateMachineId(); @@ -27,17 +27,19 @@ public void TestMachineAttributes_ShouldUseUnityIdentifier_ShouldReturnUnityIden [Test] public void TestMachineAttributes_ShouldUseMac_ShouldReturnNetowrkingIdentifier() { - var machineIdStorage = new MachineIdStorageMock(false); + var networkIdentifierDataProvider = new NetworkIdentifierProvider(); + var expectedMachineId = networkIdentifierDataProvider.Get(); + var machineIdStorage = new MachineIdStorage(new IMachineIdentifierProvider[] { networkIdentifierDataProvider }, new SessionStorageDataProvider()); var machineId = machineIdStorage.GenerateMachineId(); - Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId)); + Assert.IsFalse(GuidHelper.IsNullOrEmpty(expectedMachineId)); } [Test] public void TestMachineAttributes_ShouldUseRandomMachineId_ShouldReturnRandomMachineId() { - var machineIdStorage = new MachineIdStorageMock(false, false); + var machineIdStorage = new MachineIdStorage(new IMachineIdentifierProvider[0], new SessionStorageDataProvider()); var machineId = machineIdStorage.GenerateMachineId(); @@ -45,28 +47,36 @@ public void TestMachineAttributes_ShouldUseRandomMachineId_ShouldReturnRandomMac } [Test] - public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueUnityId_IdentifierAreTheSame() + public void TestMachineAttributes_ShouldConvertInvalidIdIntoGuid_ValidIdIsAlwaysUsed() { - var firstMachineIdStorage = new MachineIdStorageMock().GenerateMachineId(); - var secGenerationOfMachineIdStorage = new MachineIdStorageMock().GenerateMachineId(); + var invalidValue = "randomValue"; + PlayerPrefs.SetString(MachineIdStorage.MachineIdentifierKey, invalidValue); + var expectedGuid = GuidHelper.FromString(invalidValue).ToString(); - Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); + var machineId = new MachineIdStorage().GenerateMachineId(); + + Assert.IsTrue(expectedGuid == machineId); } [Test] - public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueMacId_IdentifierAreTheSame() + public void TestMachineAttributes_ShouldRetrieveValueFromStorage_IdentifierIsStored() { - var firstMachineIdStorage = new MachineIdStorageMock(false).GenerateMachineId(); - var secGenerationOfMachineIdStorage = new MachineIdStorageMock(false).GenerateMachineId(); + // make sure it's always empty + PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); - Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); + var machineId = new MachineIdStorage().GenerateMachineId(); + + var storage = new SessionStorageDataProvider(); + var storedMachineId = storage.GetString(MachineIdStorage.MachineIdentifierKey); + + Assert.IsTrue(machineId == storedMachineId); } [Test] - public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueRandomId_IdentifierAreTheSame() + public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueUnityId_IdentifierAreTheSame() { - var firstMachineIdStorage = new MachineIdStorageMock(false, false).GenerateMachineId(); - var secGenerationOfMachineIdStorage = new MachineIdStorageMock(false, false).GenerateMachineId(); + var firstMachineIdStorage = new MachineIdStorage().GenerateMachineId(); + var secGenerationOfMachineIdStorage = new MachineIdStorage().GenerateMachineId(); Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage); } @@ -74,7 +84,7 @@ public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueRandomId_Identif [Test] public void TestMachineAttributes_ShouldAlwaysGenerateTheSameUntiyAttribute_ShouldReturnTheSameUnityIdentitfier() { - var machineIdStorage = new MachineIdStorageMock(); + var machineIdStorage = new MachineIdStorage(); var machineId = machineIdStorage.GenerateMachineId(); PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); @@ -86,7 +96,7 @@ public void TestMachineAttributes_ShouldAlwaysGenerateTheSameUntiyAttribute_Shou [Test] public void TestMachineAttributes_ShouldAlwaysGenerateTheSameMacAttribute_ShouldReturnTheSameMacIdentitfier() { - var machineIdStorage = new MachineIdStorageMock(false); + var machineIdStorage = new MachineIdStorage(new IMachineIdentifierProvider[] { new NetworkIdentifierProvider() }, new SessionStorageDataProvider()); var machineId = machineIdStorage.GenerateMachineId(); PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey); diff --git a/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs b/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs deleted file mode 100644 index 4a5760a4..00000000 --- a/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index fda396ef..00000000 --- a/Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d5e8f08a85cb6094ab6e27c6a018641e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: