diff --git a/Runtime/Extensions/GuidExtensions.cs b/Runtime/Common/GuidHelper.cs
similarity index 65%
rename from Runtime/Extensions/GuidExtensions.cs
rename to Runtime/Common/GuidHelper.cs
index 2e67b560..4338cbb5 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 IsNullOrEmpty(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..07dfc973
--- /dev/null
+++ b/Runtime/Model/MachineIdStorage.cs
@@ -0,0 +1,112 @@
+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.IsNullOrEmpty(unityIdentifier))
+ {
+ StoreMachineId(unityIdentifier);
+ return unityIdentifier;
+ }
+ var networkIdentifier = UseNetworkingIdentifier();
+ if (!GuidHelper.IsNullOrEmpty(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()
+ {
+ 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;
+ }
+ }
+}
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..db7aac69
--- /dev/null
+++ b/Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs
@@ -0,0 +1,98 @@
+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
+ {
+ [SetUp]
+ public void Cleanup()
+ {
+ PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey);
+ }
+
+ [Test]
+ public void TestMachineAttributes_ShouldUseUnityIdentifier_ShouldReturnUnityIdentitfier()
+ {
+ var machineIdStorage = new MachineIdStorageMock();
+
+ var machineId = machineIdStorage.GenerateMachineId();
+
+ Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId));
+ }
+
+ [Test]
+ public void TestMachineAttributes_ShouldUseMac_ShouldReturnNetowrkingIdentifier()
+ {
+ var machineIdStorage = new MachineIdStorageMock(false);
+
+ var machineId = machineIdStorage.GenerateMachineId();
+
+ Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId));
+ }
+
+ [Test]
+ public void TestMachineAttributes_ShouldUseRandomMachineId_ShouldReturnRandomMachineId()
+ {
+ var machineIdStorage = new MachineIdStorageMock(false, false);
+
+ var machineId = machineIdStorage.GenerateMachineId();
+
+ Assert.IsFalse(GuidHelper.IsNullOrEmpty(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);
+ }
+
+ [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);
+ }
+ }
+}
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: