diff --git a/Duplicati/Library/Utility/DeviceIDHelper.cs b/Duplicati/Library/Utility/DeviceIDHelper.cs index 80fc3c70f..146abc09c 100644 --- a/Duplicati/Library/Utility/DeviceIDHelper.cs +++ b/Duplicati/Library/Utility/DeviceIDHelper.cs @@ -18,30 +18,95 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable using Duplicati.Library.Utility; using DeviceId; +using System.Collections.Generic; +using System.Linq; /// /// Helper class to get the device ID string and computed hash /// public static class DeviceIDHelper { + /// + /// Generates an empty ID + /// + /// An empty ID + private static string? GenerateEmptyId() + { + var builder = new DeviceIdBuilder(); + builder.Components.Clear(); + return builder.ToString(); + } + + /// + /// The empty ID produced by DeviceIdBuilder if no information is available + /// + private static readonly HashSet EMPTY_DEVICE_IDS = new[] { + // Known empty Id + "WERC8GMRZGE196QVYK49JVXS4GKTWGF4CJDS6K54JPCHPY2JQ1AG", + // In case the library changes + GenerateEmptyId() + } + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x!) + // Don't allow empty strings either + .Append(string.Empty) + .ToHashSet(); + + /// + /// Hashes a set of device IDs + /// + /// The deviceIds to hash + /// The list of hashed ids + private static HashSet HashDeviceIds(IEnumerable deviceIds) + { + var hasher = HashFactory.CreateHasher("SHA256"); + return deviceIds.Select(x => x.ComputeHashToHex(hasher)).ToHashSet(); + } + + /// + /// The empty deviceId hashes + /// + public static readonly HashSet EMPTY_DEVICE_ID_HASHES = HashDeviceIds(EMPTY_DEVICE_IDS); + + /// + /// Returns a configured builder + /// + /// The builder + private static DeviceIdBuilder GetBuilder() + => new DeviceIdBuilder() + .OnWindows(deviceid => deviceid + .AddMotherboardSerialNumber()) + .OnLinux(deviceid => deviceid + .AddMotherboardSerialNumber()) + .OnMac(deviceid => deviceid + .AddPlatformSerialNumber()); /// /// Get the device ID string (motherboard serial number on Windows and Linux, platform serial number on Mac) /// - /// String - public static string GetDeviceID() + /// The device Id + public static string? GetDeviceID() { - return new DeviceIdBuilder() - .OnWindows(deviceid => deviceid - .AddMotherboardSerialNumber()) - .OnLinux(deviceid => deviceid - .AddMotherboardSerialNumber()) - .OnMac(deviceid => deviceid - .AddPlatformSerialNumber()) - .ToString(); + var id = GetBuilder().ToString(); + if (string.IsNullOrWhiteSpace(id)) + return EMPTY_DEVICE_IDS.First(); + + return id; + } + + /// + /// Checks if the deviceId is a meaningful value + /// + /// true if a deviceId can be obtained, false otherwise + public static bool CanGetDeviceId() + { + var builder = GetBuilder(); + builder.UseFormatter(new DeviceId.Formatters.StringDeviceIdFormatter(new DeviceId.Encoders.PlainTextDeviceIdComponentEncoder())); + return !string.IsNullOrWhiteSpace(builder.ToString()); } /// @@ -50,6 +115,7 @@ public static class DeviceIDHelper /// public static string GetDeviceIDHash() { - return GetDeviceID().ComputeHashToHex(HashFactory.CreateHasher("SHA256")); + using var hasher = HashFactory.CreateHasher("SHA256"); + return (GetDeviceID() ?? "").ComputeHashToHex(hasher); } } \ No newline at end of file