File: Telemetry\MachineInformationProviderBase.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Cli.Telemetry;
 
// This is copied from https://github.com/microsoft/mcp/tree/6bb4d76a63d24854efe0fa0bd96f5ab6f699ed3a/core/Azure.Mcp.Core/src/Services/Telemetry
// Keep in sync with updates there.
 
internal abstract class MachineInformationProviderBase(ILogger<MachineInformationProviderBase> logger)
    : IMachineInformationProvider
{
    protected const string MicrosoftDirectory = "Microsoft";
    protected const string DeveloperToolsDirectory = "DeveloperTools";
    protected const string NotAvailable = "N/A";
 
    /// <summary>
    /// The name of the registry key or file containing device id.
    /// </summary>
    protected const string DeviceId = "deviceid";
 
    private static readonly SHA256 s_sHA256 = SHA256.Create();
 
    private readonly ILogger<MachineInformationProviderBase> _logger = logger;
 
    /// <inheritdoc/>
    public abstract Task<string?> GetOrCreateDeviceId();
 
    /// <inheritdoc/>
    public virtual Task<string> GetMacAddressHash()
    {
        return Task.Run(() =>
        {
            try
            {
                var address = GetMacAddress();
 
                return address != null
                    ? HashValue(address)
                    : NotAvailable;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unable to calculate MAC address hash.");
                return NotAvailable;
            }
        });
    }
 
    /// <summary>
    /// Searches for first network interface card that is up and has a physical address.
    /// </summary>
    /// <returns>Hash of the MAC address or <see cref="NotAvailable"/> if none can be found.</returns>
    protected virtual string? GetMacAddress()
    {
        return NetworkInterface.GetAllNetworkInterfaces()
            .Where(x => x.OperationalStatus == OperationalStatus.Up && x.NetworkInterfaceType != NetworkInterfaceType.Loopback)
            .Select(x => x.GetPhysicalAddress().ToString())
            .FirstOrDefault(x => !string.IsNullOrEmpty(x));
    }
 
    /// <summary>
    /// Generates a new device identifier.  Conditions that have to be met are:
    /// <list type="bullet">
    /// <item>Randomly generated UUID/GUID</item>
    /// <item>Follows the 8-4-4-4-12 format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)</item>
    /// <item>Contains only lowercase characters and hyphens</item>
    /// </list>
    /// </summary>
    /// <returns>The generated device identifier.</returns>
    protected static string GenerateDeviceId() => Guid.NewGuid().ToString("D").ToLowerInvariant();
 
    /// <summary>
    /// Generates a SHA-256 of the given value.
    /// </summary>
    protected static string HashValue(string value)
    {
        var hashInput = s_sHA256.ComputeHash(Encoding.UTF8.GetBytes(value));
        return BitConverter.ToString(hashInput).Replace("-", string.Empty).ToLowerInvariant();
    }
}