Skip to content

Commit 7545596

Browse files
authored
Generate not-empty machine identifier (#162)
* Empty machine ID * Code review adjustements + more tests + fixed setup
1 parent fc076b0 commit 7545596

10 files changed

+299
-34
lines changed

Runtime/Extensions/GuidExtensions.cs Runtime/Common/GuidHelper.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Backtrace.Unity.Extensions
55
/// <summary>
66
/// Extension for Guid class
77
/// </summary>
8-
public static class GuidExtensions
8+
public static class GuidHelper
99
{
1010
/// <summary>
1111
/// Convert long to Guid
@@ -17,5 +17,11 @@ public static Guid FromLong(long source)
1717
Array.Copy(BitConverter.GetBytes(source), guidData, 8);
1818
return new Guid(guidData);
1919
}
20+
21+
public static bool IsNullOrEmpty(string guid)
22+
{
23+
const string emptyGuid = "00000000-0000-0000-0000-000000000000";
24+
return string.IsNullOrEmpty(guid) || guid == emptyGuid;
25+
}
2026
}
2127
}
File renamed without changes.

Runtime/Model/Attributes/MachineAttributeProvider.cs

+2-33
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,20 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Globalization;
6-
using System.Linq;
7-
using System.Net.NetworkInformation;
86
using UnityEngine;
97

108
namespace Backtrace.Unity.Model.Attributes
119
{
1210
internal sealed class MachineAttributeProvider : IScopeAttributeProvider
1311
{
12+
private readonly MachineIdStorage _machineIdStorage = new MachineIdStorage();
1413
public void GetAttributes(IDictionary<string, string> attributes)
1514
{
1615
if (attributes == null)
1716
{
1817
return;
1918
}
20-
attributes["guid"] = GenerateMachineId();
19+
attributes["guid"] = _machineIdStorage.GenerateMachineId();
2120
IncludeGraphicCardInformation(attributes);
2221
IncludeOsInformation(attributes);
2322
}
@@ -83,35 +82,5 @@ private void IncludeGraphicCardInformation(IDictionary<string, string> attribute
8382
attributes["graphic.shader"] = SystemInfo.graphicsShaderLevel.ToString(CultureInfo.InvariantCulture);
8483
attributes["graphic.topUv"] = SystemInfo.graphicsUVStartsAtTop.ToString(CultureInfo.InvariantCulture);
8584
}
86-
87-
private string GenerateMachineId()
88-
{
89-
#if !UNITY_WEBGL && !UNITY_SWITCH
90-
// DeviceUniqueIdentifier will return "Switch" on Nintendo Switch
91-
// try to generate random guid instead
92-
if (SystemInfo.deviceUniqueIdentifier != SystemInfo.unsupportedIdentifier)
93-
{
94-
return SystemInfo.deviceUniqueIdentifier;
95-
}
96-
var networkInterface =
97-
NetworkInterface.GetAllNetworkInterfaces()
98-
.FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up);
99-
100-
PhysicalAddress physicalAddr = null;
101-
string macAddress = null;
102-
if (networkInterface == null
103-
|| (physicalAddr = networkInterface.GetPhysicalAddress()) == null
104-
|| string.IsNullOrEmpty(macAddress = physicalAddr.ToString()))
105-
{
106-
return Guid.NewGuid().ToString();
107-
}
108-
109-
string hex = macAddress.Replace(":", string.Empty);
110-
var value = Convert.ToInt64(hex, 16);
111-
return GuidExtensions.FromLong(value).ToString();
112-
#else
113-
return Guid.NewGuid().ToString();
114-
#endif
115-
}
11685
}
11786
}

