Skip to content

Generate valid machine id #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Runtime/Common/GuidHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Security.Cryptography;
using System.Text;

namespace Backtrace.Unity.Extensions
{
Expand All @@ -23,5 +25,20 @@ public static bool IsNullOrEmpty(string guid)
const string emptyGuid = "00000000-0000-0000-0000-000000000000";
return string.IsNullOrEmpty(guid) || guid == emptyGuid;
}

/// <summary>
/// Converts a random string into a guid representation.
/// </summary>
public static Guid FromString(string value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to FromMD5String? FromString suggests that a GUID string can be provided here.

Copy link
Collaborator Author

@konraddysput konraddysput Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MD5 is an algorithm behind the scene that we're using to convert string into bytes that later we can use to generate GUID. It doesn't make sense to include MD5 in the name in my opinion. It's string and md5 is an implementation detail.

{
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)));
}
}
}
1 change: 0 additions & 1 deletion Runtime/Model/Attributes/MachineAttributeProvider.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Backtrace.Unity.Common;
using Backtrace.Unity.Extensions;
using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Runtime/Model/DataProvider/IMachineIdentifierDataProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")]
namespace Backtrace.Unity.Model.DataProvider
{
internal interface IMachineIdentifierDataProvider
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only provides the machine identifier. Why not name this IMachineIdentifierProvider?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

{
string Get();
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Runtime/Model/DataProvider/ISessionStorageDataProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions Runtime/Model/DataProvider/NetworkIdentifierDataProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Backtrace.Unity.Extensions;
using System;
using System.Linq;
using System.Net.NetworkInformation;

namespace Backtrace.Unity.Model.DataProvider
{
internal class NetworkIdentifierDataProvider : IMachineIdentifierDataProvider
{
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;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions Runtime/Model/DataProvider/SessionStorageDataProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
2 changes: 2 additions & 0 deletions Runtime/Model/DataProvider/SessionStorageDataProvider.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions Runtime/Model/DataProvider/UnityMachineIdentifierProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Backtrace.Unity.Extensions;
using System;
using System.Linq;
using System.Net.NetworkInformation;
using UnityEngine;

namespace Backtrace.Unity.Model.DataProvider
{
internal class UnityMachineIdentifierProvider : IMachineIdentifierDataProvider
{
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();
Comment on lines +23 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often the ID will be generated? Maybe it's a good idea to generate this in the constructor, or cache the value somewhere?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see that in MachineIdStorage it is retrieved only once? If so, feel free to discard this comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to confirm - yes. we're doing it only once.

}

private bool IsValidIdentifier()
{
return _deviceUniqueIdentifier != SystemInfo.unsupportedIdentifier && !string.IsNullOrEmpty(_deviceUniqueIdentifier);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 33 additions & 57 deletions Runtime/Model/MachineIdStorage.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,6 +15,19 @@ internal class MachineIdStorage
/// </summary>
internal const string MachineIdentifierKey = "backtrace-machine-id";

private readonly ISessionStorageDataProvider _sessionStorageDataProvider;
private readonly IMachineIdentifierDataProvider[] _machineIdentifierDataProviders;

internal MachineIdStorage() : this(
new IMachineIdentifierDataProvider[] { new UnityMachineIdentifierProvider(), new NetworkIdentifierDataProvider() },
new SessionStorageDataProvider())
{ }
internal MachineIdStorage(IMachineIdentifierDataProvider[] machineIdentifierDataProviders, ISessionStorageDataProvider sessionStorageDataProvider)
{
_machineIdentifierDataProviders = machineIdentifierDataProviders;
_sessionStorageDataProvider = sessionStorageDataProvider;
}

/// <summary>
/// Generate unique machine id.
/// </summary>
Expand All @@ -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();
Expand All @@ -55,7 +63,17 @@ internal string GenerateMachineId()
/// <returns>machine identifier in the GUID string format</returns>
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;
}

/// <summary>
Expand All @@ -64,49 +82,7 @@ private string FetchMachineIdFromStorage()
/// <param name="machineId">machine identifier</param>
private void StoreMachineId(string machineId)
{
PlayerPrefs.SetString(MachineIdentifierKey, machineId);
}

/// <summary>
/// Use Unity device identifier to generate machine identifier
/// </summary>
/// <returns>Unity machine identifier if the device identifier is supported. Otherwise null</returns>
protected virtual string UseUnityIdentifier()
{
if (SystemInfo.deviceUniqueIdentifier == SystemInfo.unsupportedIdentifier)
{
return null;
}
return SystemInfo.deviceUniqueIdentifier;
}

/// <summary>
/// Use Networking interface to generate machine identifier - MAC number from the networking interface.
/// </summary>
/// <returns>Machine id - MAC in a GUID format. If the networking interface is not available then it returns null.</returns>
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);
}
}
}
Loading
Loading