├── .editorconfig ├── .gitattributes ├── .gitignore ├── DeviceId.sln ├── Directory.Build.props ├── build ├── build-alpha.cmd ├── build-alpha.ps1 ├── build-release.cmd ├── build-release.ps1 ├── push-to-nuget.cmd └── push-to-nuget.ps1 ├── license.txt ├── readme.md ├── src ├── DeviceId.Linux │ ├── Components │ │ ├── DockerContainerIdComponent.cs │ │ └── LinuxRootDriveSerialNumberDeviceIdComponent.cs │ ├── DeviceId.Linux.csproj │ ├── DeviceIdBuilderExtensions.cs │ ├── LinuxDeviceIdBuilder.cs │ ├── LinuxDeviceIdBuilderExtensions.cs │ ├── Serialization │ │ ├── LsblkDevice.cs │ │ ├── LsblkOutput.cs │ │ └── SourceGenerationContext.cs │ └── _InternalsVisibleTo.cs ├── DeviceId.Mac │ ├── DeviceId.Mac.csproj │ ├── DeviceIdBuilderExtensions.cs │ ├── MacDeviceIdBuilder.cs │ └── MacDeviceIdBuilderExtensions.cs ├── DeviceId.SqlServer │ ├── DeviceId.SqlServer.csproj │ ├── DeviceIdBuilderExtensions.cs │ ├── SqlServerDeviceIdBuilder.cs │ └── SqlServerDeviceIdBuilderExtensions.cs ├── DeviceId.Windows.Mmi │ ├── Components │ │ ├── MmiMacAddressDeviceIdComponent.cs │ │ ├── MmiSystemDriveSerialNumberDeviceIdComponent.cs │ │ └── MmiWqlDeviceIdComponent.cs │ ├── DeviceId.Windows.Mmi.csproj │ └── WindowsDeviceIdBuilderExtensions.cs ├── DeviceId.Windows.Wmi │ ├── Components │ │ ├── WmiDeviceIdComponent.cs │ │ ├── WmiMacAddressDeviceIdComponent.cs │ │ └── WmiSystemDriveSerialNumberDeviceIdComponent.cs │ ├── DeviceId.Windows.Wmi.csproj │ └── WindowsDeviceIdBuilderExtensions.cs ├── DeviceId.Windows.WmiLight │ ├── Components │ │ ├── WmiLightDeviceIdComponent.cs │ │ ├── WmiLightMacAddressDeviceIdComponent.cs │ │ └── WmiLightSystemDriveSerialNumberDeviceIdComponent.cs │ ├── DeviceId.Windows.WmiLight.csproj │ └── WindowsDeviceIdBuilderExtensions.cs ├── DeviceId.Windows │ ├── Components │ │ ├── RegistryValueDeviceIdComponent.cs │ │ └── SystemIdDeviceIdComponent.cs │ ├── DeviceId.Windows.csproj │ ├── DeviceIdBuilderExtensions.cs │ ├── WindowsDeviceIdBuilder.cs │ └── WindowsDeviceIdBuilderExtensions.cs ├── DeviceId.snk └── DeviceId │ ├── CommandExecutors │ ├── BashCommandExecutor.cs │ ├── CommandExecutor.cs │ ├── CommandExecutorBase.cs │ ├── ICommandExecutor.cs │ └── ShCommandExecutor.cs │ ├── Components │ ├── CommandComponent.cs │ ├── DatabaseQueryDeviceIdComponent.cs │ ├── DeviceIdComponent.cs │ ├── FileContentsDeviceIdComponent.cs │ ├── FileTokenDeviceIdComponent.cs │ └── MacAddressDeviceIdComponent.cs │ ├── DeviceId.csproj │ ├── DeviceIdBuilder.cs │ ├── DeviceIdBuilderExtensions.cs │ ├── DeviceIdFormatters.cs │ ├── DeviceIdManager.cs │ ├── DeviceIdVersionEncoder.cs │ ├── Encoders │ ├── Base32ByteArrayEncoder.cs │ ├── Base64ByteArrayEncoder.cs │ ├── Base64UrlByteArrayEncoder.cs │ ├── ByteArrayHasher.cs │ ├── HashDeviceIdComponentEncoder.cs │ ├── HexByteArrayEncoder.cs │ └── PlainTextDeviceIdComponentEncoder.cs │ ├── Formatters │ ├── HashDeviceIdFormatter.cs │ ├── StringDeviceIdFormatter.cs │ └── XmlDeviceIdFormatter.cs │ ├── IByteArrayEncoder.cs │ ├── IByteArrayHasher.cs │ ├── IDeviceIdComponent.cs │ ├── IDeviceIdComponentEncoder.cs │ ├── IDeviceIdFormatter.cs │ ├── IDeviceIdVersionEncoder.cs │ ├── Internal │ ├── ByteArrayEncoders.cs │ ├── ByteArrayHashers.cs │ ├── MacAddressFormatter.cs │ └── OS.cs │ └── _InternalsVisibleTo.cs └── test └── DeviceId.Tests ├── Components ├── DockerContainerIdComponentTests.cs ├── LinuxRootDriveSerialNumberDeviceIdComponentTests.cs ├── SqlServerComponentTests.cs └── WmiAndMmiDriveSerialNumberPerfTests.cs ├── DeviceId.Tests.csproj ├── DeviceId.Tests.snk ├── DeviceIdBuilderTests.cs ├── DeviceIdManagerTests.cs ├── Encoders ├── Base32ByteArrayEncoderTests.cs ├── Base64ByteArrayEncoderTests.cs ├── Base64UrlByteArrayEncoderTests.cs ├── HashDeviceIdComponentEncoderTests.cs ├── HexByteArrayEncoderTests.cs └── PlainTextDeviceIdComponentEncoderTests.cs ├── Formatters ├── HashDeviceIdFormatterTests.cs ├── StringDeviceIdFormatterTests.cs └── XmlDeviceIdFormatterTests.cs ├── Internal └── MacAddressFormatterTests.cs ├── Linux_4.4.txt ├── Linux_4.8-4.13.txt └── linux_nodocker.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | [root] = true 2 | 3 | [*] 4 | indent_style = space 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.csproj] 9 | indent_size = 2 10 | 11 | [*.cs] 12 | indent_size = 4 13 | csharp_style_namespace_declarations = file_scoped 14 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion 15 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style 16 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields 17 | dotnet_naming_style.instance_field_style.capitalization = camel_case 18 | dotnet_naming_style.instance_field_style.required_prefix = _ 19 | dotnet_naming_symbols.instance_fields.applicable_kinds = field 20 | dotnet_sort_system_directives_first = true 21 | dotnet_style_object_initializer = false 22 | dotnet_style_predefined_type_for_locals_parameters_members = true 23 | dotnet_style_predefined_type_for_member_access = false 24 | dotnet_style_prefer_inferred_anonymous_type_member_names = false 25 | dotnet_style_qualification_for_event = false 26 | dotnet_style_qualification_for_field = false 27 | dotnet_style_qualification_for_method = false 28 | dotnet_style_qualification_for_property = false 29 | dotnet_style_readonly_field = true 30 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 31 | dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary import 32 | dotnet_diagnostic.IDE0017.severity = none # Use object initializers 33 | dotnet_diagnostic.IDE0161.severity = warning # File-scoped namespaces 34 | 35 | [Directory.Build.props] 36 | indent_size = 2 37 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | artifacts/ 5 | *.user 6 | TestResults/ 7 | *.nupkg 8 | *.snupkg 9 | -------------------------------------------------------------------------------- /DeviceId.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31919.166 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BD122A91-437F-497E-80F1-F21BAD62C51F}" 7 | ProjectSection(SolutionItems) = preProject 8 | Directory.Build.props = Directory.Build.props 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5D030E54-9156-468B-9288-498DE74D8C61}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId", "src\DeviceId\DeviceId.csproj", "{02A96024-620B-4E75-AD22-439A7943BD4E}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Windows", "src\DeviceId.Windows\DeviceId.Windows.csproj", "{CB8011DD-7FD1-4BA5-A63C-8CF11AD706E2}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Windows.Wmi", "src\DeviceId.Windows.Wmi\DeviceId.Windows.Wmi.csproj", "{AAFA28A8-128B-4840-B38A-3D3408175C09}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Windows.Mmi", "src\DeviceId.Windows.Mmi\DeviceId.Windows.Mmi.csproj", "{1C78A1BF-A91D-4280-978D-9FD8B497FEF0}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Linux", "src\DeviceId.Linux\DeviceId.Linux.csproj", "{48E205B2-5C2B-4F98-AAD9-B44398ABA5E2}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Mac", "src\DeviceId.Mac\DeviceId.Mac.csproj", "{865D5271-A21F-4F4B-8F57-7278FEC051AA}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Tests", "test\DeviceId.Tests\DeviceId.Tests.csproj", "{C706C3B5-4912-41E6-9EF3-B8560464680A}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceId.SqlServer", "src\DeviceId.SqlServer\DeviceId.SqlServer.csproj", "{678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceId.Windows.WmiLight", "src\DeviceId.Windows.WmiLight\DeviceId.Windows.WmiLight.csproj", "{FA9327EF-BC7C-4237-8D6E-C81233362CEF}" 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {02A96024-620B-4E75-AD22-439A7943BD4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {02A96024-620B-4E75-AD22-439A7943BD4E}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {02A96024-620B-4E75-AD22-439A7943BD4E}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {02A96024-620B-4E75-AD22-439A7943BD4E}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {CB8011DD-7FD1-4BA5-A63C-8CF11AD706E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {CB8011DD-7FD1-4BA5-A63C-8CF11AD706E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {CB8011DD-7FD1-4BA5-A63C-8CF11AD706E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {CB8011DD-7FD1-4BA5-A63C-8CF11AD706E2}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {AAFA28A8-128B-4840-B38A-3D3408175C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {AAFA28A8-128B-4840-B38A-3D3408175C09}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {AAFA28A8-128B-4840-B38A-3D3408175C09}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {AAFA28A8-128B-4840-B38A-3D3408175C09}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {1C78A1BF-A91D-4280-978D-9FD8B497FEF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {1C78A1BF-A91D-4280-978D-9FD8B497FEF0}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {1C78A1BF-A91D-4280-978D-9FD8B497FEF0}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {1C78A1BF-A91D-4280-978D-9FD8B497FEF0}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {48E205B2-5C2B-4F98-AAD9-B44398ABA5E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {48E205B2-5C2B-4F98-AAD9-B44398ABA5E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {48E205B2-5C2B-4F98-AAD9-B44398ABA5E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {48E205B2-5C2B-4F98-AAD9-B44398ABA5E2}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {865D5271-A21F-4F4B-8F57-7278FEC051AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {865D5271-A21F-4F4B-8F57-7278FEC051AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {865D5271-A21F-4F4B-8F57-7278FEC051AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {865D5271-A21F-4F4B-8F57-7278FEC051AA}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {C706C3B5-4912-41E6-9EF3-B8560464680A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {C706C3B5-4912-41E6-9EF3-B8560464680A}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {C706C3B5-4912-41E6-9EF3-B8560464680A}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {C706C3B5-4912-41E6-9EF3-B8560464680A}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Release|Any CPU.Build.0 = Release|Any CPU 73 | EndGlobalSection 74 | GlobalSection(SolutionProperties) = preSolution 75 | HideSolutionNode = FALSE 76 | EndGlobalSection 77 | GlobalSection(NestedProjects) = preSolution 78 | {02A96024-620B-4E75-AD22-439A7943BD4E} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 79 | {CB8011DD-7FD1-4BA5-A63C-8CF11AD706E2} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 80 | {AAFA28A8-128B-4840-B38A-3D3408175C09} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 81 | {1C78A1BF-A91D-4280-978D-9FD8B497FEF0} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 82 | {48E205B2-5C2B-4F98-AAD9-B44398ABA5E2} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 83 | {865D5271-A21F-4F4B-8F57-7278FEC051AA} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 84 | {C706C3B5-4912-41E6-9EF3-B8560464680A} = {5D030E54-9156-468B-9288-498DE74D8C61} 85 | {678F5EAE-C13E-4EEC-A2F3-17F43CE099FE} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 86 | {FA9327EF-BC7C-4237-8D6E-C81233362CEF} = {BD122A91-437F-497E-80F1-F21BAD62C51F} 87 | EndGlobalSection 88 | GlobalSection(ExtensibilityGlobals) = postSolution 89 | SolutionGuid = {702DF5F2-A40A-46BC-A155-7CEAF7F7AA93} 90 | EndGlobalSection 91 | EndGlobal 92 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Matthew King 5 | Copyright Matthew King. 6 | MIT 7 | https://github.com/MatthewKing/DeviceId 8 | https://github.com/MatthewKing/DeviceId 9 | git 10 | deviceid;unique;device;identifier 11 | readme.md 12 | 6.9.0 13 | 14 | 15 | 16 | true 17 | ..\DeviceId.snk 18 | 19 | 20 | 21 | true 22 | true 23 | true 24 | snupkg 25 | 26 | 27 | 28 | 29 | false 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /build/build-alpha.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0\build-alpha.ps1" 3 | -------------------------------------------------------------------------------- /build/build-alpha.ps1: -------------------------------------------------------------------------------- 1 | $root = Resolve-Path (Join-Path $PSScriptRoot "..") 2 | $output = "$root/artifacts" 3 | $projects = @( 4 | "$root/src/DeviceId/DeviceId.csproj", 5 | "$root/src/DeviceId.Windows/DeviceId.Windows.csproj", 6 | "$root/src/DeviceId.Windows.Wmi/DeviceId.Windows.Wmi.csproj", 7 | "$root/src/DeviceId.Windows.Mmi/DeviceId.Windows.Mmi.csproj", 8 | "$root/src/DeviceId.SqlServer/DeviceId.SqlServer.csproj", 9 | "$root/src/DeviceId.Linux/DeviceId.Linux.csproj", 10 | "$root/src/DeviceId.Mac/DeviceId.Mac.csproj" 11 | ) 12 | 13 | $timestamp = git log -1 --format=%ct 14 | 15 | foreach ($project in $projects) { 16 | dotnet pack $project --configuration Release --output $output --version-suffix "alpha.$timestamp" -p:ContinuousIntegrationBuild=true 17 | } 18 | -------------------------------------------------------------------------------- /build/build-release.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0\build-release.ps1" 3 | -------------------------------------------------------------------------------- /build/build-release.ps1: -------------------------------------------------------------------------------- 1 | $root = Resolve-Path (Join-Path $PSScriptRoot "..") 2 | $output = "$root/artifacts" 3 | $projects = @( 4 | "$root/src/DeviceId/DeviceId.csproj", 5 | "$root/src/DeviceId.Windows/DeviceId.Windows.csproj", 6 | "$root/src/DeviceId.Windows.Wmi/DeviceId.Windows.Wmi.csproj", 7 | "$root/src/DeviceId.Windows.WmiLight/DeviceId.Windows.WmiLight.csproj", 8 | "$root/src/DeviceId.Windows.Mmi/DeviceId.Windows.Mmi.csproj", 9 | "$root/src/DeviceId.SqlServer/DeviceId.SqlServer.csproj", 10 | "$root/src/DeviceId.Linux/DeviceId.Linux.csproj", 11 | "$root/src/DeviceId.Mac/DeviceId.Mac.csproj" 12 | ) 13 | 14 | foreach ($project in $projects) { 15 | dotnet pack $project --configuration Release --output $output -p:ContinuousIntegrationBuild=true 16 | } 17 | -------------------------------------------------------------------------------- /build/push-to-nuget.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0\push-to-nuget.ps1" 3 | -------------------------------------------------------------------------------- /build/push-to-nuget.ps1: -------------------------------------------------------------------------------- 1 | $apiKey = Read-Host -Prompt "Enter your nuget.org API key" 2 | 3 | $root = Resolve-Path (Join-Path $PSScriptRoot "..") 4 | $output = "$root/artifacts" 5 | foreach ($package in Get-ChildItem -Path $output -Filter "*.nupkg") { 6 | dotnet nuget push $package.FullName --source https://api.nuget.org/v3/index.json --api-key $apiKey --skip-duplicate 7 | } 8 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2021 Matthew King 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/Components/DockerContainerIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace DeviceId.Linux.Components; 5 | 6 | /// 7 | /// An implementation of that uses the cgroup to read the Docker container id. 8 | /// 9 | public class DockerContainerIdComponent : IDeviceIdComponent 10 | { 11 | /// 12 | /// The cgroup file. 13 | /// 14 | private readonly string _cGroupFile; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The cgroup file. 20 | public DockerContainerIdComponent(string cGroupFile) 21 | { 22 | _cGroupFile = cGroupFile; 23 | } 24 | 25 | /// 26 | /// Gets the component value. 27 | /// 28 | /// The component value. 29 | public string GetValue() 30 | { 31 | if (string.IsNullOrWhiteSpace(_cGroupFile) || !File.Exists(_cGroupFile)) 32 | { 33 | return null; 34 | } 35 | 36 | using var file = File.OpenText(_cGroupFile); 37 | 38 | if (TryGetContainerId(file, out string containerId)) 39 | { 40 | return containerId; 41 | } 42 | 43 | return null; 44 | } 45 | 46 | private static bool TryGetContainerId(StreamReader reader, out string containerId) 47 | { 48 | var regex = new Regex("(\\d)+\\:(.)+?\\:(/.+?)??(/docker[-/])([0-9a-f]+)"); 49 | 50 | string line; 51 | while ((line = reader?.ReadLine()) != null) 52 | { 53 | var match = regex.Match(line); 54 | if (match.Success) 55 | { 56 | containerId = match.Groups[5].Value; 57 | return true; 58 | } 59 | } 60 | 61 | containerId = default; 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using System.Text.Json; 5 | using DeviceId.CommandExecutors; 6 | using DeviceId.Linux.Serialization; 7 | 8 | namespace DeviceId.Linux.Components; 9 | 10 | /// 11 | /// An implementation of that uses the root drive's serial number. 12 | /// 13 | public class LinuxRootDriveSerialNumberDeviceIdComponent : IDeviceIdComponent 14 | { 15 | /// 16 | /// Command executor. 17 | /// 18 | private readonly ICommandExecutor _commandExecutor; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | [EditorBrowsable(EditorBrowsableState.Never)] 24 | [Obsolete("This constructor is obsolete and will be removed in a future version. Use the constructor that accepts an ICommandExecutor instead.")] 25 | public LinuxRootDriveSerialNumberDeviceIdComponent() 26 | : this(CommandExecutor.Bash) { } 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The command executor to use. 32 | public LinuxRootDriveSerialNumberDeviceIdComponent(ICommandExecutor commandExecutor) 33 | { 34 | _commandExecutor = commandExecutor; 35 | } 36 | 37 | /// 38 | /// Gets the component value. 39 | /// 40 | /// The component value. 41 | public string GetValue() 42 | { 43 | var outputJson = _commandExecutor.Execute("lsblk -f -J"); 44 | var output = JsonSerializer.Deserialize(outputJson, SourceGenerationContext.Default.LsblkOutput); 45 | 46 | var device = FindRootParent(output); 47 | if (device == null) 48 | { 49 | return null; 50 | } 51 | 52 | var udevInfo = _commandExecutor.Execute($"udevadm info --query=all --name=/dev/{device.Name} | grep ID_SERIAL="); 53 | if (udevInfo == null) 54 | { 55 | return null; 56 | } 57 | 58 | var components = udevInfo.Split('='); 59 | if (components.Length < 2) 60 | { 61 | return null; 62 | } 63 | 64 | return components[1]; 65 | } 66 | 67 | private static LsblkDevice FindRootParent(LsblkOutput devices) 68 | { 69 | return devices.BlockDevices.FirstOrDefault(x => DeviceContainsRoot(x)); 70 | } 71 | 72 | private static bool DeviceContainsRoot(LsblkDevice device) 73 | { 74 | if (device.MountPoint == "/") 75 | { 76 | return true; 77 | } 78 | 79 | if (device.Children == null || device.Children.Count == 0) 80 | { 81 | return false; 82 | } 83 | 84 | return device.Children.Any(x => DeviceContainsRoot(x)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/DeviceId.Linux.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DeviceId.Linux 5 | DeviceId (Linux) 6 | Provides Linux-specific components for the DeviceId package. 7 | 8 | 9 | 10 | netstandard2.0;net8.0;net9.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/DeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Internal; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class DeviceIdBuilderExtensions 10 | { 11 | /// 12 | /// Adds Linux-specific components to the device ID. 13 | /// 14 | /// The device ID builder to add the components to. 15 | /// An action that adds the Linux-specific components. 16 | /// The device ID builder. 17 | public static DeviceIdBuilder OnLinux(this DeviceIdBuilder builder, Action linuxBuilderConfiguration) 18 | { 19 | if (OS.IsLinux && linuxBuilderConfiguration is not null) 20 | { 21 | var linuxBuilder = new LinuxDeviceIdBuilder(builder); 22 | linuxBuilderConfiguration.Invoke(linuxBuilder); 23 | } 24 | 25 | return builder; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/LinuxDeviceIdBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Internal; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Provides a fluent interface for adding Linux-specific components to a device identifier. 8 | /// 9 | public class LinuxDeviceIdBuilder 10 | { 11 | /// 12 | /// The base device identifier builder. 13 | /// 14 | private readonly DeviceIdBuilder _baseBuilder; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The base device identifier builder. 20 | public LinuxDeviceIdBuilder(DeviceIdBuilder baseBuilder) 21 | { 22 | _baseBuilder = baseBuilder ?? throw new ArgumentNullException(nameof(baseBuilder)); 23 | } 24 | 25 | /// 26 | /// Adds a component to the device identifier. 27 | /// If a component with the specified name already exists, it will be replaced with this newly added component. 28 | /// 29 | /// The component name. 30 | /// The component to add. 31 | /// The builder instance. 32 | public LinuxDeviceIdBuilder AddComponent(string name, IDeviceIdComponent component) 33 | { 34 | if (OS.IsLinux) 35 | { 36 | _baseBuilder.AddComponent(name, component); 37 | } 38 | 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/LinuxDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.CommandExecutors; 2 | using DeviceId.Components; 3 | using DeviceId.Linux.Components; 4 | 5 | namespace DeviceId; 6 | 7 | /// 8 | /// Extension methods for . 9 | /// 10 | public static class LinuxDeviceIdBuilderExtensions 11 | { 12 | /// 13 | /// Adds the system drive serial number to the device identifier. 14 | /// 15 | /// The to add the component to. 16 | /// The instance. 17 | public static LinuxDeviceIdBuilder AddSystemDriveSerialNumber(this LinuxDeviceIdBuilder builder) 18 | { 19 | return AddSystemDriveSerialNumber(builder, CommandExecutor.Bash); 20 | } 21 | 22 | /// 23 | /// Adds the system drive serial number to the device identifier. 24 | /// 25 | /// The to add the component to. 26 | /// The command executor to use. 27 | /// The instance. 28 | public static LinuxDeviceIdBuilder AddSystemDriveSerialNumber(this LinuxDeviceIdBuilder builder, ICommandExecutor commandExecutor) 29 | { 30 | return builder.AddComponent("SystemDriveSerialNumber", new LinuxRootDriveSerialNumberDeviceIdComponent(commandExecutor)); 31 | } 32 | 33 | /// 34 | /// Adds the docker container id to the device identifier. 35 | /// 36 | /// The to add the component to. 37 | /// The instance. 38 | public static LinuxDeviceIdBuilder AddDockerContainerId(this LinuxDeviceIdBuilder builder) 39 | { 40 | return builder.AddComponent("DockerContainerId", new DockerContainerIdComponent("/proc/1/cgroup")); 41 | } 42 | 43 | /// 44 | /// Adds the machine ID (from /var/lib/dbus/machine-id or /etc/machine-id) to the device identifier. 45 | /// 46 | /// The to add the component to. 47 | /// The instance. 48 | public static LinuxDeviceIdBuilder AddMachineId(this LinuxDeviceIdBuilder builder) 49 | { 50 | return builder.AddComponent("MachineID", new FileContentsDeviceIdComponent(new[] { "/var/lib/dbus/machine-id", "/etc/machine-id" }, false)); 51 | } 52 | 53 | /// 54 | /// Adds the product UUID (from /sys/class/dmi/id/product_uuid) to the device identifier. 55 | /// 56 | /// The to add the component to. 57 | /// The instance. 58 | public static LinuxDeviceIdBuilder AddProductUuid(this LinuxDeviceIdBuilder builder) 59 | { 60 | return builder.AddComponent("ProductUUID", new FileContentsDeviceIdComponent("/sys/class/dmi/id/product_uuid", false)); 61 | } 62 | 63 | /// 64 | /// Adds the CPU info (from /proc/cpuinfo) to the device identifier. 65 | /// 66 | /// The to add the component to. 67 | /// The instance. 68 | public static LinuxDeviceIdBuilder AddCpuInfo(this LinuxDeviceIdBuilder builder) 69 | { 70 | return builder.AddComponent("CPUInfo", new FileContentsDeviceIdComponent("/proc/cpuinfo", true)); 71 | } 72 | 73 | /// 74 | /// Adds the motherboard serial number (from /sys/class/dmi/id/board_serial) to the device identifier. 75 | /// 76 | /// The to add the component to. 77 | /// The instance. 78 | public static LinuxDeviceIdBuilder AddMotherboardSerialNumber(this LinuxDeviceIdBuilder builder) 79 | { 80 | return builder.AddComponent("MotherboardSerialNumber", new FileContentsDeviceIdComponent("/sys/class/dmi/id/board_serial", false)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/Serialization/LsblkDevice.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DeviceId.Linux.Serialization; 4 | 5 | internal sealed class LsblkDevice 6 | { 7 | public string Name { get; set; } = string.Empty; 8 | public string MountPoint { get; set; } = string.Empty; 9 | public List Children { get; set; } = new List(); 10 | } 11 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/Serialization/LsblkOutput.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DeviceId.Linux.Serialization; 4 | 5 | internal sealed class LsblkOutput 6 | { 7 | public List BlockDevices { get; set; } = new List(); 8 | } 9 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/Serialization/SourceGenerationContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace DeviceId.Linux.Serialization; 4 | 5 | [JsonSerializable(typeof(LsblkDevice))] 6 | [JsonSerializable(typeof(LsblkOutput))] 7 | [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)] 8 | internal partial class SourceGenerationContext : JsonSerializerContext { } 9 | -------------------------------------------------------------------------------- /src/DeviceId.Linux/_InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] 4 | [assembly: InternalsVisibleTo("DeviceId.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001008906d2e5a92d72693cfdd24b29f9c3ea5ca51972be746724afef8a65000a1ebbc88aee54e4c9c3bef49c0e837702170e99919a8b8075cfd6ed8494c5f9cd1a640a57cc907a84861bfe7ecb877d475a94ec333c6c0a586b6f37a15e67431381cac046217c0fa570c3e8e140e733254686213b77ae53fccdc1f5b3ab806ac692c1")] 5 | -------------------------------------------------------------------------------- /src/DeviceId.Mac/DeviceId.Mac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DeviceId.Mac 5 | DeviceId (Mac) 6 | Provides Mac-specific components for the DeviceId package. 7 | 8 | 9 | 10 | netstandard2.0;net8.0;net9.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/DeviceId.Mac/DeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Internal; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class DeviceIdBuilderExtensions 10 | { 11 | /// 12 | /// Adds Mac-specific components to the device ID. 13 | /// 14 | /// The device ID builder to add the components to. 15 | /// An action that adds the Mac-specific components. 16 | /// The device ID builder. 17 | public static DeviceIdBuilder OnMac(this DeviceIdBuilder builder, Action macBuilderConfiguration) 18 | { 19 | if (OS.IsMacOS && macBuilderConfiguration is not null) 20 | { 21 | var macBuilder = new MacDeviceIdBuilder(builder); 22 | macBuilderConfiguration.Invoke(macBuilder); 23 | } 24 | 25 | return builder; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/DeviceId.Mac/MacDeviceIdBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Internal; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Provides a fluent interface for adding Mac-specific components to a device identifier. 8 | /// 9 | public class MacDeviceIdBuilder 10 | { 11 | /// 12 | /// The base device identifier builder. 13 | /// 14 | private readonly DeviceIdBuilder _baseBuilder; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The base device identifier builder. 20 | public MacDeviceIdBuilder(DeviceIdBuilder baseBuilder) 21 | { 22 | _baseBuilder = baseBuilder ?? throw new ArgumentNullException(nameof(baseBuilder)); 23 | } 24 | 25 | /// 26 | /// Adds a component to the device identifier. 27 | /// If a component with the specified name already exists, it will be replaced with this newly added component. 28 | /// 29 | /// The component name. 30 | /// The component to add. 31 | /// The builder instance. 32 | public MacDeviceIdBuilder AddComponent(string name, IDeviceIdComponent component) 33 | { 34 | if (OS.IsMacOS) 35 | { 36 | _baseBuilder.AddComponent(name, component); 37 | } 38 | 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DeviceId.Mac/MacDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.CommandExecutors; 2 | using DeviceId.Components; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class MacDeviceIdBuilderExtensions 10 | { 11 | /// 12 | /// Adds the system drive serial number to the device identifier. 13 | /// 14 | /// The to add the component to. 15 | /// The instance. 16 | public static MacDeviceIdBuilder AddSystemDriveSerialNumber(this MacDeviceIdBuilder builder) 17 | { 18 | return AddSystemDriveSerialNumber(builder, CommandExecutor.Bash); 19 | } 20 | 21 | /// 22 | /// Adds the system drive serial number to the device identifier. 23 | /// 24 | /// The to add the component to. 25 | /// The command executor to use. 26 | /// The instance. 27 | public static MacDeviceIdBuilder AddSystemDriveSerialNumber(this MacDeviceIdBuilder builder, ICommandExecutor commandExecutor) 28 | { 29 | return builder.AddComponent("SystemDriveSerialNumber", new CommandComponent("system_profiler SPSerialATADataType | sed -En 's/.*Serial Number: ([\\d\\w]*)//p'", commandExecutor)); 30 | } 31 | 32 | /// 33 | /// Adds the platform serial number to the device identifier. 34 | /// 35 | /// The to add the component to. 36 | /// The instance. 37 | public static MacDeviceIdBuilder AddPlatformSerialNumber(this MacDeviceIdBuilder builder) 38 | { 39 | return AddPlatformSerialNumber(builder, CommandExecutor.Bash); 40 | } 41 | 42 | /// 43 | /// Adds the platform serial number to the device identifier. 44 | /// 45 | /// The to add the component to. 46 | /// The command executor to use. 47 | /// The instance. 48 | public static MacDeviceIdBuilder AddPlatformSerialNumber(this MacDeviceIdBuilder builder, ICommandExecutor commandExecutor) 49 | { 50 | return builder.AddComponent("IOPlatformSerialNumber", new CommandComponent("ioreg -l | grep IOPlatformSerialNumber | sed 's/.*= //' | sed 's/\"//g'", commandExecutor)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/DeviceId.SqlServer/DeviceId.SqlServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DeviceId.SqlServer 5 | DeviceId (SQL Server) 6 | Provides SQL Server components for the DeviceId package. 7 | 8 | 9 | 10 | net35;net40;netstandard2.0;net8.0;net9.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/DeviceId.SqlServer/DeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | namespace DeviceId.SqlServer; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class DeviceIdBuilderExtensions 10 | { 11 | /// 12 | /// Adds SQL Server components to the device ID. 13 | /// 14 | /// The device ID builder to add the components to. 15 | /// A connection to the SQL Server database. 16 | /// An action that adds the SQL Server components. 17 | /// The device ID builder. 18 | public static DeviceIdBuilder AddSqlServer(this DeviceIdBuilder builder, DbConnection connection, Action sqlServerBuilderConfiguration) 19 | { 20 | if (sqlServerBuilderConfiguration is not null) 21 | { 22 | var sqlServerBuilder = new SqlServerDeviceIdBuilder(builder, connection); 23 | sqlServerBuilderConfiguration.Invoke(sqlServerBuilder); 24 | } 25 | 26 | return builder; 27 | } 28 | 29 | /// 30 | /// Adds SQL Server components to the device ID. 31 | /// 32 | /// The device ID builder to add the components to. 33 | /// A factory used to get a connection to the SQL Server database. 34 | /// An action that adds the SQL Server components. 35 | /// The device ID builder. 36 | public static DeviceIdBuilder AddSqlServer(this DeviceIdBuilder builder, Func connectionFactory, Action sqlServerBuilderConfiguration) 37 | { 38 | if (sqlServerBuilderConfiguration is not null) 39 | { 40 | var sqlServerBuilder = new SqlServerDeviceIdBuilder(builder, connectionFactory); 41 | sqlServerBuilderConfiguration.Invoke(sqlServerBuilder); 42 | } 43 | 44 | return builder; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/DeviceId.SqlServer/SqlServerDeviceIdBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | using DeviceId.Components; 4 | 5 | namespace DeviceId.SqlServer; 6 | 7 | /// 8 | /// Provides a fluent interface for adding SQL Server components to a device identifier. 9 | /// 10 | public class SqlServerDeviceIdBuilder 11 | { 12 | /// 13 | /// The base device identifier builder. 14 | /// 15 | private readonly DeviceIdBuilder _baseBuilder; 16 | 17 | /// 18 | /// A factory used to get a connection to the SQL Server database. 19 | /// 20 | private readonly Func _connectionFactory; 21 | 22 | /// 23 | /// A value determining whether the connection should be disposed after use. 24 | /// 25 | private readonly bool _disposeConnection; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// The base device identifier builder. 31 | /// A connection to the SQL Server database. 32 | public SqlServerDeviceIdBuilder(DeviceIdBuilder baseBuilder, DbConnection connection) 33 | { 34 | if (baseBuilder is null) 35 | { 36 | throw new ArgumentNullException(nameof(baseBuilder)); 37 | } 38 | 39 | if (connection is null) 40 | { 41 | throw new ArgumentNullException(nameof(connection)); 42 | } 43 | 44 | _baseBuilder = baseBuilder; 45 | _connectionFactory = () => connection; 46 | _disposeConnection = false; 47 | } 48 | 49 | /// 50 | /// Initializes a new instance of the class. 51 | /// 52 | /// The base device identifier builder. 53 | /// A factory used to get a connection to the SQL Server database. 54 | public SqlServerDeviceIdBuilder(DeviceIdBuilder baseBuilder, Func connectionFactory) 55 | { 56 | if (baseBuilder is null) 57 | { 58 | throw new ArgumentNullException(nameof(baseBuilder)); 59 | } 60 | 61 | if (connectionFactory is null) 62 | { 63 | throw new ArgumentNullException(nameof(connectionFactory)); 64 | } 65 | 66 | _baseBuilder = baseBuilder; 67 | _connectionFactory = connectionFactory; 68 | _disposeConnection = true; 69 | } 70 | 71 | /// 72 | /// Adds the result of a SQL query to the device identifier. 73 | /// 74 | /// The name of the component. 75 | /// SQL query that returns a single value to be added to the device identifier. 76 | /// The instance. 77 | public SqlServerDeviceIdBuilder AddQueryResult(string componentName, string sql) 78 | { 79 | return AddQueryResult(componentName, sql, x => x.ToString()); 80 | } 81 | 82 | /// 83 | /// Adds the result of a SQL query to the device identifier. 84 | /// 85 | /// The name of the component. 86 | /// SQL query that returns a single value to be added to the device identifier. 87 | /// A function that transforms the result of the query into a string. 88 | /// The instance. 89 | public SqlServerDeviceIdBuilder AddQueryResult(string componentName, string sql, Func valueTransformer) 90 | { 91 | _baseBuilder.Components.Add(componentName, new DatabaseQueryDeviceIdComponent(_connectionFactory, sql, valueTransformer, _disposeConnection)); 92 | 93 | return this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/DeviceId.SqlServer/SqlServerDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId.SqlServer; 2 | 3 | /// 4 | /// Extension methods for . 5 | /// 6 | public static class SqlServerDeviceIdBuilderExtensions 7 | { 8 | /// 9 | /// Adds the server name to the device identifier. 10 | /// 11 | /// The to add the component to. 12 | /// The instance. 13 | public static SqlServerDeviceIdBuilder AddServerName(this SqlServerDeviceIdBuilder builder) 14 | { 15 | var name = $"SqlServerProperty:ServerName"; 16 | var sql = $"select serverproperty('ServerName');"; 17 | 18 | return builder.AddQueryResult(name, sql, x => 19 | { 20 | var s = x.ToString(); 21 | var i = s.IndexOf('#'); 22 | return i >= 0 ? s.Substring(0, i) : s; 23 | }); 24 | } 25 | 26 | /// 27 | /// Adds the database name to the device identifier. 28 | /// 29 | /// The to add the component to. 30 | /// The instance. 31 | public static SqlServerDeviceIdBuilder AddDatabaseName(this SqlServerDeviceIdBuilder builder) 32 | { 33 | var name = "SqlServerDatabaseName"; 34 | var sql = "select db_name();"; 35 | 36 | return builder.AddQueryResult(name, sql); 37 | } 38 | 39 | /// 40 | /// Adds the database ID to the device identifier. 41 | /// 42 | /// The to add the component to. 43 | /// The instance. 44 | public static SqlServerDeviceIdBuilder AddDatabaseId(this SqlServerDeviceIdBuilder builder) 45 | { 46 | var name = "SqlServerDatabaseId"; 47 | var sql = "select db_id();"; 48 | 49 | return builder.AddQueryResult(name, sql); 50 | } 51 | 52 | /// 53 | /// Adds the specified server property to the device identifier. 54 | /// 55 | /// The to add the component to. 56 | /// The property name. 57 | /// The instance. 58 | public static SqlServerDeviceIdBuilder AddServerProperty(this SqlServerDeviceIdBuilder builder, string propertyName) 59 | { 60 | var name = $"SqlServerProperty:{propertyName}"; 61 | var sql = $"select serverproperty('{propertyName.Replace("'", "''")}')"; 62 | 63 | return builder.AddQueryResult(name, sql); 64 | } 65 | 66 | /// 67 | /// Adds the specified extended property to the device identifier. 68 | /// 69 | /// The to add the component to. 70 | /// The property name. 71 | /// The instance. 72 | public static SqlServerDeviceIdBuilder AddExtendedProperty(this SqlServerDeviceIdBuilder builder, string propertyName) 73 | { 74 | var name = $"SqlServerExtendedProperty:{propertyName}"; 75 | var sql = $"select [value] from [sys].[extended_properties] where [name] = '{propertyName.Replace("'", "''")}';"; 76 | 77 | return builder.AddQueryResult(name, sql); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Mmi/Components/MmiMacAddressDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DeviceId.Components; 3 | using DeviceId.Internal; 4 | using Microsoft.Management.Infrastructure; 5 | 6 | namespace DeviceId.Windows.Mmi.Components; 7 | 8 | /// 9 | /// An implementation of that uses the MAC Address of the PC. 10 | /// This improves upon the basic by using MMI 11 | /// to get better information from either MSFT_NetAdapter or Win32_NetworkAdapter. 12 | /// 13 | public class MmiMacAddressDeviceIdComponent : IDeviceIdComponent 14 | { 15 | /// 16 | /// A value determining whether wireless devices should be excluded. 17 | /// 18 | private readonly bool _excludeWireless; 19 | 20 | /// 21 | /// A value determining whether non-physical devices should be excluded. 22 | /// 23 | private readonly bool _excludeNonPhysical; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// A value determining whether wireless devices should be excluded. 29 | /// A value determining whether non-physical devices should be excluded. 30 | public MmiMacAddressDeviceIdComponent(bool excludeWireless, bool excludeNonPhysical) 31 | { 32 | _excludeWireless = excludeWireless; 33 | _excludeNonPhysical = excludeNonPhysical; 34 | } 35 | 36 | /// 37 | /// Gets the component value. 38 | /// 39 | /// The component value. 40 | public string GetValue() 41 | { 42 | // First, try to get a value using MSFT_NetAdapter: 43 | try 44 | { 45 | return GetValueUsingMsftNetAdapter(_excludeWireless, _excludeNonPhysical); 46 | } 47 | catch { } 48 | 49 | // Next, try using Win32_NetworkAdapter: 50 | try 51 | { 52 | return GetValueUsingWin32NetworkAdapter(_excludeWireless, _excludeNonPhysical); 53 | } 54 | catch { } 55 | 56 | // Finally, try the fallback component: 57 | var fallback = new MacAddressDeviceIdComponent(_excludeWireless); 58 | return fallback.GetValue(); 59 | } 60 | 61 | /// 62 | /// Gets the component value using MSFT_NetAdapter. 63 | /// 64 | /// A value determining whether wireless devices should be excluded. 65 | /// A value determining whether non-physical devices should be excluded. 66 | /// The component value. 67 | private static string GetValueUsingMsftNetAdapter(bool excludeWireless, bool excludeNonPhysical) 68 | { 69 | var values = new List(); 70 | 71 | using var session = CimSession.Create(null); 72 | 73 | foreach (var instance in session.EnumerateInstances("root/StandardCimv2", "MSFT_NetAdapter")) 74 | { 75 | // Skip non-physical adapters if instructed to do so. 76 | if (instance.CimInstanceProperties["ConnectorPresent"].Value is bool connectorPresent) 77 | { 78 | if (excludeNonPhysical && !connectorPresent) 79 | { 80 | continue; 81 | } 82 | } 83 | 84 | // Skip wireless adapters if instructed to do so. 85 | if (instance.CimInstanceProperties["NdisPhysicalMedium"].Value is uint ndisPhysicalMedium) 86 | { 87 | if (excludeWireless && ndisPhysicalMedium == 9) // Native802_11 88 | { 89 | continue; 90 | } 91 | } 92 | 93 | if (instance.CimInstanceProperties["PermanentAddress"].Value is string permanentAddress) 94 | { 95 | if (!string.IsNullOrEmpty(permanentAddress)) 96 | { 97 | // Ensure the hardware addresses are formatted as MAC addresses if possible. 98 | // This is a discrepancy between the MSFT_NetAdapter and Win32_NetworkAdapter interfaces. 99 | values.Add(MacAddressFormatter.FormatMacAddress(permanentAddress)); 100 | } 101 | } 102 | } 103 | 104 | values.Sort(); 105 | 106 | return values.Count > 0 107 | ? string.Join(",", values.ToArray()) 108 | : null; 109 | } 110 | 111 | /// 112 | /// Gets the component value using Win32_NetworkAdapter. 113 | /// 114 | /// A value determining whether wireless devices should be excluded. 115 | /// A value determining whether non-physical devices should be excluded. 116 | /// The component value. 117 | private static string GetValueUsingWin32NetworkAdapter(bool excludeWireless, bool excludeNonPhysical) 118 | { 119 | var values = new List(); 120 | 121 | using var session = CimSession.Create(null); 122 | foreach (var instance in session.QueryInstances(@"root\cimv2", "WQL", "select MACAddress, AdapterTypeID, PhysicalAdapter from Win32_NetworkAdapter")) 123 | { 124 | // Skip non-physical adapters if instructed to do so. 125 | if (instance.CimInstanceProperties["PhysicalAdapter"].Value is bool isPhysical) 126 | { 127 | if (excludeNonPhysical && !isPhysical) 128 | { 129 | continue; 130 | } 131 | } 132 | 133 | // Skip wireless adapters if instructed to do so. 134 | if (instance.CimInstanceProperties["AdapterTypeID"].Value is ushort adapterTypeId) 135 | { 136 | if (excludeWireless && adapterTypeId == 9) 137 | { 138 | continue; 139 | } 140 | } 141 | 142 | if (instance.CimInstanceProperties["MACAddress"].Value is string macAddress) 143 | { 144 | if (!string.IsNullOrEmpty(macAddress)) 145 | { 146 | values.Add(macAddress); 147 | } 148 | } 149 | } 150 | 151 | values.Sort(); 152 | 153 | return values.Count > 0 154 | ? string.Join(",", values.ToArray()) 155 | : null; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Mmi/Components/MmiSystemDriveSerialNumberDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Management.Infrastructure; 3 | 4 | namespace DeviceId.Windows.Mmi.Components; 5 | 6 | /// 7 | /// An implementation of that uses the system drive's serial number. 8 | /// 9 | public class MmiSystemDriveSerialNumberDeviceIdComponent : IDeviceIdComponent 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MmiSystemDriveSerialNumberDeviceIdComponent() { } 15 | 16 | /// 17 | /// Gets the component value. 18 | /// 19 | /// The component value. 20 | public string GetValue() 21 | { 22 | var systemDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System); 23 | 24 | // SystemDirectory can sometimes be null or empty. 25 | // See: https://github.com/dotnet/runtime/issues/21430 and https://github.com/MatthewKing/DeviceId/issues/64 26 | if (string.IsNullOrEmpty(systemDirectory) || systemDirectory.Length < 2) 27 | { 28 | return null; 29 | } 30 | 31 | try 32 | { 33 | var systemLogicalDiskDeviceId = systemDirectory.Substring(0, 2); 34 | 35 | using var session = CimSession.Create(null); 36 | 37 | foreach (var logicalDiskAssociator in session.QueryInstances(@"root\cimv2", "WQL", $"ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID=\"{systemLogicalDiskDeviceId}\"}} WHERE ResultClass = Win32_DiskPartition")) 38 | { 39 | if (logicalDiskAssociator.CimClass.CimSystemProperties.ClassName == "Win32_DiskPartition") 40 | { 41 | if (logicalDiskAssociator.CimInstanceProperties["DeviceId"].Value is string diskPartitionDeviceId) 42 | { 43 | foreach (var diskPartitionAssociator in session.QueryInstances(@"root\cimv2", "WQL", $"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID=\"{diskPartitionDeviceId}\"}}")) 44 | { 45 | if (diskPartitionAssociator.CimClass.CimSystemProperties.ClassName == "Win32_DiskDrive") 46 | { 47 | if (diskPartitionAssociator.CimInstanceProperties["SerialNumber"].Value is string diskDriveSerialNumber) 48 | { 49 | return diskDriveSerialNumber; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | catch 58 | { 59 | // Swallow exceptions. 60 | } 61 | 62 | return null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Mmi/Components/MmiWqlDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Management.Infrastructure; 3 | 4 | namespace DeviceId.Windows.Mmi.Components; 5 | 6 | /// 7 | /// An implementation of that retrieves data from a WQL query 8 | /// 9 | public class MmiWqlDeviceIdComponent : IDeviceIdComponent 10 | { 11 | /// 12 | /// The class name. 13 | /// 14 | private readonly string _className; 15 | 16 | /// 17 | /// The property name. 18 | /// 19 | private readonly string _propertyName; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The class name. 25 | /// The property name. 26 | public MmiWqlDeviceIdComponent(string className, string propertyName) 27 | { 28 | _className = className; 29 | _propertyName = propertyName; 30 | } 31 | 32 | /// 33 | /// Gets the component value. 34 | /// 35 | /// The component value. 36 | public string GetValue() 37 | { 38 | var values = new List(); 39 | 40 | try 41 | { 42 | using var session = CimSession.Create(null); 43 | 44 | var instances = session.QueryInstances(@"root\cimv2", "WQL", $"SELECT {_propertyName} FROM {_className}"); 45 | foreach (var instance in instances) 46 | { 47 | try 48 | { 49 | if (instance.CimInstanceProperties[_propertyName].Value is string value) 50 | { 51 | values.Add(value); 52 | } 53 | } 54 | finally 55 | { 56 | instance.Dispose(); 57 | } 58 | } 59 | } 60 | catch 61 | { 62 | 63 | } 64 | 65 | values.Sort(); 66 | 67 | return values.Count > 0 68 | ? string.Join(",", values.ToArray()) 69 | : null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Mmi/DeviceId.Windows.Mmi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DeviceId.Windows.Mmi 5 | DeviceId (Windows / MMI) 6 | Provides extra Windows-specific components (using MMI) for the DeviceId package. 7 | 8 | 9 | 10 | netstandard2.0;net8.0;net9.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Mmi/WindowsDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using DeviceId.Windows.Mmi.Components; 4 | 5 | namespace DeviceId; 6 | 7 | /// 8 | /// Extension methods for . 9 | /// 10 | public static class WindowsDeviceIdBuilderExtensions 11 | { 12 | /// 13 | /// Adds the MAC address to the device identifier, optionally excluding wireless adapters and/or non-physical adapters. 14 | /// 15 | /// The to add the component to. 16 | /// A value indicating whether wireless adapters should be excluded. 17 | /// A value indicating whether non-physical adapters should be excluded. 18 | /// The instance. 19 | public static WindowsDeviceIdBuilder AddMacAddressFromMmi(this WindowsDeviceIdBuilder builder, bool excludeWireless, bool excludeNonPhysical) 20 | { 21 | return builder.AddComponent("MACAddress", new MmiMacAddressDeviceIdComponent(excludeWireless, excludeNonPhysical)); 22 | } 23 | 24 | /// 25 | /// Adds the processor ID to the device identifier. 26 | /// 27 | /// The to add the component to. 28 | /// The instance. 29 | public static WindowsDeviceIdBuilder AddProcessorId(this WindowsDeviceIdBuilder builder) 30 | { 31 | return builder.AddComponent("ProcessorId", new MmiWqlDeviceIdComponent("Win32_Processor", "ProcessorId")); 32 | } 33 | 34 | /// 35 | /// Adds the motherboard serial number to the device identifier. 36 | /// 37 | /// The to add the component to. 38 | /// The instance. 39 | public static WindowsDeviceIdBuilder AddMotherboardSerialNumber(this WindowsDeviceIdBuilder builder) 40 | { 41 | return builder.AddComponent("MotherboardSerialNumber", new MmiWqlDeviceIdComponent("Win32_BaseBoard", "SerialNumber")); 42 | } 43 | 44 | /// 45 | /// Adds the system UUID to the device identifier. 46 | /// 47 | /// The to add the component to. 48 | /// The instance. 49 | public static WindowsDeviceIdBuilder AddSystemUuid(this WindowsDeviceIdBuilder builder) 50 | { 51 | return builder.AddComponent("SystemUUID", new MmiWqlDeviceIdComponent("Win32_ComputerSystemProduct", "UUID")); 52 | } 53 | 54 | /// 55 | /// Adds the system serial drive number to the device identifier. 56 | /// 57 | /// The to add the component to. 58 | /// The instance. 59 | [EditorBrowsable(EditorBrowsableState.Never)] 60 | [Obsolete("This method name was a typo. Use AddSystemDriveSerialNumber instead.")] 61 | public static WindowsDeviceIdBuilder AddSystemSerialDriveNumber(this WindowsDeviceIdBuilder builder) 62 | { 63 | return builder.AddComponent("SystemDriveSerialNumber", new MmiSystemDriveSerialNumberDeviceIdComponent()); 64 | } 65 | 66 | /// 67 | /// Adds the system serial drive number to the device identifier. 68 | /// 69 | /// The to add the component to. 70 | /// The instance. 71 | public static WindowsDeviceIdBuilder AddSystemDriveSerialNumber(this WindowsDeviceIdBuilder builder) 72 | { 73 | return builder.AddComponent("SystemDriveSerialNumber", new MmiSystemDriveSerialNumberDeviceIdComponent()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Wmi/Components/WmiDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management; 3 | 4 | namespace DeviceId.Windows.Wmi.Components; 5 | 6 | /// 7 | /// An implementation of that retrieves data from a WMI class. 8 | /// 9 | public class WmiDeviceIdComponent : IDeviceIdComponent 10 | { 11 | /// 12 | /// The class name. 13 | /// 14 | private readonly string _className; 15 | 16 | /// 17 | /// The property name. 18 | /// 19 | private readonly string _propertyName; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The class name. 25 | /// The property name. 26 | public WmiDeviceIdComponent(string className, string propertyName) 27 | { 28 | _className = className; 29 | _propertyName = propertyName; 30 | } 31 | 32 | /// 33 | /// Gets the component value. 34 | /// 35 | /// The component value. 36 | public string GetValue() 37 | { 38 | var values = new List(); 39 | 40 | try 41 | { 42 | using var managementObjectSearcher = new ManagementObjectSearcher($"SELECT {_propertyName} FROM {_className}"); 43 | using var managementObjectCollection = managementObjectSearcher.Get(); 44 | foreach (var managementObject in managementObjectCollection) 45 | { 46 | try 47 | { 48 | if (managementObject[_propertyName] is string value) 49 | { 50 | values.Add(value); 51 | } 52 | } 53 | finally 54 | { 55 | managementObject.Dispose(); 56 | } 57 | } 58 | } 59 | catch 60 | { 61 | 62 | } 63 | 64 | values.Sort(); 65 | 66 | return values.Count > 0 67 | ? string.Join(",", values.ToArray()) 68 | : null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Wmi/Components/WmiMacAddressDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management; 3 | using DeviceId.Components; 4 | using DeviceId.Internal; 5 | 6 | namespace DeviceId.Windows.Wmi.Components; 7 | 8 | /// 9 | /// An implementation of that uses the MAC Address of the PC. 10 | /// This improves upon the basic by using WMI 11 | /// to get better information from either MSFT_NetAdapter or Win32_NetworkAdapter. 12 | /// 13 | public class WmiMacAddressDeviceIdComponent : IDeviceIdComponent 14 | { 15 | /// 16 | /// A value determining whether wireless devices should be excluded. 17 | /// 18 | private readonly bool _excludeWireless; 19 | 20 | /// 21 | /// A value determining whether non-physical devices should be excluded. 22 | /// 23 | private readonly bool _excludeNonPhysical; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// A value determining whether wireless devices should be excluded. 29 | /// A value determining whether non-physical devices should be excluded. 30 | public WmiMacAddressDeviceIdComponent(bool excludeWireless, bool excludeNonPhysical) 31 | { 32 | _excludeWireless = excludeWireless; 33 | _excludeNonPhysical = excludeNonPhysical; 34 | } 35 | 36 | /// 37 | /// Gets the component value. 38 | /// 39 | /// The component value. 40 | public string GetValue() 41 | { 42 | // First, try to get a value using MSFT_NetAdapter: 43 | try 44 | { 45 | return GetValueUsingMsftNetAdapter(_excludeWireless, _excludeNonPhysical); 46 | } 47 | catch { } 48 | 49 | // Next, try using Win32_NetworkAdapter: 50 | try 51 | { 52 | return GetValueUsingWin32NetworkAdapter(_excludeWireless, _excludeNonPhysical); 53 | } 54 | catch { } 55 | 56 | // Finally, try the fallback component: 57 | var fallback = new MacAddressDeviceIdComponent(_excludeWireless); 58 | return fallback.GetValue(); 59 | } 60 | 61 | /// 62 | /// Gets the component value using MSFT_NetAdapter. 63 | /// 64 | /// A value determining whether wireless devices should be excluded. 65 | /// A value determining whether non-physical devices should be excluded. 66 | /// The component value. 67 | private static string GetValueUsingMsftNetAdapter(bool excludeWireless, bool excludeNonPhysical) 68 | { 69 | var values = new List(); 70 | 71 | using var managementClass = new ManagementClass("root/StandardCimv2", "MSFT_NetAdapter", new ObjectGetOptions()); 72 | 73 | foreach (var managementInstance in managementClass.GetInstances()) 74 | { 75 | try 76 | { 77 | // Skip non-physical adapters if instructed to do so. 78 | if (managementInstance["ConnectorPresent"] is bool isPhysical) 79 | { 80 | if (excludeNonPhysical && !isPhysical) 81 | { 82 | continue; 83 | } 84 | } 85 | 86 | // Skip wireless adapters if instructed to do so. 87 | if (managementInstance["NdisPhysicalMedium"] is uint ndisPhysicalMedium) 88 | { 89 | if (excludeWireless && ndisPhysicalMedium == 9) // Native802_11 90 | { 91 | continue; 92 | } 93 | } 94 | 95 | if (managementInstance["PermanentAddress"] is string permanentAddress) 96 | { 97 | // Ensure the hardware addresses are formatted as MAC addresses if possible. 98 | // This is a discrepancy between the MSFT_NetAdapter and Win32_NetworkAdapter interfaces. 99 | values.Add(MacAddressFormatter.FormatMacAddress(permanentAddress)); 100 | } 101 | } 102 | finally 103 | { 104 | managementInstance.Dispose(); 105 | } 106 | } 107 | 108 | values.Sort(); 109 | 110 | return values.Count > 0 111 | ? string.Join(",", values.ToArray()) 112 | : null; 113 | } 114 | 115 | /// 116 | /// Gets the component value using Win32_NetworkAdapter. 117 | /// 118 | /// A value determining whether wireless devices should be excluded. 119 | /// A value determining whether non-physical devices should be excluded. 120 | /// The component value. 121 | private static string GetValueUsingWin32NetworkAdapter(bool excludeWireless, bool excludeNonPhysical) 122 | { 123 | var values = new List(); 124 | 125 | using var managementObjectSearcher = new ManagementObjectSearcher("select MACAddress, AdapterTypeID, PhysicalAdapter from Win32_NetworkAdapter"); 126 | using var managementObjectCollection = managementObjectSearcher.Get(); 127 | foreach (var managementObject in managementObjectCollection) 128 | { 129 | try 130 | { 131 | // Skip non-physical adapters if instructed to do so. 132 | if (managementObject["PhysicalAdapter"] is bool isPhysical) 133 | { 134 | if (excludeNonPhysical && !isPhysical) 135 | { 136 | continue; 137 | } 138 | } 139 | 140 | // Skip wireless adapters if instructed to do so. 141 | if (managementObject["AdapterTypeID"] is ushort adapterTypeId) 142 | { 143 | if (excludeWireless && adapterTypeId == 9) 144 | { 145 | continue; 146 | } 147 | } 148 | 149 | if (managementObject["MACAddress"] is string macAddress) 150 | { 151 | if (!string.IsNullOrEmpty(macAddress)) 152 | { 153 | values.Add(macAddress); 154 | } 155 | } 156 | } 157 | finally 158 | { 159 | managementObject.Dispose(); 160 | } 161 | } 162 | 163 | values.Sort(); 164 | 165 | return values.Count > 0 166 | ? string.Join(",", values.ToArray()) 167 | : null; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Wmi/Components/WmiSystemDriveSerialNumberDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Management; 4 | 5 | namespace DeviceId.Windows.Wmi.Components; 6 | 7 | /// 8 | /// An implementation of that uses the system drive's serial number. 9 | /// 10 | public class WmiSystemDriveSerialNumberDeviceIdComponent : IDeviceIdComponent 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public WmiSystemDriveSerialNumberDeviceIdComponent() { } 16 | 17 | /// 18 | /// Gets the component value. 19 | /// 20 | /// The component value. 21 | public string GetValue() 22 | { 23 | try 24 | { 25 | var systemDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System); 26 | 27 | // SystemDirectory can sometimes be null or empty. 28 | // See: https://github.com/dotnet/runtime/issues/21430 and https://github.com/MatthewKing/DeviceId/issues/64 29 | if (string.IsNullOrEmpty(systemDirectory) || systemDirectory.Length < 2) 30 | { 31 | return null; 32 | } 33 | 34 | var systemLogicalDiskDeviceId = systemDirectory.Substring(0, 2); 35 | 36 | var queryString = $"SELECT * FROM Win32_LogicalDisk where DeviceId = '{systemLogicalDiskDeviceId}'"; 37 | using var searcher = new ManagementObjectSearcher(queryString); 38 | 39 | foreach (var disk in searcher.Get().OfType()) 40 | { 41 | foreach (var partition in disk.GetRelated("Win32_DiskPartition").OfType()) 42 | { 43 | foreach (var drive in partition.GetRelated("Win32_DiskDrive").OfType()) 44 | { 45 | if (drive["SerialNumber"] is string serialNumber) 46 | { 47 | return serialNumber; 48 | } 49 | } 50 | } 51 | } 52 | } 53 | catch 54 | { 55 | // Swallow exceptions. 56 | } 57 | 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Wmi/DeviceId.Windows.Wmi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DeviceId.Windows.Wmi 5 | DeviceId (Windows / WMI) 6 | Provides extra Windows-specific components (using WMI) for the DeviceId package. 7 | 8 | 9 | 10 | net35;net40;netstandard2.0;net8.0;net9.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.Wmi/WindowsDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using DeviceId.Windows.Wmi.Components; 4 | 5 | namespace DeviceId; 6 | 7 | /// 8 | /// Extension methods for . 9 | /// 10 | public static class WindowsDeviceIdBuilderExtensions 11 | { 12 | /// 13 | /// Adds the MAC address to the device identifier, optionally excluding wireless adapters and/or non-physical adapters. 14 | /// 15 | /// The to add the component to. 16 | /// A value indicating whether wireless adapters should be excluded. 17 | /// A value indicating whether non-physical adapters should be excluded. 18 | /// The instance. 19 | public static WindowsDeviceIdBuilder AddMacAddressFromWmi(this WindowsDeviceIdBuilder builder, bool excludeWireless, bool excludeNonPhysical) 20 | { 21 | return builder.AddComponent("MACAddress", new WmiMacAddressDeviceIdComponent(excludeWireless, excludeNonPhysical)); 22 | } 23 | 24 | /// 25 | /// Adds the processor ID to the device identifier. 26 | /// 27 | /// The to add the component to. 28 | /// The instance. 29 | public static WindowsDeviceIdBuilder AddProcessorId(this WindowsDeviceIdBuilder builder) 30 | { 31 | return builder.AddComponent("ProcessorId", new WmiDeviceIdComponent("Win32_Processor", "ProcessorId")); 32 | } 33 | 34 | /// 35 | /// Adds the motherboard serial number to the device identifier. 36 | /// 37 | /// The to add the component to. 38 | /// The instance. 39 | public static WindowsDeviceIdBuilder AddMotherboardSerialNumber(this WindowsDeviceIdBuilder builder) 40 | { 41 | return builder.AddComponent("MotherboardSerialNumber", new WmiDeviceIdComponent("Win32_BaseBoard", "SerialNumber")); 42 | } 43 | 44 | /// 45 | /// Adds the system UUID to the device identifier. 46 | /// 47 | /// The to add the component to. 48 | /// The instance. 49 | public static WindowsDeviceIdBuilder AddSystemUuid(this WindowsDeviceIdBuilder builder) 50 | { 51 | return builder.AddComponent("SystemUUID", new WmiDeviceIdComponent("Win32_ComputerSystemProduct", "UUID")); 52 | } 53 | 54 | /// 55 | /// Adds the system serial drive number to the device identifier. 56 | /// 57 | /// The to add the component to. 58 | /// The instance. 59 | [EditorBrowsable(EditorBrowsableState.Never)] 60 | [Obsolete("This method name was a typo. Use AddSystemDriveSerialNumber instead.")] 61 | public static WindowsDeviceIdBuilder AddSystemSerialDriveNumber(this WindowsDeviceIdBuilder builder) 62 | { 63 | return builder.AddComponent("SystemDriveSerialNumber", new WmiSystemDriveSerialNumberDeviceIdComponent()); 64 | } 65 | 66 | /// 67 | /// Adds the system serial drive number to the device identifier. 68 | /// 69 | /// The to add the component to. 70 | /// The instance. 71 | public static WindowsDeviceIdBuilder AddSystemDriveSerialNumber(this WindowsDeviceIdBuilder builder) 72 | { 73 | return builder.AddComponent("SystemDriveSerialNumber", new WmiSystemDriveSerialNumberDeviceIdComponent()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.WmiLight/Components/WmiLightDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using WmiLight; 3 | 4 | namespace DeviceId.Windows.WmiLight.Components; 5 | 6 | /// 7 | /// An implementation of that retrieves data from a WMI class. 8 | /// 9 | /// The class name. 10 | /// The property name. 11 | public class WmiLightDeviceIdComponent(string className, string propertyName) : IDeviceIdComponent 12 | { 13 | /// 14 | /// Gets the component value. 15 | /// 16 | /// The component value. 17 | public string GetValue() 18 | { 19 | var values = new List(); 20 | 21 | try 22 | { 23 | using var wmiConnection = new WmiConnection(); 24 | foreach (var wmiObject in wmiConnection.CreateQuery($"SELECT * FROM {className}")) 25 | { 26 | try 27 | { 28 | if (wmiObject[propertyName] is string value) 29 | { 30 | values.Add(value); 31 | } 32 | } 33 | finally 34 | { 35 | wmiObject.Dispose(); 36 | } 37 | } 38 | } 39 | catch 40 | { 41 | // Ignore exceptions 42 | } 43 | 44 | values.Sort(); 45 | 46 | return values.Count > 0 47 | ? string.Join(",", values.ToArray()) 48 | : null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.WmiLight/Components/WmiLightMacAddressDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DeviceId.Components; 3 | using DeviceId.Internal; 4 | using WmiLight; 5 | 6 | namespace DeviceId.Windows.WmiLight.Components; 7 | 8 | /// 9 | /// An implementation of that uses the MAC Address of the PC. 10 | /// This improves upon the basic by using WMI 11 | /// to get better information from either MSFT_NetAdapter or Win32_NetworkAdapter. 12 | /// 13 | /// A value determining whether wireless devices should be excluded. 14 | /// A value determining whether non-physical devices should be excluded. 15 | public class WmiLightMacAddressDeviceIdComponent(bool excludeWireless, bool excludeNonPhysical) : IDeviceIdComponent 16 | { 17 | /// 18 | /// Gets the component value. 19 | /// 20 | /// The component value. 21 | public string GetValue() 22 | { 23 | // First, try to get a value using MSFT_NetAdapter: 24 | try 25 | { 26 | return GetValueUsingMsftNetAdapter(excludeWireless, excludeNonPhysical); 27 | } 28 | catch { } 29 | 30 | // Next, try using Win32_NetworkAdapter: 31 | try 32 | { 33 | return GetValueUsingWin32NetworkAdapter(excludeWireless, excludeNonPhysical); 34 | } 35 | catch { } 36 | 37 | // Finally, try the fallback component: 38 | var fallback = new MacAddressDeviceIdComponent(excludeWireless); 39 | return fallback.GetValue(); 40 | } 41 | 42 | /// 43 | /// Gets the component value using MSFT_NetAdapter. 44 | /// 45 | /// A value determining whether wireless devices should be excluded. 46 | /// A value determining whether non-physical devices should be excluded. 47 | /// The component value. 48 | private static string GetValueUsingMsftNetAdapter(bool excludeWireless, bool excludeNonPhysical) 49 | { 50 | var values = new List(); 51 | 52 | values.Sort(); 53 | 54 | using var wmiConnection = new WmiConnection(@"\\.\root\StandardCimv2"); 55 | 56 | foreach (var wmiObject in wmiConnection.CreateQuery("SELECT * FROM MSFT_NetAdapter")) 57 | { 58 | try 59 | { 60 | // Skip non-physical adapters if instructed to do so. 61 | if (wmiObject["ConnectorPresent"] is bool isPhysical) 62 | { 63 | if (excludeNonPhysical && !isPhysical) 64 | { 65 | continue; 66 | } 67 | } 68 | 69 | // Skip wireless adapters if instructed to do so. 70 | if (wmiObject["NdisPhysicalMedium"] is uint ndisPhysicalMedium) 71 | { 72 | if (excludeWireless && ndisPhysicalMedium == 9) // Native802_11 73 | { 74 | continue; 75 | } 76 | } 77 | if (wmiObject["PermanentAddress"] is string permanentAddress) 78 | { 79 | // Ensure the hardware addresses are formatted as MAC addresses if possible. 80 | // This is a discrepancy between the MSFT_NetAdapter and Win32_NetworkAdapter interfaces. 81 | values.Add(MacAddressFormatter.FormatMacAddress(permanentAddress)); 82 | } 83 | } 84 | finally 85 | { 86 | wmiObject.Dispose(); 87 | } 88 | } 89 | 90 | return values.Count > 0 91 | ? string.Join(",", values.ToArray()) 92 | : null; 93 | } 94 | 95 | /// 96 | /// Gets the component value using Win32_NetworkAdapter. 97 | /// 98 | /// A value determining whether wireless devices should be excluded. 99 | /// A value determining whether non-physical devices should be excluded. 100 | /// The component value. 101 | private static string GetValueUsingWin32NetworkAdapter(bool excludeWireless, bool excludeNonPhysical) 102 | { 103 | var values = new List(); 104 | 105 | using var wmiConnection = new WmiConnection(); 106 | var wmiQuery = wmiConnection.CreateQuery("SELECT MACAddress, AdapterTypeID, PhysicalAdapter FROM Win32_NetworkAdapter"); 107 | foreach (var managementObject in wmiQuery) 108 | { 109 | try 110 | { 111 | // Skip non-physical adapters if instructed to do so. 112 | if (managementObject["PhysicalAdapter"] is bool isPhysical) 113 | { 114 | if (excludeNonPhysical && !isPhysical) 115 | { 116 | continue; 117 | } 118 | } 119 | 120 | // Skip wireless adapters if instructed to do so. 121 | if (managementObject["AdapterTypeID"] is ushort adapterTypeId) 122 | { 123 | if (excludeWireless && adapterTypeId == 9) 124 | { 125 | continue; 126 | } 127 | } 128 | 129 | if (managementObject["MACAddress"] is string macAddress) 130 | { 131 | if (!string.IsNullOrEmpty(macAddress)) 132 | { 133 | values.Add(macAddress); 134 | } 135 | } 136 | } 137 | finally 138 | { 139 | managementObject.Dispose(); 140 | } 141 | } 142 | 143 | values.Sort(); 144 | 145 | return values.Count > 0 146 | ? string.Join(",", values.ToArray()) 147 | : null; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.WmiLight/Components/WmiLightSystemDriveSerialNumberDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WmiLight; 3 | 4 | namespace DeviceId.Windows.WmiLight.Components; 5 | 6 | /// 7 | /// An implementation of that uses the system drive's serial number. 8 | /// 9 | public class WmiLightSystemDriveSerialNumberDeviceIdComponent() : IDeviceIdComponent 10 | { 11 | /// 12 | /// Gets the component value. 13 | /// 14 | /// The component value. 15 | public string GetValue() 16 | { 17 | var systemDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System); 18 | var systemLogicalDiskDeviceId = systemDirectory.Substring(0, 2); 19 | 20 | // SystemDirectory can sometimes be null or empty. 21 | // See: https://github.com/dotnet/runtime/issues/21430 and https://github.com/MatthewKing/DeviceId/issues/64 22 | if (string.IsNullOrEmpty(systemDirectory) || systemDirectory.Length < 2) 23 | { 24 | return null; 25 | } 26 | 27 | try 28 | { 29 | using var wmiConnection = new WmiConnection(); 30 | 31 | foreach (var logicalDisk in wmiConnection.CreateQuery($"ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID=\"{systemLogicalDiskDeviceId}\"}} WHERE ResultClass = Win32_DiskPartition")) 32 | { 33 | try 34 | { 35 | if (logicalDisk.Class != "Win32_DiskPartition") continue; 36 | if (logicalDisk["DeviceId"] is not string diskPartitionDeviceId) continue; 37 | foreach (var diskPartitionAssociator in wmiConnection.CreateQuery( 38 | $"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID=\"{diskPartitionDeviceId}\"}}")) 39 | { 40 | if (diskPartitionAssociator.Class == "Win32_DiskDrive" 41 | && diskPartitionAssociator["SerialNumber"] is string diskDriveSerialNumber) 42 | { 43 | return diskDriveSerialNumber; 44 | } 45 | } 46 | } 47 | finally 48 | { 49 | logicalDisk.Dispose(); 50 | } 51 | } 52 | } 53 | catch 54 | { 55 | // Swallow exceptions. 56 | } 57 | 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.WmiLight/DeviceId.Windows.WmiLight.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DeviceId.Windows.WmiLight 5 | DeviceId (Windows / WmiLight) 6 | Provides extra Windows-specific components (using WmiLight) for the DeviceId package. 7 | 8 | 9 | 10 | netstandard2.0;net8.0;net9.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/DeviceId.Windows.WmiLight/WindowsDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using DeviceId.Windows.WmiLight.Components; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace DeviceId; 7 | 8 | /// 9 | /// Extension methods for . 10 | /// 11 | public static class WindowsDeviceIdBuilderExtensions 12 | { 13 | /// 14 | /// Adds the MAC address to the device identifier, optionally excluding wireless adapters and/or non-physical adapters. 15 | /// 16 | /// The to add the component to. 17 | /// A value indicating whether wireless adapters should be excluded. 18 | /// A value indicating whether non-physical adapters should be excluded. 19 | /// The instance. 20 | public static WindowsDeviceIdBuilder AddMacAddressFromWmi(this WindowsDeviceIdBuilder builder, bool excludeWireless, bool excludeNonPhysical) 21 | { 22 | return builder.AddComponent("MACAddress", new WmiLightMacAddressDeviceIdComponent(excludeWireless, excludeNonPhysical)); 23 | } 24 | 25 | /// 26 | /// Adds the processor ID to the device identifier. 27 | /// 28 | /// The to add the component to. 29 | /// The instance. 30 | public static WindowsDeviceIdBuilder AddProcessorId(this WindowsDeviceIdBuilder builder) 31 | { 32 | return builder.AddComponent("ProcessorId", new WmiLightDeviceIdComponent("Win32_Processor", "ProcessorId")); 33 | } 34 | 35 | /// 36 | /// Adds the motherboard serial number to the device identifier. 37 | /// 38 | /// The to add the component to. 39 | /// The instance. 40 | public static WindowsDeviceIdBuilder AddMotherboardSerialNumber(this WindowsDeviceIdBuilder builder) 41 | { 42 | return builder.AddComponent("MotherboardSerialNumber", new WmiLightDeviceIdComponent("Win32_BaseBoard", "SerialNumber")); 43 | } 44 | 45 | /// 46 | /// Adds the system UUID to the device identifier. 47 | /// 48 | /// The to add the component to. 49 | /// The instance. 50 | public static WindowsDeviceIdBuilder AddSystemUuid(this WindowsDeviceIdBuilder builder) 51 | { 52 | return builder.AddComponent("SystemUUID", new WmiLightDeviceIdComponent("Win32_ComputerSystemProduct", "UUID")); 53 | } 54 | 55 | /// 56 | /// Adds the system serial drive number to the device identifier. 57 | /// 58 | /// The to add the component to. 59 | /// The instance. 60 | [EditorBrowsable(EditorBrowsableState.Never)] 61 | [Obsolete("This method name was a typo. Use AddSystemDriveSerialNumber instead.")] 62 | public static WindowsDeviceIdBuilder AddSystemSerialDriveNumber(this WindowsDeviceIdBuilder builder) 63 | { 64 | return builder.AddComponent("SystemDriveSerialNumber", new WmiLightSystemDriveSerialNumberDeviceIdComponent()); 65 | } 66 | 67 | /// 68 | /// Adds the system serial drive number to the device identifier. 69 | /// 70 | /// The to add the component to. 71 | /// The instance. 72 | public static WindowsDeviceIdBuilder AddSystemDriveSerialNumber(this WindowsDeviceIdBuilder builder) 73 | { 74 | return builder.AddComponent("SystemDriveSerialNumber", new WmiLightSystemDriveSerialNumberDeviceIdComponent()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/DeviceId.Windows/Components/RegistryValueDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Win32; 5 | 6 | namespace DeviceId.Windows.Components; 7 | 8 | /// 9 | /// An implementation of that retrieves its value from the Windows registry. 10 | /// 11 | public class RegistryValueDeviceIdComponent : IDeviceIdComponent 12 | { 13 | #if !NET35 14 | /// 15 | /// The registry views. 16 | /// 17 | private readonly RegistryView[] _registryViews; 18 | 19 | /// 20 | /// The registry hive. 21 | /// 22 | private readonly RegistryHive _registryHive; 23 | #endif 24 | 25 | /// 26 | /// The name of the registry key. 27 | /// 28 | private readonly string _keyName; 29 | 30 | /// 31 | /// The name of the registry value. 32 | /// 33 | private readonly string _valueName; 34 | 35 | /// 36 | /// An optional function to use to format the value before returning it. 37 | /// 38 | private readonly Func _formatter; 39 | 40 | #if NET35 41 | /// 42 | /// Initializes a new instance of the class. 43 | /// 44 | /// The name of the registry key. 45 | /// The name of the registry value. 46 | public RegistryValueDeviceIdComponent(string keyName, string valueName) 47 | : this(keyName, valueName, null) { } 48 | 49 | /// 50 | /// Initializes a new instance of the class. 51 | /// 52 | /// The name of the registry key. 53 | /// The name of the registry value. 54 | /// An optional function to use to format the value before returning it. 55 | public RegistryValueDeviceIdComponent(string keyName, string valueName, Func formatter) 56 | { 57 | _keyName = keyName; 58 | _valueName = valueName; 59 | _formatter = formatter; 60 | } 61 | #else 62 | /// 63 | /// Initializes a new instance of the class. 64 | /// 65 | /// The registry view. 66 | /// The registry hive. 67 | /// The name of the registry key. 68 | /// The name of the registry value. 69 | public RegistryValueDeviceIdComponent(RegistryView registryView, RegistryHive registryHive, string keyName, string valueName) 70 | : this(registryView, registryHive, keyName, valueName, null) { } 71 | 72 | /// 73 | /// Initializes a new instance of the class. 74 | /// 75 | /// The registry view. 76 | /// The registry hive. 77 | /// The name of the registry key. 78 | /// The name of the registry value. 79 | /// An optional function to use to format the value before returning it. 80 | public RegistryValueDeviceIdComponent(RegistryView registryView, RegistryHive registryHive, string keyName, string valueName, Func formatter) 81 | : this(new RegistryView[] { registryView }, registryHive, keyName, valueName, formatter) { } 82 | 83 | /// 84 | /// Initializes a new instance of the class. 85 | /// 86 | /// The registry views. 87 | /// The registry hive. 88 | /// The name of the registry key. 89 | /// The name of the registry value. 90 | /// An optional function to use to format the value before returning it. 91 | public RegistryValueDeviceIdComponent(IEnumerable registryViews, RegistryHive registryHive, string keyName, string valueName, Func formatter) 92 | { 93 | _registryViews = registryViews.ToArray(); 94 | _registryHive = registryHive; 95 | _keyName = keyName; 96 | _valueName = valueName; 97 | _formatter = formatter; 98 | } 99 | #endif 100 | 101 | /// 102 | /// Gets the component value. 103 | /// 104 | /// The component value. 105 | public string GetValue() 106 | { 107 | #if NET35 108 | // In .NET 3.5, it's not possible to specify the registry view. 109 | // Technically I could write some native API calls to do it properly, 110 | // but we're going to drop support for .NET 3.5 soon anyway, so I don't really want to bother. 111 | 112 | try 113 | { 114 | var value = Registry.GetValue(_keyName, _valueName, null); 115 | var valueAsString = value?.ToString(); 116 | if (valueAsString is null) 117 | { 118 | return null; 119 | } 120 | 121 | return _formatter?.Invoke(valueAsString) ?? valueAsString; 122 | } 123 | catch { } 124 | #else 125 | foreach (var registryView in _registryViews) 126 | { 127 | try 128 | { 129 | using var registry = RegistryKey.OpenBaseKey(_registryHive, registryView); 130 | using var subKey = registry.OpenSubKey(_keyName); 131 | if (subKey != null) 132 | { 133 | var value = subKey.GetValue(_valueName); 134 | var valueAsString = value?.ToString(); 135 | if (valueAsString != null) 136 | { 137 | return _formatter?.Invoke(valueAsString) ?? valueAsString; 138 | } 139 | } 140 | } 141 | catch { } 142 | } 143 | #endif 144 | 145 | return null; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/DeviceId.Windows/Components/SystemIdDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | #if NET5_0_OR_GREATER && WINDOWS10_0_17763_0_OR_GREATER 2 | 3 | using System.Runtime.InteropServices.WindowsRuntime; 4 | using Windows.System.Profile; 5 | 6 | namespace DeviceId.Windows.Components; 7 | 8 | /// 9 | /// An implementation of that uses the SystemIdentification value. 10 | /// 11 | /// 12 | /// See: https://devblogs.microsoft.com/oldnewthing/20180131-00/?p=97945 13 | /// 14 | internal class SystemIdDeviceIdComponent : IDeviceIdComponent 15 | { 16 | /// 17 | /// The byte array encoder to use. 18 | /// 19 | private readonly IByteArrayEncoder _byteArrayEncoder; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The byte array encoder to use. 25 | public SystemIdDeviceIdComponent(IByteArrayEncoder byteArrayEncoder) 26 | { 27 | _byteArrayEncoder = byteArrayEncoder; 28 | } 29 | 30 | /// 31 | /// Gets the component value. 32 | /// 33 | /// The component value. 34 | public string GetValue() 35 | { 36 | var systemId = SystemIdentification.GetSystemIdForPublisher(); 37 | if (systemId == null) 38 | { 39 | return null; 40 | } 41 | 42 | var systemIdBytes = systemId.Id.ToArray(); 43 | return _byteArrayEncoder.Encode(systemIdBytes); 44 | } 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/DeviceId.Windows/DeviceId.Windows.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DeviceId.Windows 5 | DeviceId (Windows) 6 | Provides Windows-specific components for the DeviceId package. 7 | 8 | 9 | 10 | net35;net40;netstandard2.0;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0 11 | latest 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/DeviceId.Windows/DeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Internal; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class DeviceIdBuilderExtensions 10 | { 11 | /// 12 | /// Adds Windows-specific components to the device ID. 13 | /// 14 | /// The device ID builder to add the components to. 15 | /// An action that adds the Windows-specific components. 16 | /// The device ID builder. 17 | public static DeviceIdBuilder OnWindows(this DeviceIdBuilder builder, Action windowsBuilderConfiguration) 18 | { 19 | if (OS.IsWindows && windowsBuilderConfiguration is not null) 20 | { 21 | var windowsBuilder = new WindowsDeviceIdBuilder(builder); 22 | windowsBuilderConfiguration.Invoke(windowsBuilder); 23 | } 24 | 25 | return builder; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/DeviceId.Windows/WindowsDeviceIdBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Internal; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Provides a fluent interface for adding Windows-specific components to a device identifier. 8 | /// 9 | public class WindowsDeviceIdBuilder 10 | { 11 | /// 12 | /// The base device identifier builder. 13 | /// 14 | private readonly DeviceIdBuilder _baseBuilder; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The base device identifier builder. 20 | public WindowsDeviceIdBuilder(DeviceIdBuilder baseBuilder) 21 | { 22 | _baseBuilder = baseBuilder ?? throw new ArgumentNullException(nameof(baseBuilder)); 23 | } 24 | 25 | /// 26 | /// Adds a component to the device identifier. 27 | /// If a component with the specified name already exists, it will be replaced with this newly added component. 28 | /// 29 | /// The component name. 30 | /// The component to add. 31 | /// The builder instance. 32 | public WindowsDeviceIdBuilder AddComponent(string name, IDeviceIdComponent component) 33 | { 34 | if (OS.IsWindows) 35 | { 36 | _baseBuilder.AddComponent(name, component); 37 | } 38 | 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DeviceId.Windows/WindowsDeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NET5_0_OR_GREATER && WINDOWS10_0_17763_0_OR_GREATER 2 | using DeviceId.Internal; 3 | #endif 4 | using DeviceId.Windows.Components; 5 | #if !NET35 6 | using Microsoft.Win32; 7 | #endif 8 | 9 | namespace DeviceId; 10 | 11 | /// 12 | /// Extension methods for . 13 | /// 14 | public static class WindowsDeviceIdBuilderExtensions 15 | { 16 | #if !NET35 17 | private static RegistryView[] Both32BitAnd64BitRegistryViews { get; } = new[] { RegistryView.Registry32, RegistryView.Registry64 }; 18 | #endif 19 | 20 | #if NET35 21 | /// 22 | /// Adds a registry value to the device identifier. 23 | /// 24 | /// The to add the component to. 25 | /// The name of the component. 26 | /// The full path of the registry key. 27 | /// The name of the registry value. 28 | /// The instance. 29 | public static WindowsDeviceIdBuilder AddRegistryValue(this WindowsDeviceIdBuilder builder, string componentName, string registryKeyName, string registryValueName) 30 | { 31 | return builder.AddComponent(componentName, new RegistryValueDeviceIdComponent(registryKeyName, registryValueName)); 32 | } 33 | #else 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | /// The to add the component to. 38 | /// The name of the component. 39 | /// The registry view. 40 | /// The registry hive. 41 | /// The name of the registry key. 42 | /// The name of the registry value. 43 | public static WindowsDeviceIdBuilder AddRegistryValue(this WindowsDeviceIdBuilder builder, string componentName, RegistryView registryView, RegistryHive registryHive, string registryKeyName, string registryValueName) 44 | { 45 | return builder.AddComponent(componentName, new RegistryValueDeviceIdComponent(registryView, registryHive, registryKeyName, registryValueName)); 46 | } 47 | #endif 48 | 49 | /// 50 | /// Adds the Windows Device ID (also known as Machine ID or Advertising ID) to the device identifier. 51 | /// This value is the one displayed as "Device ID" in the Windows Device Specifications UI. 52 | /// 53 | /// The to add the component to. 54 | /// The instance. 55 | public static WindowsDeviceIdBuilder AddWindowsDeviceId(this WindowsDeviceIdBuilder builder) 56 | { 57 | #if NET35 58 | var component = new RegistryValueDeviceIdComponent( 59 | keyName: @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SQMClient", 60 | valueName: "MachineId", 61 | formatter: value => value.TrimStart('{').TrimEnd('}')); 62 | #else 63 | 64 | var component = new RegistryValueDeviceIdComponent( 65 | registryViews: Both32BitAnd64BitRegistryViews, 66 | registryHive: RegistryHive.LocalMachine, 67 | keyName: @"SOFTWARE\Microsoft\SQMClient", 68 | valueName: "MachineId", 69 | formatter: value => value.TrimStart('{').TrimEnd('}')); 70 | #endif 71 | 72 | return builder.AddComponent("WindowsDeviceId", component); 73 | } 74 | 75 | /// 76 | /// Adds the Windows Product ID to the device identifier. 77 | /// This value is the one displayed as "Product ID" in the Windows Device Specifications UI. 78 | /// 79 | /// The to add the component to. 80 | /// The instance. 81 | public static WindowsDeviceIdBuilder AddWindowsProductId(this WindowsDeviceIdBuilder builder) 82 | { 83 | #if NET35 84 | var component = new RegistryValueDeviceIdComponent( 85 | keyName: @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", 86 | valueName: "ProductId"); 87 | #else 88 | var component = new RegistryValueDeviceIdComponent( 89 | registryViews: Both32BitAnd64BitRegistryViews, 90 | registryHive: RegistryHive.LocalMachine, 91 | keyName: @"SOFTWARE\Microsoft\Windows NT\CurrentVersion", 92 | valueName: "ProductId", 93 | formatter: null); 94 | #endif 95 | 96 | return builder.AddComponent("WindowsProductId", component); 97 | } 98 | 99 | /// 100 | /// Adds the Machine GUID (from HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid) to the device identifier. 101 | /// 102 | /// The to add the component to. 103 | /// The instance. 104 | public static WindowsDeviceIdBuilder AddMachineGuid(this WindowsDeviceIdBuilder builder) 105 | { 106 | #if NET35 107 | var component = new RegistryValueDeviceIdComponent( 108 | keyName: @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography", 109 | valueName: "MachineGuid"); 110 | #else 111 | var component = new RegistryValueDeviceIdComponent( 112 | registryViews: Both32BitAnd64BitRegistryViews, 113 | registryHive: RegistryHive.LocalMachine, 114 | keyName: @"SOFTWARE\Microsoft\Cryptography", 115 | valueName: "MachineGuid", 116 | formatter: null); 117 | #endif 118 | 119 | return builder.AddComponent("MachineGuid", component); 120 | } 121 | 122 | #if NET5_0_OR_GREATER && WINDOWS10_0_17763_0_OR_GREATER 123 | /// 124 | /// Adds the System ID to the device identifier. 125 | /// 126 | /// The to add the component to. 127 | /// The instance. 128 | public static WindowsDeviceIdBuilder AddSystemId(this WindowsDeviceIdBuilder builder) 129 | { 130 | return builder.AddComponent("SystemId", new SystemIdDeviceIdComponent(ByteArrayEncoders.Base32Crockford)); 131 | } 132 | #endif 133 | } 134 | -------------------------------------------------------------------------------- /src/DeviceId.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewKing/DeviceId/1b9f03b12c859980fd802976d938b006e62c95e6/src/DeviceId.snk -------------------------------------------------------------------------------- /src/DeviceId/CommandExecutors/BashCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId.CommandExecutors; 2 | 3 | /// 4 | /// An implementation of that uses /bin/bash to execute commands. 5 | /// 6 | public class BashCommandExecutor : CommandExecutorBase 7 | { 8 | /// 9 | /// Executes the specified command. 10 | /// 11 | /// The command to execute. 12 | /// The command output. 13 | public override string Execute(string command) 14 | { 15 | return RunWithShell("/bin/bash", $"-c \"{command.Replace("\"", "\\\"")}\"").Trim('\r').Trim('\n').TrimEnd().TrimStart(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DeviceId/CommandExecutors/CommandExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId.CommandExecutors; 2 | 3 | /// 4 | /// Enumerate the various command executors that are available. 5 | /// 6 | public static class CommandExecutor 7 | { 8 | /// 9 | /// Gets a command executor that uses /bin/bash to execute commands. 10 | /// 11 | public static ICommandExecutor Bash { get; } = new BashCommandExecutor(); 12 | 13 | /// 14 | /// Gets a command executor that uses /bin/sh to execute commands. 15 | /// 16 | public static ICommandExecutor Sh { get; } = new ShCommandExecutor(); 17 | } 18 | -------------------------------------------------------------------------------- /src/DeviceId/CommandExecutors/CommandExecutorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace DeviceId.CommandExecutors; 4 | 5 | /// 6 | /// A base implementation of . 7 | /// 8 | public abstract class CommandExecutorBase : ICommandExecutor 9 | { 10 | /// 11 | /// Executes the specified command. 12 | /// 13 | /// The command to execute. 14 | /// The command output. 15 | public abstract string Execute(string command); 16 | 17 | /// 18 | /// Runs the specified command with the specified shell. 19 | /// 20 | /// The shell to use. 21 | /// The command to run. 22 | /// The output. 23 | protected string RunWithShell(string shell, string command) 24 | { 25 | var psi = new ProcessStartInfo(); 26 | psi.FileName = shell; 27 | psi.Arguments = command; 28 | psi.RedirectStandardOutput = true; 29 | psi.UseShellExecute = false; 30 | psi.CreateNoWindow = true; 31 | 32 | using var process = Process.Start(psi); 33 | 34 | process?.WaitForExit(); 35 | 36 | var output = process?.StandardOutput.ReadToEnd(); 37 | 38 | return output; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DeviceId/CommandExecutors/ICommandExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId.CommandExecutors; 2 | 3 | /// 4 | /// Provides functionality to execute a command. 5 | /// 6 | public interface ICommandExecutor 7 | { 8 | /// 9 | /// Executes the specified command. 10 | /// 11 | /// The command to execute. 12 | /// The command output. 13 | string Execute(string command); 14 | } 15 | -------------------------------------------------------------------------------- /src/DeviceId/CommandExecutors/ShCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId.CommandExecutors; 2 | 3 | /// 4 | /// An implementation of that uses /bin/sh to execute commands. 5 | /// 6 | public class ShCommandExecutor : CommandExecutorBase 7 | { 8 | /// 9 | /// Executes the specified command. 10 | /// 11 | /// The command to execute. 12 | /// The command output. 13 | public override string Execute(string command) 14 | { 15 | return RunWithShell("/bin/sh", $"-c \"{command.Replace("\"", "\\\"")}\"").Trim('\r').Trim('\n').TrimEnd().TrimStart(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DeviceId/Components/CommandComponent.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.CommandExecutors; 2 | 3 | namespace DeviceId.Components; 4 | 5 | /// 6 | /// An implementation of that executes a command. 7 | /// 8 | public class CommandComponent : IDeviceIdComponent 9 | { 10 | /// 11 | /// The command executed by the component. 12 | /// 13 | private readonly string _command; 14 | 15 | /// 16 | /// The command executor to use. 17 | /// 18 | private readonly ICommandExecutor _commandExecutor; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The command executed by the component. 24 | /// The command executor. 25 | public CommandComponent(string command, ICommandExecutor commandExecutor) 26 | { 27 | _command = command; 28 | _commandExecutor = commandExecutor; 29 | } 30 | 31 | /// 32 | /// Gets the component value. 33 | /// 34 | /// The component value. 35 | public string GetValue() 36 | { 37 | return _commandExecutor.Execute(_command); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/DeviceId/Components/DatabaseQueryDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | namespace DeviceId.Components; 5 | 6 | /// 7 | /// An implementation of that gets its value from the result of a database command. 8 | /// 9 | public class DatabaseQueryDeviceIdComponent : IDeviceIdComponent 10 | { 11 | /// 12 | /// A factory used to get a connection to the database. 13 | /// 14 | private readonly Func _connectionFactory; 15 | 16 | /// 17 | /// SQL query that returns a single value to be added to the device identifier. 18 | /// 19 | private readonly string _sql; 20 | 21 | /// 22 | /// A function that transforms the result of the query into a string. 23 | /// 24 | private readonly Func _valueTransformer; 25 | 26 | /// 27 | /// A value determining whether the connection should be disposed after use or not. 28 | /// 29 | private readonly bool _disposeConnection; 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | /// A factory used to get a connection to the database. 35 | /// SQL query that returns a single value to be added to the device identifier. 36 | /// A function that transforms the result of the query into a string. 37 | public DatabaseQueryDeviceIdComponent(Func connectionFactory, string sql, Func valueTransformer) 38 | : this(connectionFactory, sql, valueTransformer, true) { } 39 | 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | /// A factory used to get a connection to the database. 44 | /// SQL query that returns a single value to be added to the device identifier. 45 | /// A function that transforms the result of the query into a string. 46 | /// A value determining whether the connection should be disposed after use or not. Default is true. 47 | public DatabaseQueryDeviceIdComponent(Func connectionFactory, string sql, Func valueTransformer, bool disposeConnection) 48 | { 49 | _connectionFactory = connectionFactory; 50 | _sql = sql; 51 | _valueTransformer = valueTransformer; 52 | _disposeConnection = disposeConnection; 53 | } 54 | 55 | /// 56 | /// Gets the component value. 57 | /// 58 | /// The component value. 59 | public string GetValue() 60 | { 61 | try 62 | { 63 | var connection = _connectionFactory.Invoke(); 64 | try 65 | { 66 | connection.Open(); 67 | 68 | using var command = connection.CreateCommand(); 69 | command.CommandText = _sql; 70 | 71 | var result = command.ExecuteScalar(); 72 | if (result != null) 73 | { 74 | var value = _valueTransformer?.Invoke(result); 75 | return value; 76 | } 77 | } 78 | finally 79 | { 80 | if (_disposeConnection && connection is not null) 81 | { 82 | connection.Dispose(); 83 | } 84 | } 85 | 86 | return null; 87 | } 88 | catch 89 | { 90 | return null; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/DeviceId/Components/DeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeviceId.Components; 4 | 5 | /// 6 | /// An implementation of that uses either a specified value 7 | /// or the result of a specified function as its component value. 8 | /// 9 | public class DeviceIdComponent : IDeviceIdComponent 10 | { 11 | /// 12 | /// A function that returns the component value. 13 | /// 14 | private readonly Func _valueFactory; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The component value. 20 | public DeviceIdComponent(string value) 21 | : this(() => value) { } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// A function that returns the component value. 27 | public DeviceIdComponent(Func valueFactory) 28 | { 29 | _valueFactory = valueFactory; 30 | } 31 | 32 | /// 33 | /// Gets the component value. 34 | /// 35 | /// The component value. 36 | public string GetValue() 37 | { 38 | return _valueFactory.Invoke(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DeviceId/Components/FileContentsDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | 8 | namespace DeviceId.Components; 9 | 10 | /// 11 | /// An implementation of that retrieves its value from a file. 12 | /// 13 | public class FileContentsDeviceIdComponent : IDeviceIdComponent 14 | { 15 | /// 16 | /// The paths to read. 17 | /// 18 | private readonly string[] _paths; 19 | 20 | /// 21 | /// Should the contents of the file be hashed? 22 | /// 23 | private readonly bool _hashContents; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The path of the file holding the component ID. 29 | /// A value determining whether the file contents should be hashed. 30 | public FileContentsDeviceIdComponent(string path, bool hashContents = false) 31 | : this(new[] { path }, hashContents) { } 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | /// The paths to read. The first path that can be successfully read will be used. 37 | /// A value determining whether the file contents should be hashed. 38 | public FileContentsDeviceIdComponent(IEnumerable paths, bool hashContents = false) 39 | { 40 | _paths = paths.ToArray(); 41 | _hashContents = hashContents; 42 | } 43 | 44 | /// 45 | /// Gets the component value. 46 | /// 47 | /// The component value. 48 | public string GetValue() 49 | { 50 | foreach (var path in _paths) 51 | { 52 | if (!File.Exists(path)) 53 | { 54 | continue; 55 | } 56 | 57 | try 58 | { 59 | string contents; 60 | 61 | using (var file = File.OpenText(path)) 62 | { 63 | contents = file.ReadToEnd(); // File.ReadAllBytes() fails for special files such as /sys/class/dmi/id/product_uuid 64 | } 65 | 66 | contents = contents.Trim(); 67 | 68 | if (!_hashContents) 69 | { 70 | return contents; 71 | } 72 | 73 | using var hasher = MD5.Create(); 74 | var hash = hasher.ComputeHash(Encoding.ASCII.GetBytes(contents)); 75 | return BitConverter.ToString(hash).Replace("-", "").ToUpper(); 76 | } 77 | catch 78 | { 79 | // Can fail if we have no permissions to access the file. 80 | } 81 | } 82 | 83 | return null; 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/DeviceId/Components/FileTokenDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace DeviceId.Components; 6 | 7 | /// 8 | /// An implementation of that retrieves its value from a file. 9 | /// 10 | /// 11 | /// If the file exists, the contents of that file will be used as the component value. 12 | /// If the file does not exist, a new file will be created and populated with a new GUID, 13 | /// which will be used as the component value. 14 | /// 15 | public class FileTokenDeviceIdComponent : IDeviceIdComponent 16 | { 17 | /// 18 | /// The path where the token will be stored. 19 | /// 20 | private readonly string _path; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The path where the component will be stored. 26 | public FileTokenDeviceIdComponent(string path) 27 | { 28 | _path = path; 29 | } 30 | 31 | /// 32 | /// Gets the component value. 33 | /// 34 | /// The component value. 35 | public string GetValue() 36 | { 37 | if (File.Exists(_path)) 38 | { 39 | try 40 | { 41 | var bytes = File.ReadAllBytes(_path); 42 | var value = Encoding.ASCII.GetString(bytes); 43 | return value; 44 | } 45 | catch { } 46 | } 47 | else 48 | { 49 | try 50 | { 51 | var value = Guid.NewGuid().ToString().ToUpper(); 52 | var bytes = Encoding.ASCII.GetBytes(value); 53 | File.WriteAllBytes(_path, bytes); 54 | return value; 55 | } 56 | catch { } 57 | } 58 | 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/DeviceId/Components/MacAddressDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Net.NetworkInformation; 3 | using DeviceId.Internal; 4 | 5 | namespace DeviceId.Components; 6 | 7 | /// 8 | /// An implementation of that uses the MAC Address of the PC. 9 | /// 10 | public class MacAddressDeviceIdComponent : IDeviceIdComponent 11 | { 12 | private const string _dockerBridgeInterfaceName = "docker0"; 13 | 14 | /// 15 | /// A value determining whether wireless devices should be excluded. 16 | /// 17 | private readonly bool _excludeWireless; 18 | 19 | /// 20 | /// A value determining whether docker bridge should be excluded. 21 | /// 22 | private readonly bool _excludeDockerBridge; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public MacAddressDeviceIdComponent() 28 | : this(false, false) { } 29 | 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// A value determining whether wireless devices should be excluded. 34 | public MacAddressDeviceIdComponent(bool excludeWireless) 35 | : this(excludeWireless, false) { } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// A value determining whether wireless devices should be excluded. 41 | /// A value determining whether docker bridge should be excluded. 42 | public MacAddressDeviceIdComponent(bool excludeWireless, bool excludeDockerBridge) 43 | { 44 | _excludeWireless = excludeWireless; 45 | _excludeDockerBridge = excludeDockerBridge; 46 | } 47 | 48 | /// 49 | /// Gets the component value. 50 | /// 51 | /// The component value. 52 | public string GetValue() 53 | { 54 | var values = NetworkInterface.GetAllNetworkInterfaces() 55 | .Where(x => (!_excludeWireless || x.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) && (!_excludeDockerBridge || x.Name != _dockerBridgeInterfaceName)) 56 | .Select(x => x.GetPhysicalAddress().ToString()) 57 | .Where(x => x != "000000000000") 58 | .Select(x => MacAddressFormatter.FormatMacAddress(x)) 59 | .ToList(); 60 | 61 | values.Sort(); 62 | 63 | return values.Count > 0 64 | ? string.Join(",", values.ToArray()) 65 | : null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/DeviceId/DeviceId.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DeviceId 5 | DeviceId 6 | Provides functionality to generate a 'device ID' that can be used to uniquely identify a computer. 7 | Please note that as of v6.0.0, some functionality that was previously in the main DeviceId package has been moved to the platform-specific packages: DeviceId.Windows, DeviceId.Windows.Wmi, DeviceId.Windows.Mmi, DeviceId.Linux, DeviceId.Mac 8 | 9 | 10 | 11 | net35;net40;netstandard2.0;net8.0;net9.0 12 | latest 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/DeviceId/DeviceIdBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DeviceId; 5 | 6 | /// 7 | /// Provides a fluent interface for constructing unique device identifiers. 8 | /// 9 | public class DeviceIdBuilder 10 | { 11 | /// 12 | /// Gets or sets the formatter to use. 13 | /// 14 | public IDeviceIdFormatter Formatter { get; set; } 15 | 16 | /// 17 | /// A dictionary containing the components that will make up the device identifier. 18 | /// 19 | public IDictionary Components { get; } 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public DeviceIdBuilder() 25 | { 26 | Formatter = DeviceIdFormatters.DefaultV6; 27 | Components = new Dictionary(StringComparer.OrdinalIgnoreCase); 28 | } 29 | 30 | /// 31 | /// Adds a component to the device identifier. 32 | /// If a component with the specified name already exists, it will be replaced with this newly added component. 33 | /// 34 | /// The component name. 35 | /// The component to add. 36 | /// The builder instance. 37 | public DeviceIdBuilder AddComponent(string name, IDeviceIdComponent component) 38 | { 39 | Components[name] = component; 40 | return this; 41 | } 42 | 43 | /// 44 | /// Returns a string representation of the device identifier. 45 | /// 46 | /// A string representation of the device identifier. 47 | public override string ToString() 48 | { 49 | if (Formatter == null) 50 | { 51 | throw new InvalidOperationException($"The {nameof(Formatter)} property must not be null in order for {nameof(ToString)} to be called."); 52 | } 53 | 54 | return Formatter.GetDeviceId(Components); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/DeviceId/DeviceIdBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Components; 3 | using DeviceId.Internal; 4 | 5 | namespace DeviceId; 6 | 7 | /// 8 | /// Extension methods for . 9 | /// 10 | public static class DeviceIdBuilderExtensions 11 | { 12 | /// 13 | /// Use the specified formatter. 14 | /// 15 | /// The to use the formatter. 16 | /// The to use. 17 | /// The instance. 18 | public static DeviceIdBuilder UseFormatter(this DeviceIdBuilder builder, IDeviceIdFormatter formatter) 19 | { 20 | builder.Formatter = formatter; 21 | return builder; 22 | } 23 | 24 | /// 25 | /// Adds the current user name to the device identifier. 26 | /// 27 | /// The to add the component to. 28 | /// The instance. 29 | public static DeviceIdBuilder AddUserName(this DeviceIdBuilder builder) 30 | { 31 | // Default to false for backwards compatibility. May consider changing this to true in the next major version. 32 | 33 | return AddUserName(builder, false); 34 | } 35 | 36 | /// 37 | /// Adds the current user name to the device identifier. 38 | /// 39 | /// The to add the component to. 40 | /// A value determining whether the user name should be normalized or not. 41 | /// The instance. 42 | public static DeviceIdBuilder AddUserName(this DeviceIdBuilder builder, bool normalize) 43 | { 44 | var userName = normalize 45 | ? Environment.UserName?.ToLowerInvariant() 46 | : Environment.UserName; 47 | 48 | return builder.AddComponent("UserName", new DeviceIdComponent(userName)); 49 | } 50 | 51 | /// 52 | /// Adds the machine name to the device identifier. 53 | /// 54 | /// The to add the component to. 55 | /// The instance. 56 | public static DeviceIdBuilder AddMachineName(this DeviceIdBuilder builder) 57 | { 58 | return builder.AddComponent("MachineName", new DeviceIdComponent(Environment.MachineName)); 59 | } 60 | 61 | /// 62 | /// Adds the operating system version to the device identifier. 63 | /// 64 | /// The to add the component to. 65 | /// The instance. 66 | public static DeviceIdBuilder AddOsVersion(this DeviceIdBuilder builder) 67 | { 68 | return builder.AddComponent("OSVersion", new DeviceIdComponent(OS.Version)); 69 | } 70 | 71 | /// 72 | /// Adds the MAC address to the device identifier, optionally excluding wireless adapters. 73 | /// 74 | /// The to add the component to. 75 | /// A value indicating whether wireless adapters should be excluded. 76 | /// A value determining whether docker bridge should be excluded. 77 | /// The instance. 78 | public static DeviceIdBuilder AddMacAddress(this DeviceIdBuilder builder, bool excludeWireless = false, bool excludeDockerBridge = false) 79 | { 80 | return builder.AddComponent("MACAddress", new MacAddressDeviceIdComponent(excludeWireless, excludeDockerBridge)); 81 | } 82 | 83 | /// 84 | /// Adds a file-based token to the device identifier. 85 | /// 86 | /// The to add the component to. 87 | /// The path of the token. 88 | /// The instance. 89 | public static DeviceIdBuilder AddFileToken(this DeviceIdBuilder builder, string path) 90 | { 91 | var name = string.Concat("FileToken", path.GetHashCode()); 92 | return builder.AddComponent(name, new FileTokenDeviceIdComponent(path)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/DeviceId/DeviceIdFormatters.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.Encoders; 2 | using DeviceId.Formatters; 3 | using DeviceId.Internal; 4 | 5 | namespace DeviceId; 6 | 7 | /// 8 | /// Provides access to some of the default formatters. 9 | /// 10 | public static class DeviceIdFormatters 11 | { 12 | /// 13 | /// Returns the default formatter used in version 5 of the DeviceId library. 14 | /// 15 | public static IDeviceIdFormatter DefaultV5 { get; } = new HashDeviceIdFormatter(ByteArrayHashers.Sha256, new Base64UrlByteArrayEncoder()); 16 | 17 | /// 18 | /// Returns the default formatter used in version 6 of the DeviceId library. 19 | /// 20 | public static IDeviceIdFormatter DefaultV6 { get; } = new HashDeviceIdFormatter(ByteArrayHashers.Sha256, ByteArrayEncoders.Base32Crockford); 21 | } 22 | -------------------------------------------------------------------------------- /src/DeviceId/DeviceIdManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace DeviceId; 6 | 7 | /// 8 | /// Provides functionality to manage the generation and validation of multiple device identifier formats. 9 | /// 10 | public class DeviceIdManager 11 | { 12 | /// 13 | /// A dictionary mapping the version numbers to the device ID builders. 14 | /// 15 | private readonly Dictionary _builders; 16 | 17 | /// 18 | /// The version encoder. 19 | /// 20 | private IDeviceIdVersionEncoder _versionEncoder; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | public DeviceIdManager() 26 | { 27 | _builders = new Dictionary(); 28 | _versionEncoder = new DeviceIdVersionEncoder(); 29 | } 30 | 31 | /// 32 | /// Sets the version encoder to use. 33 | /// 34 | /// The version encoder. 35 | /// This instance. 36 | public DeviceIdManager WithVersionEncoder(IDeviceIdVersionEncoder encoder) 37 | { 38 | _versionEncoder = encoder; 39 | 40 | return this; 41 | } 42 | 43 | /// 44 | /// Adds a device identifier builder with the specified version number. 45 | /// 46 | /// The version number. 47 | /// The device identifier builder. 48 | /// This instance. 49 | public DeviceIdManager AddBuilder(int version, DeviceIdBuilder builder) 50 | { 51 | _builders[version] = builder; 52 | 53 | return this; 54 | } 55 | 56 | /// 57 | /// Adds a device identifier builder with the specified version number. 58 | /// 59 | /// The version number. 60 | /// The device identifier builder configuration. 61 | /// This instance. 62 | public DeviceIdManager AddBuilder(int version, Action builderConfiguration) 63 | { 64 | var builder = new DeviceIdBuilder(); 65 | builderConfiguration?.Invoke(builder); 66 | 67 | return AddBuilder(version, builder); 68 | } 69 | 70 | /// 71 | /// Gets the current builder version. 72 | /// 73 | /// The current builder version. 74 | public int GetCurrentVersion() 75 | { 76 | var version = _builders.Keys.Max(); // Always use the latest version. 77 | return version; 78 | } 79 | 80 | /// 81 | /// Gets the device identifier from the builder with the highest version number. 82 | /// 83 | /// A device identifier. 84 | public string GetDeviceId() 85 | { 86 | if (_builders.Count > 0) 87 | { 88 | var version = GetCurrentVersion(); 89 | return GetDeviceId(version); 90 | } 91 | 92 | return null; 93 | } 94 | 95 | /// 96 | /// Gets the device identifier from the builder with the specified version number. 97 | /// 98 | /// The version number. 99 | /// A device identifier. 100 | public string GetDeviceId(int version) 101 | { 102 | if (_builders.TryGetValue(version, out var builder)) 103 | { 104 | var deviceId = builder.ToString(); 105 | return _versionEncoder.Encode(deviceId, version); 106 | } 107 | 108 | return null; 109 | } 110 | 111 | /// 112 | /// Returns a value indicating whether the specified value is a valid identifier for this device. 113 | /// 114 | /// The value to validate. 115 | /// true if the value is a valid identifier for this device; otherwise, false. 116 | public bool Validate(string value) 117 | { 118 | // If no builders, nothing is valid. 119 | if (_builders.Count == 0) 120 | { 121 | return false; 122 | } 123 | 124 | // If we can decode the version/deviceId, then we just need to generate the current device identifier 125 | // with the specified builder, and test against that. 126 | if (_versionEncoder.TryDecode(value, out var versionedDeviceId, out var version)) 127 | { 128 | if (_builders.TryGetValue(version, out var builder)) 129 | { 130 | return string.Equals(builder.ToString(), versionedDeviceId); 131 | } 132 | } 133 | 134 | // If we couldn't decode the version/deviceId, there is still a chance! 135 | // We can treat the entire input as a raw unversioned device ID (perhaps generated before the 136 | // dev decided to use this class). We then assume that the FIRST builder known to us is the 137 | // same builder that generated the aforementioned raw unversioned device ID. This gives us a 138 | // bit of backwards compatibility. 139 | 140 | var firstVersion = _builders.Keys.Min(); 141 | var firstBuilder = _builders[firstVersion]; 142 | return string.Equals(firstBuilder.ToString(), value); 143 | } 144 | 145 | /// 146 | /// Returns a string representation of the device identifier generated from the builder with the highest version number. 147 | /// 148 | /// A device identifier. 149 | public override string ToString() 150 | { 151 | return GetDeviceId(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/DeviceId/DeviceIdVersionEncoder.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace DeviceId; 4 | 5 | /// 6 | /// The default implementation of . 7 | /// 8 | public class DeviceIdVersionEncoder : IDeviceIdVersionEncoder 9 | { 10 | /// 11 | /// Encodes a device identifier and a version number into a string. 12 | /// 13 | /// The device identifier. 14 | /// The version number. 15 | /// An encoded string comprising a device identifier and a version number. 16 | public string Encode(string deviceId, int version) 17 | { 18 | return $"${version}${deviceId}"; 19 | } 20 | 21 | /// 22 | /// Attempts to decode a string into a device identifier and a version number. 23 | /// 24 | /// The string to decode. 25 | /// If successful, the device identifier; otherwise, null. 26 | /// If successful, the version number; otherwise, 0. 27 | /// true if the string was decoded successfully; otherwise, false. 28 | public bool TryDecode(string value, out string deviceId, out int version) 29 | { 30 | var match = Regex.Match(value, "^\\$(.*?)\\$(.*?)$"); 31 | if (match.Success) 32 | { 33 | if (int.TryParse(match.Groups[1].Value, out var parsedVersion)) 34 | { 35 | version = parsedVersion; 36 | deviceId = match.Groups[2].Value; 37 | 38 | return true; 39 | } 40 | } 41 | 42 | version = default; 43 | deviceId = default; 44 | return false; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/Base32ByteArrayEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace DeviceId.Encoders; 5 | 6 | /// 7 | /// An implementation of that encodes byte arrays as Base32 strings. 8 | /// 9 | public class Base32ByteArrayEncoder : IByteArrayEncoder 10 | { 11 | /// 12 | /// Gets the RFC-4648 Base32 alphabet. 13 | /// 14 | /// 15 | /// See https://datatracker.ietf.org/doc/html/rfc4648#section-6 16 | /// 17 | public static string Rfc4648Alphabet { get; } = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 18 | 19 | /// 20 | /// Gets the Crockford Base32 alphabet. 21 | /// 22 | /// 23 | /// See https://www.crockford.com/base32.html 24 | /// 25 | public static string CrockfordAlphabet { get; } = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; 26 | 27 | /// 28 | /// Gets the alphabet in use. 29 | /// 30 | private readonly string _alphabet; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | /// The alphabet to use. 36 | public Base32ByteArrayEncoder(string alphabet) 37 | { 38 | if (alphabet is null) 39 | { 40 | throw new ArgumentNullException(nameof(alphabet)); 41 | } 42 | 43 | if (alphabet.Length != 32) 44 | { 45 | throw new ArgumentException("The alphabet must have a length of 32.", nameof(alphabet)); 46 | } 47 | 48 | _alphabet = alphabet; 49 | } 50 | 51 | /// 52 | /// Encodes the specified byte array as a string. 53 | /// 54 | /// The byte array to encode. 55 | /// The byte array encoded as a string. 56 | public string Encode(byte[] bytes) 57 | { 58 | if (bytes == null) 59 | { 60 | throw new ArgumentNullException(nameof(bytes)); 61 | } 62 | 63 | if (bytes.Length == 0) 64 | { 65 | return string.Empty; 66 | } 67 | 68 | const int shift = 5; 69 | const int mask = 31; 70 | 71 | var outputLength = (bytes.Length * 8 + shift - 1) / shift; 72 | var sb = new StringBuilder(outputLength); 73 | 74 | var offset = 0; 75 | var last = bytes.Length; 76 | int buffer = bytes[offset++]; 77 | var bitsLeft = 8; 78 | while (bitsLeft > 0 || offset < last) 79 | { 80 | if (bitsLeft < shift) 81 | { 82 | if (offset < last) 83 | { 84 | buffer <<= 8; 85 | buffer |= bytes[offset++] & 0xff; 86 | bitsLeft += 8; 87 | } 88 | else 89 | { 90 | var pad = shift - bitsLeft; 91 | buffer <<= pad; 92 | bitsLeft += pad; 93 | } 94 | } 95 | 96 | var index = mask & (buffer >> (bitsLeft - shift)); 97 | bitsLeft -= shift; 98 | sb.Append(_alphabet[index]); 99 | } 100 | 101 | return sb.ToString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/Base64ByteArrayEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeviceId.Encoders; 4 | 5 | /// 6 | /// An implementation of that encodes byte arrays as Base64 strings. 7 | /// 8 | public class Base64ByteArrayEncoder : IByteArrayEncoder 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public Base64ByteArrayEncoder() { } 14 | 15 | /// 16 | /// Encodes the specified byte array as a string. 17 | /// 18 | /// The byte array to encode. 19 | /// The byte array encoded as a string. 20 | public string Encode(byte[] bytes) 21 | { 22 | if (bytes == null) 23 | { 24 | throw new ArgumentNullException(nameof(bytes)); 25 | } 26 | 27 | return Convert.ToBase64String(bytes); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/Base64UrlByteArrayEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeviceId.Encoders; 4 | 5 | /// 6 | /// An implementation of that encodes byte arrays as Base64Url strings. 7 | /// 8 | public class Base64UrlByteArrayEncoder : IByteArrayEncoder 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public Base64UrlByteArrayEncoder() { } 14 | 15 | /// 16 | /// Encodes the specified byte array as a string. 17 | /// 18 | /// The byte array to encode. 19 | /// The byte array encoded as a string. 20 | public string Encode(byte[] bytes) 21 | { 22 | if (bytes == null) 23 | { 24 | throw new ArgumentNullException(nameof(bytes)); 25 | } 26 | 27 | return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/ByteArrayHasher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace DeviceId.Encoders; 5 | 6 | /// 7 | /// An implementation of that uses an arbitrary . 8 | /// 9 | public class ByteArrayHasher : IByteArrayHasher 10 | { 11 | /// 12 | /// A function that returns a new instance. 13 | /// 14 | private readonly Func _hashAlgorithmFactory; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// A function that returns a new instance. 20 | public ByteArrayHasher(Func hashAlgorithmFactory) 21 | { 22 | _hashAlgorithmFactory = hashAlgorithmFactory; 23 | } 24 | 25 | /// 26 | /// Returns a hash of the specified byte array. 27 | /// 28 | /// The byte array to hash. 29 | /// A hash of the specified byte array. 30 | public byte[] Hash(byte[] bytes) 31 | { 32 | using var hashAlgorithm = _hashAlgorithmFactory.Invoke(); 33 | return hashAlgorithm.ComputeHash(bytes); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/HashDeviceIdComponentEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace DeviceId.Encoders; 6 | 7 | /// 8 | /// An implementation of that encodes components as hashes. 9 | /// 10 | public class HashDeviceIdComponentEncoder : IDeviceIdComponentEncoder 11 | { 12 | /// 13 | /// The to use to hash the component value. 14 | /// 15 | private readonly IByteArrayHasher _byteArrayHasher; 16 | 17 | /// 18 | /// The to use to encode the resulting hash. 19 | /// 20 | private readonly IByteArrayEncoder _byteArrayEncoder; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The to use to hash the component value. 26 | /// The to use to encode the resulting hash. 27 | public HashDeviceIdComponentEncoder(IByteArrayHasher byteArrayHasher, IByteArrayEncoder byteArrayEncoder) 28 | { 29 | _byteArrayHasher = byteArrayHasher ?? throw new ArgumentNullException(nameof(byteArrayHasher)); 30 | _byteArrayEncoder = byteArrayEncoder ?? throw new ArgumentNullException(nameof(byteArrayEncoder)); 31 | } 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | /// A function that returns the hash algorithm to use. 37 | /// The to use to encode the resulting hash. 38 | public HashDeviceIdComponentEncoder(Func hashAlgorithm, IByteArrayEncoder byteArrayEncoder) 39 | : this(new ByteArrayHasher(hashAlgorithm ?? throw new ArgumentNullException(nameof(hashAlgorithm))), byteArrayEncoder) { } 40 | 41 | /// 42 | /// Encodes the specified as a string. 43 | /// 44 | /// The component to encode. 45 | /// The component encoded as a string. 46 | public string Encode(IDeviceIdComponent component) 47 | { 48 | var value = component.GetValue() ?? string.Empty; 49 | var bytes = Encoding.UTF8.GetBytes(value); 50 | var hash = _byteArrayHasher.Hash(bytes); 51 | var output = _byteArrayEncoder.Encode(hash); 52 | return output; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/HexByteArrayEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace DeviceId.Encoders; 5 | 6 | /// 7 | /// An implementation of that encodes byte arrays as hex strings. 8 | /// 9 | public class HexByteArrayEncoder : IByteArrayEncoder 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public HexByteArrayEncoder() { } 15 | 16 | /// 17 | /// Encodes the specified byte array as a string. 18 | /// 19 | /// The byte array to encode. 20 | /// The byte array encoded as a string. 21 | public string Encode(byte[] bytes) 22 | { 23 | if (bytes == null) 24 | { 25 | throw new ArgumentNullException(nameof(bytes)); 26 | } 27 | 28 | var sb = new StringBuilder(bytes.Length * 2); 29 | foreach (var b in bytes) 30 | { 31 | sb.AppendFormat("{0:x2}", b); 32 | } 33 | 34 | return sb.ToString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DeviceId/Encoders/PlainTextDeviceIdComponentEncoder.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId.Encoders; 2 | 3 | /// 4 | /// An implementation of that encodes components as plain text. 5 | /// 6 | public class PlainTextDeviceIdComponentEncoder : IDeviceIdComponentEncoder 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public PlainTextDeviceIdComponentEncoder() { } 12 | 13 | /// 14 | /// Encodes the specified as a string. 15 | /// 16 | /// The component to encode. 17 | /// The component encoded as a string. 18 | public string Encode(IDeviceIdComponent component) 19 | { 20 | return component.GetValue() ?? string.Empty; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/DeviceId/Formatters/HashDeviceIdFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using DeviceId.Encoders; 7 | 8 | namespace DeviceId.Formatters; 9 | 10 | /// 11 | /// An implementation of that combines the components into a hash. 12 | /// 13 | public class HashDeviceIdFormatter : IDeviceIdFormatter 14 | { 15 | /// 16 | /// The to use to hash the device ID. 17 | /// 18 | private readonly IByteArrayHasher _byteArrayHasher; 19 | 20 | /// 21 | /// The to use to encode the resulting hash. 22 | /// 23 | private readonly IByteArrayEncoder _byteArrayEncoder; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The to use to hash the device ID. 29 | /// The to use to encode the resulting hash. 30 | public HashDeviceIdFormatter(IByteArrayHasher byteArrayHasher, IByteArrayEncoder byteArrayEncoder) 31 | { 32 | _byteArrayHasher = byteArrayHasher ?? throw new ArgumentNullException(nameof(byteArrayHasher)); 33 | _byteArrayEncoder = byteArrayEncoder ?? throw new ArgumentNullException(nameof(byteArrayEncoder)); 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// A function that returns the hash algorithm to use. 40 | /// The to use to encode the resulting hash. 41 | public HashDeviceIdFormatter(Func hashAlgorithm, IByteArrayEncoder byteArrayEncoder) 42 | : this(new ByteArrayHasher(hashAlgorithm ?? throw new ArgumentNullException(nameof(hashAlgorithm))), byteArrayEncoder) { } 43 | 44 | /// 45 | /// Returns the device identifier string created by combining the specified components. 46 | /// 47 | /// A dictionary containing the components. 48 | /// The device identifier string. 49 | public string GetDeviceId(IDictionary components) 50 | { 51 | if (components == null) 52 | { 53 | throw new ArgumentNullException(nameof(components)); 54 | } 55 | 56 | var value = string.Join(",", components.OrderBy(x => x.Key).Select(x => x.Value.GetValue()).ToArray()); 57 | var bytes = Encoding.UTF8.GetBytes(value); 58 | var hash = _byteArrayHasher.Hash(bytes); 59 | return _byteArrayEncoder.Encode(hash); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/DeviceId/Formatters/StringDeviceIdFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace DeviceId.Formatters; 6 | 7 | /// 8 | /// An implementation of that combines the components into a concatenated string. 9 | /// 10 | public class StringDeviceIdFormatter : IDeviceIdFormatter 11 | { 12 | /// 13 | /// The instance to use to encode individual components. 14 | /// 15 | private readonly IDeviceIdComponentEncoder _encoder; 16 | 17 | /// 18 | /// The delimiter to use when concatenating the encoded component values. 19 | /// 20 | private readonly string _delimiter; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The instance to use to encode individual components. 26 | public StringDeviceIdFormatter(IDeviceIdComponentEncoder encoder) 27 | : this(encoder, ".") { } 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The instance to use to encode individual components. 33 | /// The delimiter to use when concatenating the encoded component values. 34 | public StringDeviceIdFormatter(IDeviceIdComponentEncoder encoder, string delimiter) 35 | { 36 | _encoder = encoder ?? throw new ArgumentNullException(nameof(encoder)); 37 | _delimiter = delimiter; 38 | } 39 | 40 | /// 41 | /// Returns the device identifier string created by combining the specified components. 42 | /// 43 | /// A dictionary containing the components. 44 | /// The device identifier string. 45 | public string GetDeviceId(IDictionary components) 46 | { 47 | if (components == null) 48 | { 49 | throw new ArgumentNullException(nameof(components)); 50 | } 51 | 52 | return string.Join(_delimiter, components.OrderBy(x => x.Key).Select(x => _encoder.Encode(x.Value)).ToArray()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/DeviceId/Formatters/XmlDeviceIdFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | 6 | namespace DeviceId.Formatters; 7 | 8 | /// 9 | /// An implementation of that combines the components into an XML string. 10 | /// 11 | public class XmlDeviceIdFormatter : IDeviceIdFormatter 12 | { 13 | /// 14 | /// The instance to use to encode individual components. 15 | /// 16 | private readonly IDeviceIdComponentEncoder _encoder; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The instance to use to encode individual components. 22 | public XmlDeviceIdFormatter(IDeviceIdComponentEncoder encoder) 23 | { 24 | _encoder = encoder ?? throw new ArgumentNullException(nameof(encoder)); 25 | } 26 | 27 | /// 28 | /// Returns the device identifier string created by combining the specified components. 29 | /// 30 | /// A dictionary containing the components. 31 | /// The device identifier string. 32 | public string GetDeviceId(IDictionary components) 33 | { 34 | if (components == null) 35 | { 36 | throw new ArgumentNullException(nameof(components)); 37 | } 38 | 39 | var document = new XDocument(GetElement(components)); 40 | return document.ToString(SaveOptions.DisableFormatting); 41 | } 42 | 43 | /// 44 | /// Returns an XML element representing the specified components. 45 | /// 46 | /// A dictionary containing the components. 47 | /// An XML element representing the specified component values. 48 | private XElement GetElement(IDictionary components) 49 | { 50 | var elements = components 51 | .OrderBy(x => x.Key) 52 | .Select(x => GetElement(x.Key, x.Value)); 53 | 54 | return new XElement("DeviceId", elements); 55 | } 56 | 57 | /// 58 | /// Returns an XML element representing the specified component. 59 | /// 60 | /// The component name. 61 | /// The component. 62 | /// An XML element representing the specified component. 63 | private XElement GetElement(string name, IDeviceIdComponent value) 64 | { 65 | return new XElement("Component", 66 | new XAttribute("Name", name), 67 | new XAttribute("Value", _encoder.Encode(value))); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/DeviceId/IByteArrayEncoder.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId; 2 | 3 | /// 4 | /// Provides functionality to encode a byte array as a string. 5 | /// 6 | public interface IByteArrayEncoder 7 | { 8 | /// 9 | /// Encodes the specified byte array as a string. 10 | /// 11 | /// The byte array to encode. 12 | /// The byte array encoded as a string. 13 | string Encode(byte[] bytes); 14 | } 15 | -------------------------------------------------------------------------------- /src/DeviceId/IByteArrayHasher.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId; 2 | 3 | /// 4 | /// Provides functionality to hash a byte array. 5 | /// 6 | public interface IByteArrayHasher 7 | { 8 | /// 9 | /// Returns a hash of the specified byte array. 10 | /// 11 | /// The byte array to hash. 12 | /// A hash of the specified byte array. 13 | byte[] Hash(byte[] bytes); 14 | } 15 | -------------------------------------------------------------------------------- /src/DeviceId/IDeviceIdComponent.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId; 2 | 3 | /// 4 | /// Represents a component that forms part of a device identifier. 5 | /// 6 | public interface IDeviceIdComponent 7 | { 8 | /// 9 | /// Gets the component value. 10 | /// 11 | /// The component value. 12 | string GetValue(); 13 | } 14 | -------------------------------------------------------------------------------- /src/DeviceId/IDeviceIdComponentEncoder.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId; 2 | 3 | /// 4 | /// Provides functionality to encode a as a string. 5 | /// 6 | public interface IDeviceIdComponentEncoder 7 | { 8 | /// 9 | /// Encodes the specified as a string. 10 | /// 11 | /// The component to encode. 12 | /// The component encoded as a string. 13 | string Encode(IDeviceIdComponent component); 14 | } 15 | -------------------------------------------------------------------------------- /src/DeviceId/IDeviceIdFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DeviceId; 4 | 5 | /// 6 | /// Provides a method to combine a number of component values into a single device identifier string. 7 | /// 8 | public interface IDeviceIdFormatter 9 | { 10 | /// 11 | /// Returns the device identifier string created by combining the specified components. 12 | /// 13 | /// A dictionary containing the components. 14 | /// The device identifier string. 15 | string GetDeviceId(IDictionary components); 16 | } 17 | -------------------------------------------------------------------------------- /src/DeviceId/IDeviceIdVersionEncoder.cs: -------------------------------------------------------------------------------- 1 | namespace DeviceId; 2 | 3 | /// 4 | /// Provides functionality to encode/decode device identifiers and version numbers to/from strings. 5 | /// 6 | public interface IDeviceIdVersionEncoder 7 | { 8 | /// 9 | /// Encodes a device identifier and a version number into a string. 10 | /// 11 | /// The device identifier. 12 | /// The version number. 13 | /// An encoded string comprising a device identifier and a version number. 14 | string Encode(string deviceId, int version); 15 | 16 | /// 17 | /// Attempts to decode a string into a device identifier and a version number. 18 | /// 19 | /// The string to decode. 20 | /// If successful, the device identifier; otherwise, null. 21 | /// If successful, the version number; otherwise, 0. 22 | /// true if the string was decoded successfully; otherwise, false. 23 | bool TryDecode(string value, out string deviceId, out int version); 24 | } 25 | -------------------------------------------------------------------------------- /src/DeviceId/Internal/ByteArrayEncoders.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.Encoders; 2 | 3 | namespace DeviceId.Internal; 4 | 5 | /// 6 | /// Static instances of the various byte array encoders. 7 | /// 8 | internal static class ByteArrayEncoders 9 | { 10 | /// 11 | /// Gets a base-32 byte array encoder that uses the Crockkford alphabet. 12 | /// 13 | public static Base32ByteArrayEncoder Base32Crockford { get; } = new Base32ByteArrayEncoder(Base32ByteArrayEncoder.CrockfordAlphabet); 14 | } 15 | -------------------------------------------------------------------------------- /src/DeviceId/Internal/ByteArrayHashers.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using DeviceId.Encoders; 3 | 4 | namespace DeviceId.Internal; 5 | 6 | /// 7 | /// Static instances of the various byte array hashers. 8 | /// 9 | internal static class ByteArrayHashers 10 | { 11 | /// 12 | /// Gets a byte array hasher that uses SHA256. 13 | /// 14 | public static ByteArrayHasher Sha256 { get; } = new ByteArrayHasher(() => SHA256.Create()); 15 | } 16 | -------------------------------------------------------------------------------- /src/DeviceId/Internal/MacAddressFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace DeviceId.Internal; 4 | 5 | /// 6 | /// Provides functionality to format MAC addresses. 7 | /// 8 | internal static class MacAddressFormatter 9 | { 10 | /// 11 | /// Formats the specified MAC address. 12 | /// 13 | /// The MAC address to format. 14 | /// The formatted MAC address. 15 | public static string FormatMacAddress(string input) 16 | { 17 | // Check if this can be a hex formatted EUI-48 or EUI-64 identifier. 18 | if (input.Length != 12 && input.Length != 16) 19 | { 20 | return input; 21 | } 22 | 23 | // Chop up input in 2 character chunks. 24 | const int partSize = 2; 25 | var parts = Enumerable.Range(0, input.Length / partSize).Select(x => input.Substring(x * partSize, partSize)); 26 | 27 | // Put the parts in the AA:BB:CC format. 28 | var result = string.Join(":", parts.ToArray()); 29 | 30 | return result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/DeviceId/Internal/OS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | #if NETSTANDARD 4 | using System.Runtime.InteropServices; 5 | using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; 6 | #endif 7 | 8 | namespace DeviceId.Internal; 9 | 10 | /// 11 | /// Provides helper methods relating to the OS. 12 | /// 13 | internal static class OS 14 | { 15 | // ToDo: Add SupportedOSPlatformGuardAttribute to these methods so that the CA1416 warning goes away. 16 | 17 | /// 18 | /// Gets a value indicating whether this is a Windows OS. 19 | /// 20 | public static bool IsWindows { get; } 21 | #if NETFRAMEWORK 22 | = true; 23 | #elif NETSTANDARD 24 | = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 25 | #elif NET5_0_OR_GREATER 26 | = OperatingSystem.IsWindows(); 27 | #endif 28 | 29 | /// 30 | /// Gets a value indicating whether this is a Linux OS. 31 | /// 32 | public static bool IsLinux { get; } 33 | #if NETFRAMEWORK 34 | = false; 35 | #elif NETSTANDARD 36 | = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 37 | #elif NET5_0_OR_GREATER 38 | = OperatingSystem.IsLinux(); 39 | #endif 40 | 41 | /// 42 | /// Gets a value indicating whether this is Mac OS. 43 | /// 44 | public static bool IsMacOS { get; } 45 | #if NETFRAMEWORK 46 | = false; 47 | #elif NETSTANDARD 48 | = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 49 | #elif NET5_0_OR_GREATER 50 | = OperatingSystem.IsMacOS(); 51 | #endif 52 | 53 | /// 54 | /// Gets the current OS version. 55 | /// 56 | public static string Version { get; } 57 | #if (NETFRAMEWORK || NET5_0_OR_GREATER) 58 | = Environment.OSVersion.ToString(); 59 | #elif NETSTANDARD 60 | = IsWindows 61 | ? Environment.OSVersion.ToString() 62 | : string.Concat(RuntimeEnvironment.OperatingSystem, " ", RuntimeEnvironment.OperatingSystemVersion); 63 | #endif 64 | } 65 | -------------------------------------------------------------------------------- /src/DeviceId/_InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] 4 | [assembly: InternalsVisibleTo("DeviceId.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001008906d2e5a92d72693cfdd24b29f9c3ea5ca51972be746724afef8a65000a1ebbc88aee54e4c9c3bef49c0e837702170e99919a8b8075cfd6ed8494c5f9cd1a640a57cc907a84861bfe7ecb877d475a94ec333c6c0a586b6f37a15e67431381cac046217c0fa570c3e8e140e733254686213b77ae53fccdc1f5b3ab806ac692c1")] 5 | 6 | [assembly: InternalsVisibleTo("DeviceId.Windows, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")] 7 | [assembly: InternalsVisibleTo("DeviceId.Windows.Mmi, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")] 8 | [assembly: InternalsVisibleTo("DeviceId.Windows.Wmi, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")] 9 | [assembly: InternalsVisibleTo("DeviceId.Windows.WmiLight, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")] 10 | [assembly: InternalsVisibleTo("DeviceId.Linux, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")] 11 | [assembly: InternalsVisibleTo("DeviceId.Mac, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")] 12 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Components/DockerContainerIdComponentTests.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.Linux.Components; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace DeviceId.Tests.Components; 6 | 7 | public class DockerContainerIdComponentTests 8 | { 9 | [InlineData("non-existing", null)] 10 | [InlineData("", null)] 11 | [InlineData(null, null)] 12 | [InlineData("Linux_4.4.txt", "cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411")] 13 | [InlineData("Linux_4.8-4.13.txt", "afe96d48db6d2c19585572f986fc310c92421a3dac28310e847566fb82166013")] 14 | [InlineData("linux_nodocker.txt", null)] 15 | [Theory] 16 | public void TestGetValue(string file, string expected) 17 | { 18 | var component = new DockerContainerIdComponent(file); 19 | var componentValue = component.GetValue(); 20 | 21 | componentValue.Should().Be(expected); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Components/SqlServerComponentTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Common; 2 | using DeviceId.SqlServer; 3 | using NSubstitute; 4 | using Xunit; 5 | 6 | namespace DeviceId.Tests.Components; 7 | 8 | public class SqlServerComponentTests 9 | { 10 | [Fact] 11 | public void ConnectionInstance_DoNotDisposeConnection() 12 | { 13 | var connection = Substitute.For(); 14 | 15 | var deviceId = new DeviceIdBuilder() 16 | .AddSqlServer(connection, sqlDeviceId => sqlDeviceId.AddDatabaseId()) 17 | .ToString(); 18 | 19 | connection.DidNotReceive().Dispose(); 20 | connection.DidNotReceive().DisposeAsync(); 21 | } 22 | 23 | [Fact] 24 | public void ConnectionFactory_DisposeConnection() 25 | { 26 | var connection = Substitute.For(); 27 | 28 | var deviceId = new DeviceIdBuilder() 29 | .AddSqlServer(() => connection, sqlDeviceId => sqlDeviceId.AddDatabaseId()) 30 | .ToString(); 31 | 32 | connection.Received().Dispose(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Components/WmiAndMmiDriveSerialNumberPerfTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using DeviceId.Windows.Mmi.Components; 3 | using DeviceId.Windows.Wmi.Components; 4 | using DeviceId.Windows.WmiLight.Components; 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace DeviceId.Tests.Components; 9 | 10 | public class WmiAndMmiDriveSerialNumberPerfTests 11 | { 12 | [Fact] 13 | public void WmiDriveSerialNumberPerfShouldBeAcceptable() 14 | { 15 | var sw = Stopwatch.StartNew(); 16 | var deviceId = new WmiSystemDriveSerialNumberDeviceIdComponent().GetValue(); 17 | sw.Stop(); 18 | 19 | deviceId.Should().NotBeEmpty(); 20 | sw.ElapsedMilliseconds.Should().BeLessOrEqualTo(500); 21 | } 22 | 23 | [Fact] 24 | public void MmiDriveSerialNumberPerfShouldBeAcceptable() 25 | { 26 | var sw = Stopwatch.StartNew(); 27 | var deviceId = new MmiSystemDriveSerialNumberDeviceIdComponent().GetValue(); 28 | sw.Stop(); 29 | 30 | deviceId.Should().NotBeEmpty(); 31 | sw.ElapsedMilliseconds.Should().BeLessOrEqualTo(500); 32 | } 33 | 34 | [Fact] 35 | public void WmiLightDriveSerialNumberPerfShouldBeAcceptable() 36 | { 37 | var sw = Stopwatch.StartNew(); 38 | var deviceId = new WmiLightSystemDriveSerialNumberDeviceIdComponent().GetValue(); 39 | sw.Stop(); 40 | 41 | deviceId.Should().NotBeEmpty(); 42 | sw.ElapsedMilliseconds.Should().BeLessOrEqualTo(500); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/DeviceId.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | latest 6 | false 7 | true 8 | DeviceId.Tests.snk 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | false 39 | 40 | 41 | 42 | 43 | 44 | PreserveNewest 45 | 46 | 47 | PreserveNewest 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/DeviceId.Tests.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatthewKing/DeviceId/1b9f03b12c859980fd802976d938b006e62c95e6/test/DeviceId.Tests/DeviceId.Tests.snk -------------------------------------------------------------------------------- /test/DeviceId.Tests/DeviceIdBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace DeviceId.Tests; 6 | 7 | public class DeviceIdBuilderTests 8 | { 9 | [Fact] 10 | public void ToString_FormatterIsNull_ThrowsInvalidOperationException() 11 | { 12 | var builder = new DeviceIdBuilder(); 13 | builder.Formatter = null; 14 | 15 | var action = () => builder.ToString(); 16 | action.Should().Throw().WithMessage("The Formatter property must not be null in order for ToString to be called."); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/DeviceIdManagerTests.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.Components; 2 | using DeviceId.Encoders; 3 | using DeviceId.Formatters; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace DeviceId.Tests; 8 | 9 | public class DeviceIdManagerTests 10 | { 11 | [Fact] 12 | public void NoBuilderSpecified() 13 | { 14 | var manager = new DeviceIdManager(); 15 | 16 | manager.GetDeviceId().Should().BeNull(); 17 | } 18 | 19 | [Fact] 20 | public void InvalidBuilderSpecified() 21 | { 22 | var formatter = new StringDeviceIdFormatter(new PlainTextDeviceIdComponentEncoder(), string.Empty); 23 | 24 | var manager = new DeviceIdManager() 25 | .AddBuilder(1, builder => builder 26 | .UseFormatter(formatter) 27 | .AddComponent("C1", new DeviceIdComponent("C1V")) 28 | .AddComponent("C2", new DeviceIdComponent("C2V"))) 29 | .AddBuilder(2, builder => builder 30 | .UseFormatter(formatter) 31 | .AddComponent("C1", new DeviceIdComponent("C1V")) 32 | .AddComponent("C3", new DeviceIdComponent("C3V"))); 33 | 34 | manager.GetDeviceId(-1).Should().BeNull(); 35 | } 36 | 37 | [Fact] 38 | public void GetCurrentValue() 39 | { 40 | var formatter = new StringDeviceIdFormatter(new PlainTextDeviceIdComponentEncoder(), string.Empty); 41 | 42 | var manager = new DeviceIdManager() 43 | .AddBuilder(1, builder => builder 44 | .UseFormatter(formatter) 45 | .AddComponent("C1", new DeviceIdComponent("A")) 46 | .AddComponent("C2", new DeviceIdComponent("B"))) 47 | .AddBuilder(2, builder => builder 48 | .UseFormatter(formatter) 49 | .AddComponent("C1", new DeviceIdComponent("A")) 50 | .AddComponent("C3", new DeviceIdComponent("C"))); 51 | 52 | manager.GetDeviceId().Should().Be("$2$AC"); 53 | } 54 | 55 | [Fact] 56 | public void GetPreviousValue() 57 | { 58 | var formatter = new StringDeviceIdFormatter(new PlainTextDeviceIdComponentEncoder(), string.Empty); 59 | 60 | var manager = new DeviceIdManager() 61 | .AddBuilder(1, builder => builder 62 | .UseFormatter(formatter) 63 | .AddComponent("C1", new DeviceIdComponent("A")) 64 | .AddComponent("C2", new DeviceIdComponent("B"))) 65 | .AddBuilder(2, builder => builder 66 | .UseFormatter(formatter) 67 | .AddComponent("C1", new DeviceIdComponent("A")) 68 | .AddComponent("C3", new DeviceIdComponent("C"))); 69 | 70 | manager.GetDeviceId(1).Should().Be("$1$AB"); 71 | } 72 | 73 | [Fact] 74 | public void Validation() 75 | { 76 | var formatter = new StringDeviceIdFormatter(new PlainTextDeviceIdComponentEncoder(), string.Empty); 77 | 78 | var manager = new DeviceIdManager() 79 | .AddBuilder(1, builder => builder 80 | .UseFormatter(formatter) 81 | .AddComponent("C1", new DeviceIdComponent("A")) 82 | .AddComponent("C2", new DeviceIdComponent("B"))) 83 | .AddBuilder(2, builder => builder 84 | .UseFormatter(formatter) 85 | .AddComponent("C1", new DeviceIdComponent("A")) 86 | .AddComponent("C3", new DeviceIdComponent("C"))); 87 | 88 | manager.Validate("$1$AB").Should().BeTrue(); 89 | manager.Validate("$2$AB").Should().BeFalse(); 90 | 91 | manager.Validate("$2$AC").Should().BeTrue(); 92 | manager.Validate("$1$AC").Should().BeFalse(); 93 | } 94 | 95 | [Fact] 96 | public void ValidationWithoutVersionEncoded() 97 | { 98 | var formatter = new StringDeviceIdFormatter(new PlainTextDeviceIdComponentEncoder(), string.Empty); 99 | 100 | var manager = new DeviceIdManager() 101 | .AddBuilder(1, builder => builder 102 | .UseFormatter(formatter) 103 | .AddComponent("C1", new DeviceIdComponent("A")) 104 | .AddComponent("C2", new DeviceIdComponent("B"))) 105 | .AddBuilder(2, builder => builder 106 | .UseFormatter(formatter) 107 | .AddComponent("C1", new DeviceIdComponent("A")) 108 | .AddComponent("C3", new DeviceIdComponent("C"))); 109 | 110 | // If we can't decode the version, try to use the first builder. 111 | 112 | manager.Validate("AB").Should().BeTrue(); 113 | manager.Validate("AC").Should().BeFalse(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Encoders/Base32ByteArrayEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Encoders; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DeviceId.Tests.Encoders; 7 | 8 | public class Base32ByteArrayEncoderTests 9 | { 10 | [Fact] 11 | public void Constructor_AlphabetIsNull_ThrowsArgumentNullException() 12 | { 13 | var action = () => new Base32ByteArrayEncoder(null); 14 | 15 | action.Should().Throw(); 16 | } 17 | 18 | [Fact] 19 | public void Constructor_AlphabetIsTheWrongLength_ThrowsArgumentException() 20 | { 21 | var tooShort = () => new Base32ByteArrayEncoder(""); 22 | tooShort.Should().Throw(); 23 | 24 | var tooLong = () => new Base32ByteArrayEncoder("ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789"); 25 | tooShort.Should().Throw(); 26 | } 27 | 28 | [Fact] 29 | public void Encode_BytesIsNull_ThrowsArgumentNullException() 30 | { 31 | var encoder = new Base32ByteArrayEncoder(Base32ByteArrayEncoder.Rfc4648Alphabet); 32 | 33 | var action = () => encoder.Encode(null); 34 | 35 | action.Should().Throw(); 36 | } 37 | 38 | [Fact] 39 | public void Encode_BytesIsEmpty_ReturnsEmptyString() 40 | { 41 | var encoder = new Base32ByteArrayEncoder(Base32ByteArrayEncoder.Rfc4648Alphabet); 42 | 43 | encoder.Encode(Array.Empty()).Should().Be(string.Empty); 44 | } 45 | 46 | [Fact] 47 | public void Encode_Rfc4648Alphabet_ReturnsByteArrayAsBase64String() 48 | { 49 | var encoder = new Base32ByteArrayEncoder(Base32ByteArrayEncoder.Rfc4648Alphabet); 50 | 51 | var bytes = new byte[] 52 | { 53 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 54 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 55 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 56 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 57 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 58 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 59 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 60 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 61 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 62 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 63 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 64 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 65 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 66 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 67 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 68 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 69 | }; 70 | 71 | encoder.Encode(bytes).Should().Be("AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO33XP6DY7F47U6X3PP6HZ7L57Z7P674"); 72 | } 73 | 74 | [Fact] 75 | public void Encode_CrockfordAlphabet_ReturnsByteArrayAsBase64String() 76 | { 77 | var encoder = new Base32ByteArrayEncoder(Base32ByteArrayEncoder.CrockfordAlphabet); 78 | 79 | var bytes = new byte[] 80 | { 81 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 82 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 83 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 84 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 85 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 86 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 87 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 88 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 89 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 90 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 91 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 92 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 93 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 94 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 95 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 96 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 97 | }; 98 | 99 | encoder.Encode(bytes).Should().Be("000G40R40M30E209185GR38E1W8124GK2GAHC5RR34D1P70X3RFJ08924CJ2A9H750MJMASC5MQ2YC1H68SK8D9P6WW3JEHV7GYKWFT085146H258S3MGJAA9D64TKJFA18N4MTMANB5EP2SB9DNRQAYBXG62RK3CHJPCSV8D5N6PV3DDSQQ0WBJEDT7AXKQF1WQMYVWFNZ7Z041GA1R91C6GY48K2MBHJ6RX3WGJ699754NJTBSH6CTKEE9V7MZM2GT58X4MPKAFA59NANTSBDENYRB3CNKPJTVDDXRQ6XBQF5XQTZW1GE2RF2CBHP7S34WNJYCSQ7CZM6HTB9X9NEPTZCDKPPVVKEXXQZ0W7HE7S75WVKYHTFAXFPEVVQFY3RZ5WZMYQVFFY7SZBXZSZFYZW"); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Encoders/Base64ByteArrayEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Encoders; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DeviceId.Tests.Encoders; 7 | 8 | public class Base64ByteArrayEncoderTests 9 | { 10 | [Fact] 11 | public void Encode_BytesIsNull_ThrowsArgumentNullException() 12 | { 13 | var encoder = new Base64ByteArrayEncoder(); 14 | 15 | var action = () => encoder.Encode(null); 16 | 17 | action.Should().Throw(); 18 | } 19 | 20 | [Fact] 21 | public void Encode_ReturnsByteArrayAsBase64String() 22 | { 23 | var encoder = new Base64ByteArrayEncoder(); 24 | 25 | var bytes = new byte[] 26 | { 27 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 28 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 29 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 30 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 31 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 32 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 33 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 34 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 35 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 36 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 37 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 38 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 39 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 40 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 41 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 42 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 43 | }; 44 | 45 | encoder.Encode(bytes).Should().Be("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Encoders/Base64UrlByteArrayEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Encoders; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DeviceId.Tests.Encoders; 7 | 8 | public class Base64UrlByteArrayEncoderTests 9 | { 10 | [Fact] 11 | public void Encode_BytesIsNull_ThrowsArgumentNullException() 12 | { 13 | var encoder = new Base64UrlByteArrayEncoder(); 14 | 15 | var action = () => encoder.Encode(null); 16 | 17 | action.Should().Throw(); 18 | } 19 | 20 | [Fact] 21 | public void Encode_ReturnsByteArrayAsBase64UrlString() 22 | { 23 | var encoder = new Base64UrlByteArrayEncoder(); 24 | 25 | var bytes = new byte[] 26 | { 27 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 28 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 29 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 30 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 31 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 32 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 33 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 34 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 35 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 36 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 37 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 38 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 39 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 40 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 41 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 42 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 43 | }; 44 | 45 | encoder.Encode(bytes).Should().Be("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Encoders/HashDeviceIdComponentEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using DeviceId.Components; 4 | using DeviceId.Encoders; 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace DeviceId.Tests.Encoders; 9 | 10 | public class HashDeviceIdComponentEncoderTests 11 | { 12 | [Fact] 13 | public void Constructor_ByteArrayHasherIsNull_ThrowsArgumentNullException() 14 | { 15 | var action = () => new HashDeviceIdComponentEncoder(null as IByteArrayHasher, new HexByteArrayEncoder()); 16 | 17 | action.Should().Throw(); 18 | } 19 | 20 | [Fact] 21 | public void Constructor_ByteArrayEncoderIsNull_ThrowsArgumentNullException() 22 | { 23 | var action = () => new HashDeviceIdComponentEncoder(() => MD5.Create(), null); 24 | 25 | action.Should().Throw(); 26 | } 27 | 28 | [Fact] 29 | public void Constructor_HashAlgorithmIsNull_ThrowsArgumentNullException() 30 | { 31 | var action = () => new HashDeviceIdComponentEncoder(null as Func, new HexByteArrayEncoder()); 32 | 33 | action.Should().Throw(); 34 | } 35 | 36 | [Fact] 37 | public void Encode_SHA256_Hex_ReturnsHashedComponentValue() 38 | { 39 | var encoder = new HashDeviceIdComponentEncoder(() => SHA256.Create(), new HexByteArrayEncoder()); 40 | 41 | var component = new DeviceIdComponent("Value"); 42 | 43 | encoder.Encode(component).Should().Be("8e37953d23daca5ff01b8282c33f4e0a2152f1d1885f94c06418617e3ee1d24e"); 44 | } 45 | 46 | [Fact] 47 | public void Encode_SHA256_Base64_ReturnsHashedComponentValue() 48 | { 49 | var encoder = new HashDeviceIdComponentEncoder(() => SHA256.Create(), new Base64ByteArrayEncoder()); 50 | 51 | var component = new DeviceIdComponent("Value"); 52 | 53 | encoder.Encode(component).Should().Be("jjeVPSPayl/wG4KCwz9OCiFS8dGIX5TAZBhhfj7h0k4="); 54 | } 55 | 56 | [Fact] 57 | public void Encode_SHA256_Base64Url_ReturnsHashedComponentValue() 58 | { 59 | var encoder = new HashDeviceIdComponentEncoder(() => SHA256.Create(), new Base64UrlByteArrayEncoder()); 60 | 61 | var component = new DeviceIdComponent("Value"); 62 | 63 | encoder.Encode(component).Should().Be("jjeVPSPayl_wG4KCwz9OCiFS8dGIX5TAZBhhfj7h0k4"); 64 | } 65 | 66 | [Fact] 67 | public void Encode_MD5_Hex_ReturnsHashedComponentValue() 68 | { 69 | var encoder = new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder()); 70 | 71 | var component = new DeviceIdComponent("Value"); 72 | 73 | encoder.Encode(component).Should().Be("689202409e48743b914713f96d93947c"); 74 | } 75 | 76 | [Fact] 77 | public void Encode_MD5_Base64_ReturnsHashedComponentValue() 78 | { 79 | var encoder = new HashDeviceIdComponentEncoder(() => MD5.Create(), new Base64ByteArrayEncoder()); 80 | 81 | var component = new DeviceIdComponent("Value"); 82 | 83 | encoder.Encode(component).Should().Be("aJICQJ5IdDuRRxP5bZOUfA=="); 84 | } 85 | 86 | [Fact] 87 | public void Encode_MD5_Base64Url_ReturnsHashedComponentValue() 88 | { 89 | var encoder = new HashDeviceIdComponentEncoder(() => MD5.Create(), new Base64UrlByteArrayEncoder()); 90 | 91 | var component = new DeviceIdComponent("Value"); 92 | 93 | encoder.Encode(component).Should().Be("aJICQJ5IdDuRRxP5bZOUfA"); 94 | } 95 | 96 | [Fact] 97 | public void Encode_ValueIsNull_TreatItAsAnEmptyString() 98 | { 99 | var encoder = new HashDeviceIdComponentEncoder(() => MD5.Create(), new Base64UrlByteArrayEncoder()); 100 | var expected = encoder.Encode(new DeviceIdComponent(string.Empty)); 101 | var actual = encoder.Encode(new DeviceIdComponent(default(string))); 102 | actual.Should().Be(expected); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Encoders/HexByteArrayEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeviceId.Encoders; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DeviceId.Tests.Encoders; 7 | 8 | public class HexByteArrayEncoderTests 9 | { 10 | [Fact] 11 | public void Encode_BytesIsNull_ThrowsArgumentNullException() 12 | { 13 | var encoder = new HexByteArrayEncoder(); 14 | 15 | var action = () => encoder.Encode(null); 16 | 17 | action.Should().Throw(); 18 | } 19 | 20 | [Fact] 21 | public void Encode_ReturnsByteArrayAsHexString() 22 | { 23 | var encoder = new HexByteArrayEncoder(); 24 | 25 | var bytes = new byte[] 26 | { 27 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 28 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 29 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 30 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 31 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 32 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 33 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 34 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 35 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 36 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 37 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 38 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 39 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 40 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 41 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 42 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 43 | }; 44 | 45 | encoder.Encode(bytes).Should().Be("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Encoders/PlainTextDeviceIdComponentEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.Components; 2 | using DeviceId.Encoders; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace DeviceId.Tests.Encoders; 7 | 8 | public class PlainTextDeviceIdComponentEncoderTests 9 | { 10 | [Fact] 11 | public void Encode_ReturnsPlainTextComponentValue() 12 | { 13 | var encoder = new PlainTextDeviceIdComponentEncoder(); 14 | 15 | var component = new DeviceIdComponent("Value"); 16 | 17 | encoder.Encode(component).Should().Be("Value"); 18 | } 19 | 20 | [Fact] 21 | public void Encode_ValueIsNull_TreatItAsAnEmptyString() 22 | { 23 | var encoder = new PlainTextDeviceIdComponentEncoder(); 24 | var expected = encoder.Encode(new DeviceIdComponent(string.Empty)); 25 | var actual = encoder.Encode(new DeviceIdComponent(default(string))); 26 | actual.Should().Be(expected); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Formatters/HashDeviceIdFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | using DeviceId.Components; 5 | using DeviceId.Encoders; 6 | using DeviceId.Formatters; 7 | using FluentAssertions; 8 | using Xunit; 9 | 10 | namespace DeviceId.Tests.Formatters; 11 | 12 | public class HashDeviceIdFormatterTests 13 | { 14 | [Fact] 15 | public void Constructor_ByteArrayHasherIsNull_ThrowsArgumentNullException() 16 | { 17 | var action = () => new HashDeviceIdFormatter(null as IByteArrayHasher, new HexByteArrayEncoder()); 18 | 19 | action.Should().Throw(); 20 | } 21 | 22 | [Fact] 23 | public void Constructor_ByteArrayEncoderIsNull_ThrowsArgumentNullException() 24 | { 25 | var action = () => new HashDeviceIdFormatter(() => MD5.Create(), null); 26 | 27 | action.Should().Throw(); 28 | } 29 | 30 | [Fact] 31 | public void Constructor_HashAlgorithmIsNull_ThrowsArgumentNullException() 32 | { 33 | var action = () => new HashDeviceIdFormatter(null as Func, new HexByteArrayEncoder()); 34 | 35 | action.Should().Throw(); 36 | } 37 | 38 | [Fact] 39 | public void GetDeviceId_ComponentsIsNull_ThrowsArgumentNullException() 40 | { 41 | var formatter = new HashDeviceIdFormatter(() => MD5.Create(), new HexByteArrayEncoder()); 42 | 43 | var action = () => formatter.GetDeviceId(null); 44 | 45 | action.Should().Throw(); 46 | } 47 | 48 | [Fact] 49 | public void GetDeviceId_ComponentsIsEmpty_ReturnsDeviceId() 50 | { 51 | var formatter = new HashDeviceIdFormatter(() => MD5.Create(), new HexByteArrayEncoder()); 52 | 53 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase)); 54 | 55 | deviceId.Should().Be("d41d8cd98f00b204e9800998ecf8427e"); 56 | } 57 | 58 | [Fact] 59 | public void GetDeviceId_ComponentsAreValid_ReturnsDeviceId() 60 | { 61 | var formatter = new HashDeviceIdFormatter(() => MD5.Create(), new HexByteArrayEncoder()); 62 | 63 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 64 | { 65 | ["Test1"] = new DeviceIdComponent("Test1"), 66 | ["Test2"] = new DeviceIdComponent("Test2"), 67 | }); 68 | 69 | deviceId.Should().Be("b02f4481c190173f05192bc08a1b14bc"); 70 | } 71 | 72 | [Fact] 73 | public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() 74 | { 75 | var formatter = new HashDeviceIdFormatter(() => MD5.Create(), new HexByteArrayEncoder()); 76 | 77 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 78 | { 79 | ["Test1"] = new DeviceIdComponent(default(string)), 80 | }); 81 | 82 | deviceId.Should().Be("d41d8cd98f00b204e9800998ecf8427e"); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | using DeviceId.Components; 5 | using DeviceId.Encoders; 6 | using DeviceId.Formatters; 7 | using FluentAssertions; 8 | using Xunit; 9 | 10 | namespace DeviceId.Tests.Formatters; 11 | 12 | public class StringDeviceIdFormatterTests 13 | { 14 | [Fact] 15 | public void Constructor_EncoderIsNull_ThrowsArgumentNullException() 16 | { 17 | var action = () => new StringDeviceIdFormatter(null); 18 | 19 | action.Should().Throw(); 20 | } 21 | 22 | [Fact] 23 | public void GetDeviceId_ComponentsIsNull_ThrowsArgumentNullException() 24 | { 25 | var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 26 | 27 | var action = () => formatter.GetDeviceId(null); 28 | 29 | action.Should().Throw(); 30 | } 31 | 32 | [Fact] 33 | public void GetDeviceId_ComponentsIsEmpty_ReturnsEmptyString() 34 | { 35 | var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 36 | 37 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase)); 38 | 39 | deviceId.Should().Be(string.Empty); 40 | } 41 | 42 | [Fact] 43 | public void GetDeviceId_ComponentsAreValid_ReturnsDeviceId() 44 | { 45 | var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 46 | 47 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 48 | { 49 | ["Test1"] = new DeviceIdComponent("Test1"), 50 | ["Test2"] = new DeviceIdComponent("Test2"), 51 | }); 52 | 53 | deviceId.Should().Be("e1b849f9631ffc1829b2e31402373e3c.c454552d52d55d3ef56408742887362b"); 54 | } 55 | 56 | [Fact] 57 | public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() 58 | { 59 | var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 60 | 61 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 62 | { 63 | ["Test1"] = new DeviceIdComponent(default(string)), 64 | }); 65 | 66 | deviceId.Should().Be("d41d8cd98f00b204e9800998ecf8427e"); 67 | } 68 | 69 | [Fact] 70 | public void NoDelimiter() 71 | { 72 | var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder()), null); 73 | 74 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 75 | { 76 | ["Test1"] = new DeviceIdComponent("Test1"), 77 | ["Test2"] = new DeviceIdComponent("Test2"), 78 | }); 79 | 80 | deviceId.Should().Be("e1b849f9631ffc1829b2e31402373e3cc454552d52d55d3ef56408742887362b"); 81 | } 82 | 83 | [Fact] 84 | public void CustomDelimiter() 85 | { 86 | var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder()), "+"); 87 | 88 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 89 | { 90 | ["Test1"] = new DeviceIdComponent("Test1"), 91 | ["Test2"] = new DeviceIdComponent("Test2"), 92 | }); 93 | 94 | deviceId.Should().Be("e1b849f9631ffc1829b2e31402373e3c+c454552d52d55d3ef56408742887362b"); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Formatters/XmlDeviceIdFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | using DeviceId.Components; 5 | using DeviceId.Encoders; 6 | using DeviceId.Formatters; 7 | using FluentAssertions; 8 | using Xunit; 9 | 10 | namespace DeviceId.Tests.Formatters; 11 | 12 | public class XmlDeviceIdFormatterTests 13 | { 14 | [Fact] 15 | public void Constructor_EncoderIsNull_ThrowsArgumentNullException() 16 | { 17 | var action = () => new XmlDeviceIdFormatter(null); 18 | 19 | action.Should().Throw(); 20 | } 21 | 22 | [Fact] 23 | public void GetDeviceId_ComponentsIsNull_ThrowsArgumentNullException() 24 | { 25 | var formatter = new XmlDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 26 | 27 | var action = () => formatter.GetDeviceId(null); 28 | 29 | action.Should().Throw(); 30 | } 31 | 32 | [Fact] 33 | public void GetDeviceId_ComponentsIsEmpty_ReturnsEmptyXmlDocument() 34 | { 35 | var formatter = new XmlDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 36 | 37 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase)); 38 | 39 | deviceId.Should().Be(""); 40 | } 41 | 42 | [Fact] 43 | public void GetDeviceId_ComponentsAreValid_ReturnsDeviceId() 44 | { 45 | var formatter = new XmlDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 46 | 47 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 48 | { 49 | ["Test1"] = new DeviceIdComponent("Test1"), 50 | ["Test2"] = new DeviceIdComponent("Test2"), 51 | }); 52 | 53 | deviceId.Should().Be(""); 54 | } 55 | 56 | [Fact] 57 | public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() 58 | { 59 | var formatter = new XmlDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); 60 | 61 | var deviceId = formatter.GetDeviceId(new Dictionary(StringComparer.OrdinalIgnoreCase) 62 | { 63 | ["Test1"] = new DeviceIdComponent(default(string)), 64 | }); 65 | 66 | deviceId.Should().Be(""); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Internal/MacAddressFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using DeviceId.Internal; 2 | using FluentAssertions; 3 | using Xunit; 4 | 5 | namespace DeviceId.Tests.Internal; 6 | 7 | public class MacAddressFormatterTests 8 | { 9 | [Fact] 10 | public void FormatMac_NonMac() 11 | { 12 | var input = "Try me"; 13 | var result = MacAddressFormatter.FormatMacAddress(input); 14 | result.Should().BeEquivalentTo(input, "Non MAC addresses are not formatted"); 15 | } 16 | 17 | [Fact] 18 | public void FormatMac_48BitMac() 19 | { 20 | var input = "AABBCCDDEEFF"; 21 | var result = MacAddressFormatter.FormatMacAddress(input); 22 | result.Should().BeEquivalentTo("AA:BB:CC:DD:EE:FF", "MAC address should be formatted"); 23 | } 24 | 25 | [Fact] 26 | public void FormatMac_64BitMac() 27 | { 28 | var input = "AABBCCDDEEFF0011"; 29 | var result = MacAddressFormatter.FormatMacAddress(input); 30 | result.Should().BeEquivalentTo("AA:BB:CC:DD:EE:FF:00:11", "MAC address should be formatted"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Linux_4.4.txt: -------------------------------------------------------------------------------- 1 | 11:pids:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope 2 | ... 3 | 1:name=systemd:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope 4 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/Linux_4.8-4.13.txt: -------------------------------------------------------------------------------- 1 | 11:hugetlb:/docker/afe96d48db6d2c19585572f986fc310c92421a3dac28310e847566fb82166013 2 | ... 3 | 1:name=systemd:/docker/afe96d48db6d2c19585572f986fc310c92421a3dac28310e847566fb82166013 4 | -------------------------------------------------------------------------------- /test/DeviceId.Tests/linux_nodocker.txt: -------------------------------------------------------------------------------- 1 | 14:name=systemd:/ 2 | 13:rdma:/ 3 | 12:pids:/ 4 | 11:hugetlb:/ 5 | 10:net_prio:/ 6 | 9:perf_event:/ 7 | 8:net_cls:/ 8 | 7:freezer:/ 9 | 6:devices:/ 10 | 5:memory:/ 11 | 4:blkio:/ 12 | 3:cpuacct:/ 13 | 2:cpu:/ 14 | 1:cpuset:/ 15 | 0::/ 16 | --------------------------------------------------------------------------------