Runtime/Model/MachineIdStorage.cs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using Backtrace.Unity.Extensions;
2+
using System;
3+
using System.Linq;
4+
using System.Net.NetworkInformation;
5+
using UnityEngine;
6+
7+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")]
8+
namespace Backtrace.Unity.Model
9+
{
10+
/// <summary>
11+
/// Backtrace Machine Id storage
12+
/// </summary>
13+
internal class MachineIdStorage
14+
{
15+
/// <summary>
16+
/// Player prefs machine identifier key
17+
/// </summary>
18+
internal const string MachineIdentifierKey = "backtrace-machine-id";
19+
20+
/// <summary>
21+
/// Generate unique machine id.
22+
/// </summary>
23+
/// <returns>Unique machine id Guid in a string format</returns>
24+
internal string GenerateMachineId()
25+
{
26+
var storageMachineId = FetchMachineIdFromStorage();
27+
if (!string.IsNullOrEmpty(storageMachineId))
28+
{
29+
return storageMachineId;
30+
}
31+
32+
#if !UNITY_WEBGL && !UNITY_SWITCH
33+
var unityIdentifier = UseUnityIdentifier();
34+
if (!GuidHelper.IsNullOrEmpty(unityIdentifier))
35+
{
36+
StoreMachineId(unityIdentifier);
37+
return unityIdentifier;
38+
}
39+
var networkIdentifier = UseNetworkingIdentifier();
40+
if (!GuidHelper.IsNullOrEmpty(networkIdentifier))
41+
{
42+
StoreMachineId(networkIdentifier);
43+
return networkIdentifier;
44+
}
45+
#endif
46+
var backtraceRandomIdentifier = Guid.NewGuid().ToString();
47+
StoreMachineId(backtraceRandomIdentifier);
48+
return backtraceRandomIdentifier;
49+
}
50+
51+
52+
/// <summary>
53+
/// Fetch a machine id in the internal storage
54+
/// </summary>
55+
/// <returns>machine identifier in the GUID string format</returns>
56+
private string FetchMachineIdFromStorage()
57+
{
58+
return PlayerPrefs.GetString(MachineIdentifierKey);
59+
}
60+
61+
/// <summary>
62+
/// Set a machine id in the internal storage
63+
/// </summary>
64+
/// <param name="machineId">machine identifier</param>
65+
private void StoreMachineId(string machineId)
66+
{
67+
PlayerPrefs.SetString(MachineIdentifierKey, machineId);
68+
}
69+
70+
/// <summary>
71+
/// Use Unity device identifier to generate machine identifier
72+
/// </summary>
73+
/// <returns>Unity machine identifier if the device identifier is supported. Otherwise null</returns>
74+
protected virtual string UseUnityIdentifier()
75+
{
76+
if (SystemInfo.deviceUniqueIdentifier == SystemInfo.unsupportedIdentifier)
77+
{
78+
return null;
79+
}
80+
return SystemInfo.deviceUniqueIdentifier;
81+
}
82+
83+
/// <summary>
84+
/// Use Networking interface to generate machine identifier - MAC number from the networking interface.
85+
/// </summary>
86+
/// <returns>Machine id - MAC in a GUID format. If the networking interface is not available then it returns null.</returns>
87+
protected virtual string UseNetworkingIdentifier()
88+
{
89+
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
90+
.Where(n => n.OperationalStatus == OperationalStatus.Up);
91+
92+
foreach (var @interface in interfaces)
93+
{
94+
var physicalAddress = @interface.GetPhysicalAddress();
95+
if (physicalAddress == null)
96+
{
97+
continue;
98+
}
99+
var macAddress = physicalAddress.ToString();
100+
if (string.IsNullOrEmpty(macAddress))
101+
{
102+
continue;
103+
}
104+
string hex = macAddress.Replace(":", string.Empty);
105+
var value = Convert.ToInt64(hex, 16);
106+
return GuidHelper.FromLong(value).ToString();
107+
}
108+
109+
return null;
110+
}
111+
}
112+
}

Runtime/Model/MachineIdStorage.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Backtrace.Unity.Extensions;
2+
using Backtrace.Unity.Model;
3+
using Backtrace.Unity.Tests.Runtime.Client.Mocks;
4+
using NUnit.Framework;
5+
using UnityEngine;
6+
7+
namespace Backtrace.Unity.Tests.Runtime.Client
8+
{
9+
class BacktraceAttributeMachineIdTests
10+
{
11+
[SetUp]
12+
public void Cleanup()
13+
{
14+
PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey);
15+
}
16+
17+
[Test]
18+
public void TestMachineAttributes_ShouldUseUnityIdentifier_ShouldReturnUnityIdentitfier()
19+
{
20+
var machineIdStorage = new MachineIdStorageMock();
21+
22+
var machineId = machineIdStorage.GenerateMachineId();
23+
24+
Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId));
25+
}
26+
27+
[Test]
28+
public void TestMachineAttributes_ShouldUseMac_ShouldReturnNetowrkingIdentifier()
29+
{
30+
var machineIdStorage = new MachineIdStorageMock(false);
31+
32+
var machineId = machineIdStorage.GenerateMachineId();
33+
34+
Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId));
35+
}
36+
37+
[Test]
38+
public void TestMachineAttributes_ShouldUseRandomMachineId_ShouldReturnRandomMachineId()
39+
{
40+
var machineIdStorage = new MachineIdStorageMock(false, false);
41+
42+
var machineId = machineIdStorage.GenerateMachineId();
43+
44+
Assert.IsFalse(GuidHelper.IsNullOrEmpty(machineId));
45+
}
46+
47+
[Test]
48+
public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueUnityId_IdentifierAreTheSame()
49+
{
50+
var firstMachineIdStorage = new MachineIdStorageMock().GenerateMachineId();
51+
var secGenerationOfMachineIdStorage = new MachineIdStorageMock().GenerateMachineId();
52+
53+
Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage);
54+
}
55+
56+
[Test]
57+
public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueMacId_IdentifierAreTheSame()
58+
{
59+
var firstMachineIdStorage = new MachineIdStorageMock(false).GenerateMachineId();
60+
var secGenerationOfMachineIdStorage = new MachineIdStorageMock(false).GenerateMachineId();
61+
62+
Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage);
63+
}
64+
65+
[Test]
66+
public void TestMachineAttributes_ShouldAlwaysReturnTheSameValueRandomId_IdentifierAreTheSame()
67+
{
68+
var firstMachineIdStorage = new MachineIdStorageMock(false, false).GenerateMachineId();
69+
var secGenerationOfMachineIdStorage = new MachineIdStorageMock(false, false).GenerateMachineId();
70+
71+
Assert.IsTrue(firstMachineIdStorage == secGenerationOfMachineIdStorage);
72+
}
73+
74+
[Test]
75+
public void TestMachineAttributes_ShouldAlwaysGenerateTheSameUntiyAttribute_ShouldReturnTheSameUnityIdentitfier()
76+
{
77+
var machineIdStorage = new MachineIdStorageMock();
78+
79+
var machineId = machineIdStorage.GenerateMachineId();
80+
PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey);
81+
var machineIdAfterCleanup = machineIdStorage.GenerateMachineId();
82+
83+
Assert.AreEqual(machineId, machineIdAfterCleanup);
84+
}
85+
86+
[Test]
87+
public void TestMachineAttributes_ShouldAlwaysGenerateTheSameMacAttribute_ShouldReturnTheSameMacIdentitfier()
88+
{
89+
var machineIdStorage = new MachineIdStorageMock(false);
90+
91+
var machineId = machineIdStorage.GenerateMachineId();
92+
PlayerPrefs.DeleteKey(MachineIdStorage.MachineIdentifierKey);
93+
var machineIdAfterCleanup = machineIdStorage.GenerateMachineId();
94+
95+
Assert.AreEqual(machineId, machineIdAfterCleanup);
96+
}
97+
}
98+
}

Tests/Runtime/Client/BacktraceAttributeMachineIdTests.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Runtime/Client/Mocks.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Backtrace.Unity.Model;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace Backtrace.Unity.Tests.Runtime.Client.Mocks
9+
{
10+
internal class MachineIdStorageMock : MachineIdStorage
11+
{
12+
private readonly bool _allowUnityIdentifier;
13+
private readonly bool _allowNetworking;
14+
public MachineIdStorageMock(bool allowUnityIdentifier = true, bool allowNetworking = true) : base()
15+
{
16+
_allowUnityIdentifier = allowUnityIdentifier;
17+
_allowNetworking = allowNetworking;
18+
}
19+
20+
21+
protected override string UseNetworkingIdentifier()
22+
{
23+
if (!_allowNetworking)
24+
{
25+
return null;
26+
}
27+
return base.UseNetworkingIdentifier();
28+
}
29+
30+
protected override string UseUnityIdentifier()
31+
{
32+
if (!_allowUnityIdentifier)
33+
{
34+
return null;
35+
}
36+
return base.UseUnityIdentifier();
37+
}
38+
}
39+
}

Tests/Runtime/Client/Mocks/MachineIdStorageMock.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)