diff --git a/.github/workflows/dotnet-build-componentclassregistry.yml b/.github/workflows/dotnet-build-componentclassregistry.yml
new file mode 100644
index 0000000..f671308
--- /dev/null
+++ b/.github/workflows/dotnet-build-componentclassregistry.yml
@@ -0,0 +1,29 @@
+name: .NET build HardwareManifest
+
+on:
+ push:
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+ - name: Restore dependencies
+ working-directory: dotnet/ComponentClassRegistry
+ run: dotnet restore
+ - name: Build
+ working-directory: dotnet/ComponentClassRegistry
+ run: dotnet build
+ - name: Test
+ working-directory: dotnet/ComponentClassRegistry
+ run: dotnet test
+ - name: Pack
+ working-directory: dotnet/ComponentClassRegistry
+ run: dotnet pack
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/.editorconfig b/dotnet/ComponentClassRegistry/.editorconfig
new file mode 100644
index 0000000..8bf5e6c
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/.editorconfig
@@ -0,0 +1,201 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# New line preferences
+end_of_line = lf
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:suggestion
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:suggestion
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = false
+csharp_new_line_before_else = false
+csharp_new_line_before_finally = false
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = none
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = false
+csharp_preserve_single_line_statements = false
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
diff --git a/dotnet/ComponentClassRegistry/CliLib/CliLib.csproj b/dotnet/ComponentClassRegistry/CliLib/CliLib.csproj
new file mode 100644
index 0000000..694fd19
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/CliLib/CliLib.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ enable
+ NSA Cybersecurity Directorate
+ Apache-2.0
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/CliLib/src/CliOptions.cs b/dotnet/ComponentClassRegistry/CliLib/src/CliOptions.cs
new file mode 100644
index 0000000..ba79c43
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/CliLib/src/CliOptions.cs
@@ -0,0 +1,57 @@
+using CommandLine;
+using System.Data;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+
+namespace CliLib;
+public class CliOptions {
+ [Option("print-v2", SetName = "print", Default = false, HelpText = "Print hardware manifest data in JSON format according to Platform Certificate v1.1.")]
+ public bool PrintV2 {
+ get; set;
+ }
+ [Option("print-v3", SetName = "print", Default = false, HelpText = "Print hardware manifest data in JSON format according to Platform Certificate v2.0 with component identifiers wrapped in ComponentIdentifierV11Traits.")]
+ public bool PrintV3 {
+ get; set;
+ }
+
+ private static void HandleParseError(IEnumerable errs) {
+ //handle errors
+ Console.WriteLine("There was a command line error: " + errs.ToString());
+ }
+
+ public static CliOptions? ParseArguments(string[] args) {
+ CliOptions? cli = new();
+ ParserResult cliParseResult =
+ CommandLine.Parser.Default.ParseArguments(args)
+ .WithParsed(parsed => cli = parsed)
+ .WithNotParsed(HandleParseError);
+ if (cliParseResult.Tag == ParserResultType.NotParsed) {
+ Console.WriteLine("Could not parse command line arguments.");
+ cli = null;
+ }
+
+ return cli;
+ }
+
+ public static string[] SplitArgs(string argString) {
+ return argString.SplitArgs(true);
+ }
+
+ public static int IsUserPrivileged() {
+ bool priv = false;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ if (LinuxImports.geteuid() == 0) {
+ priv = true;
+ }
+ } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) {
+ priv = true;
+ }
+ }
+
+ int result = priv ? (int)ClientExitCodes.SUCCESS : (int)ClientExitCodes.NOT_PRIVILEGED;
+
+ return result;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/CliLib/src/ClientExitCodes.cs b/dotnet/ComponentClassRegistry/CliLib/src/ClientExitCodes.cs
new file mode 100644
index 0000000..8dd0ecb
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/CliLib/src/ClientExitCodes.cs
@@ -0,0 +1,9 @@
+namespace CliLib;
+
+public enum ClientExitCodes {
+ SUCCESS = 0, // Full successful program completion
+ FAIL = 1, // Unknown/Generic failure resulting in exit
+ NOT_PRIVILEGED = 23, // Client not run as root
+ CLI_PARSE_ERROR = 24, // Command line parsing failure
+ GATHER_HW_MANIFEST_FAIL = 43, // SMBIOS Hardware information retrieval failure
+}
diff --git a/dotnet/ComponentClassRegistry/CliLib/src/LinuxImports.cs b/dotnet/ComponentClassRegistry/CliLib/src/LinuxImports.cs
new file mode 100644
index 0000000..c6a6f4a
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/CliLib/src/LinuxImports.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace CliLib;
+
+public class LinuxImports {
+ ///
+ /// This method is imported to query the Linux Kernel whether the program was run with privileges.
+ ///
+ /// The Euid.
+ [DllImport("libc", SetLastError = true)]
+ internal static extern uint geteuid();
+}
diff --git a/dotnet/ComponentClassRegistry/ComponentClassRegistry.sln b/dotnet/ComponentClassRegistry/ComponentClassRegistry.sln
new file mode 100644
index 0000000..d7c57ee
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/ComponentClassRegistry.sln
@@ -0,0 +1,231 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34607.119
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Smbios", "Smbios\Smbios.csproj", "{D266779A-B24E-48DC-9709-1EFF36CF668C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmbiosTests", "SmbiosTest\SmbiosTests.csproj", "{5A757CBB-5C96-4C55-889A-FBFC42E30130}"
+ ProjectSection(ProjectDependencies) = postProject
+ {D266779A-B24E-48DC-9709-1EFF36CF668C} = {D266779A-B24E-48DC-9709-1EFF36CF668C}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmbiosCli", "SmbiosCli\SmbiosCli.csproj", "{7ACB561A-240B-4316-AEBC-C6AF781CA132}"
+ ProjectSection(ProjectDependencies) = postProject
+ {D266779A-B24E-48DC-9709-1EFF36CF668C} = {D266779A-B24E-48DC-9709-1EFF36CF668C}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pcie", "Pcie\Pcie.csproj", "{BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}"
+ ProjectSection(ProjectDependencies) = postProject
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50} = {95E3A55E-7A5B-4704-B824-D0352B9A9D50}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PcieLibTests", "PcieTests\PcieLibTests.csproj", "{5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}"
+ ProjectSection(ProjectDependencies) = postProject
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE} = {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PcieWinCfgMgr", "PcieWin\PcieWinCfgMgr.csproj", "{1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PcieLib", "PcieLib\PcieLib.csproj", "{95E3A55E-7A5B-4704-B824-D0352B9A9D50}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliLib", "CliLib\CliLib.csproj", "{E44C39B3-2483-407B-9266-8376F0C92F58}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Storage", "Storage\Storage.csproj", "{C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PcieCli", "PcieCli\PcieCli.csproj", "{0571F8D0-32E8-4A2C-B9A9-1003174177C1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageLib", "StorageLib\StorageLib.csproj", "{026F4BF5-E257-4241-8E81-A77255A36DD7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageTests", "StorageLibTests\StorageTests.csproj", "{4EB1371D-E35C-467F-840A-A2D58AC69939}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageNvme", "StorageNvme\StorageNvme.csproj", "{616DCD40-2795-4312-AF03-95A260C92874}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageCli", "StorageCli\StorageCli.csproj", "{3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Debug|x64.Build.0 = Debug|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Debug|x86.Build.0 = Debug|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Release|x64.ActiveCfg = Release|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Release|x64.Build.0 = Release|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Release|x86.ActiveCfg = Release|Any CPU
+ {D266779A-B24E-48DC-9709-1EFF36CF668C}.Release|x86.Build.0 = Release|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Debug|x64.Build.0 = Debug|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Debug|x86.Build.0 = Debug|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Release|x64.ActiveCfg = Release|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Release|x64.Build.0 = Release|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Release|x86.ActiveCfg = Release|Any CPU
+ {5A757CBB-5C96-4C55-889A-FBFC42E30130}.Release|x86.Build.0 = Release|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Debug|x64.Build.0 = Debug|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Debug|x86.Build.0 = Debug|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Release|x64.ActiveCfg = Release|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Release|x64.Build.0 = Release|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Release|x86.ActiveCfg = Release|Any CPU
+ {7ACB561A-240B-4316-AEBC-C6AF781CA132}.Release|x86.Build.0 = Release|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Debug|x64.Build.0 = Debug|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Debug|x86.Build.0 = Debug|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Release|x64.ActiveCfg = Release|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Release|x64.Build.0 = Release|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Release|x86.ActiveCfg = Release|Any CPU
+ {BCAD4500-DD66-42C4-8E24-C0A7ADB1CAEE}.Release|x86.Build.0 = Release|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Debug|x64.Build.0 = Debug|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Debug|x86.Build.0 = Debug|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Release|x64.ActiveCfg = Release|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Release|x64.Build.0 = Release|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Release|x86.ActiveCfg = Release|Any CPU
+ {5EDE8231-CE93-4BC0-8D05-18EF352A1CC0}.Release|x86.Build.0 = Release|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Debug|x64.Build.0 = Debug|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Debug|x86.Build.0 = Debug|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Release|x64.ActiveCfg = Release|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Release|x64.Build.0 = Release|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Release|x86.ActiveCfg = Release|Any CPU
+ {1ED5A0F3-D0DD-4A32-B351-E6AF7481A663}.Release|x86.Build.0 = Release|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Debug|x64.Build.0 = Debug|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Debug|x86.Build.0 = Debug|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Release|x64.ActiveCfg = Release|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Release|x64.Build.0 = Release|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Release|x86.ActiveCfg = Release|Any CPU
+ {95E3A55E-7A5B-4704-B824-D0352B9A9D50}.Release|x86.Build.0 = Release|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Debug|x64.Build.0 = Debug|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Debug|x86.Build.0 = Debug|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Release|x64.ActiveCfg = Release|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Release|x64.Build.0 = Release|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Release|x86.ActiveCfg = Release|Any CPU
+ {E44C39B3-2483-407B-9266-8376F0C92F58}.Release|x86.Build.0 = Release|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Debug|x64.Build.0 = Debug|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Debug|x86.Build.0 = Debug|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Release|x64.ActiveCfg = Release|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Release|x64.Build.0 = Release|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Release|x86.ActiveCfg = Release|Any CPU
+ {C25CA57B-7460-4D1F-B8FA-E0DE1B200BA6}.Release|x86.Build.0 = Release|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Debug|x64.Build.0 = Debug|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Debug|x86.Build.0 = Debug|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Release|x64.ActiveCfg = Release|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Release|x64.Build.0 = Release|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Release|x86.ActiveCfg = Release|Any CPU
+ {0571F8D0-32E8-4A2C-B9A9-1003174177C1}.Release|x86.Build.0 = Release|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Debug|x64.Build.0 = Debug|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Debug|x86.Build.0 = Debug|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Release|x64.ActiveCfg = Release|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Release|x64.Build.0 = Release|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Release|x86.ActiveCfg = Release|Any CPU
+ {026F4BF5-E257-4241-8E81-A77255A36DD7}.Release|x86.Build.0 = Release|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Debug|x64.Build.0 = Debug|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Debug|x86.Build.0 = Debug|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Release|x64.ActiveCfg = Release|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Release|x64.Build.0 = Release|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Release|x86.ActiveCfg = Release|Any CPU
+ {4EB1371D-E35C-467F-840A-A2D58AC69939}.Release|x86.Build.0 = Release|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Debug|x64.Build.0 = Debug|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Debug|x86.Build.0 = Debug|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Release|Any CPU.Build.0 = Release|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Release|x64.ActiveCfg = Release|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Release|x64.Build.0 = Release|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Release|x86.ActiveCfg = Release|Any CPU
+ {616DCD40-2795-4312-AF03-95A260C92874}.Release|x86.Build.0 = Release|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Debug|x64.Build.0 = Debug|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Debug|x86.Build.0 = Debug|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Release|x64.ActiveCfg = Release|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Release|x64.Build.0 = Release|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Release|x86.ActiveCfg = Release|Any CPU
+ {3E048E3D-F855-4C79-9EAC-8C0B3589ECAE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C81E8F1B-E509-4E9D-A90D-CA82241643FB}
+ EndGlobalSection
+EndGlobal
diff --git a/dotnet/ComponentClassRegistry/Pcie/Pcie.csproj b/dotnet/ComponentClassRegistry/Pcie/Pcie.csproj
new file mode 100644
index 0000000..83c0cce
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/Pcie.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net8.0
+ enable
+ enable
+ linux-x64;win-x64
+ NSA Cybersecurity Directorate
+ paccor.ComponentClassRegistry.Pcie
+ 1.0.0
+ paccor;platform;certificate;hardware;manifest;component;class;registry;pci;pcie;evidence;collection
+ README.md
+ Apache-2.0
+ icon.png
+
+ true
+ true
+ true
+ snupkg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/Pcie/Properties/PublishProfiles/FolderProfile.pubxml b/dotnet/ComponentClassRegistry/Pcie/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..3cd5745
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,15 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\net8.0\publish\
+ FileSystem
+ <_TargetId>Folder
+ net8.0
+ false
+
+
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/Pcie/README.md b/dotnet/ComponentClassRegistry/Pcie/README.md
new file mode 100644
index 0000000..1acd87f
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/README.md
@@ -0,0 +1,7 @@
+paccor is a popular solution for creating the TCG Platform Certificate. The platform certificate enables traceability in the hardware supply chain of a computing device.
+
+
+This hardware manifest plugin encapsulates paccor's shell scripts into a .NET package. This allows paccor's hardware evidence collection to be integrated into any .NET program that can [manage](https://www.nuget.org/packages/paccor.HardwareManifestPluginManager) the [IHardwareManifestPlugin interface](https://www.nuget.org/packages/paccor.HardwareManifestPlugin).
+
+See the [HIRS .NET Provisioner](https://github.com/nsacyber/hirs/) code on github for an example usage of this plugin.
+
diff --git a/dotnet/ComponentClassRegistry/Pcie/src/LinuxImports.cs b/dotnet/ComponentClassRegistry/Pcie/src/LinuxImports.cs
new file mode 100644
index 0000000..c432853
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/src/LinuxImports.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Pcie;
+public class LinuxImports {
+ ///
+ /// This method is imported to query the Linux Kernel whether the program was run with privileges.
+ ///
+ /// The Euid.
+ [DllImport("libc", SetLastError = true)]
+ internal static extern uint geteuid();
+}
diff --git a/dotnet/ComponentClassRegistry/Pcie/src/Pcie.cs b/dotnet/ComponentClassRegistry/Pcie/src/Pcie.cs
new file mode 100644
index 0000000..39fc2ea
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/src/Pcie.cs
@@ -0,0 +1,172 @@
+using PcieLib;
+using PcieWinCfgMgr;
+using System.Runtime.InteropServices;
+
+namespace Pcie;
+
+public class Pcie {
+
+ public IDictionary> Devices {
+ get;
+ private init;
+ } = new Dictionary>();
+
+ ///
+ /// True if construction was successful. False if any errors found.
+ ///
+ public bool Valid {
+ get;
+ private set;
+ }
+
+ public static Pcie GetPcie() {
+ Pcie pcie = new();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ pcie.Valid = CollectPcieWindows(pcie.Devices);
+ } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ pcie.Valid = CollectPcieLinux(pcie.Devices);
+ }
+
+ // Parse Data should be the same on Windows and Linux if I can get raw PCIe config data on Windows.
+
+ return pcie;
+ }
+
+ private static bool CollectPcieWindows(IDictionary> devices) {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ return false;
+ }
+
+ bool gotInstanceIds = PciWinCfgMgr.GetAllPciDeviceInstanceIds(out List pciDeviceInstanceIds);
+
+ foreach (string pciDeviceInstanceId in pciDeviceInstanceIds) {
+ bool gotConfig = PciWinCfgMgr.CreateMockConfigBufferFromPciDeviceInstanceId(out byte[] config, out bool isLittleEndian, pciDeviceInstanceId);
+
+ if (!gotConfig) {
+ continue;
+ }
+
+ PcieDevice device = new(config, Array.Empty(), isLittleEndian);
+
+ // For Network Adapters, attempt to get the MAC
+ switch (device.ClassCode.Hex[..4]) {
+ case "0200":
+ case "0280":
+ case "0D11":
+ // Ask NetAdapter for the permanent address.
+ Task> task = Task.Run(() => PowershellMAC(pciDeviceInstanceId));
+ bool foundMac = ParseMacAddressFromResults(out string mac, task);
+ if (foundMac) {
+ device.NetworkMac = Convert.FromHexString(mac);
+ }
+ break;
+ }
+
+ if (!devices.ContainsKey(device.ClassCode.Class)) {
+ devices.Add(device.ClassCode.Class, new List());
+ }
+ devices[device.ClassCode.Class].Add(device);
+ }
+
+ return true;
+ }
+
+ private static bool CollectPcieLinux(IDictionary> devices) {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ return false;
+ }
+ // Is this program running with elevated privileges?
+ if (LinuxImports.geteuid() != 0) {
+ return false;
+ }
+
+ const string pciBusFolderRoot = "/sys/bus/pci/devices";
+ string[] deviceFolders = Directory.GetDirectories(pciBusFolderRoot);
+ foreach (string folder in deviceFolders) {
+ string folderPath = Path.GetFullPath(Path.Combine(pciBusFolderRoot, folder));
+
+ // Define paths to collect PCIe data made available by the Linux Kernel
+ string configFile = Path.GetFullPath(Path.Combine(folderPath, "config"));
+ string vpdFile = Path.GetFullPath(Path.Combine(folderPath, "vpd"));
+ string netFolder = Path.GetFullPath(Path.Combine(folderPath, "net"));
+ byte[] configBytes = Array.Empty();
+ byte[] vpdBytes = Array.Empty();
+
+ // Read bytes from these files. If there's no config header, don't collect vpd.
+ if (File.Exists(configFile)) {
+ FileInfo configFileInfo = new(configFile);
+ configBytes = File.ReadAllBytes(configFile);
+ if (File.Exists(vpdFile)) {
+ vpdBytes = File.ReadAllBytes(vpdFile);
+ }
+ }
+
+ if (configBytes.Length <= 0) {
+ continue;
+ }
+
+ PcieDevice device = new(configBytes, vpdBytes);
+
+ // For Network Adapters, attempt to get the MAC
+ if (Directory.Exists(netFolder)) {
+ string[] interfaceFolders = Directory.GetDirectories(netFolder);
+
+ if (interfaceFolders.Length >= 1) {
+ string interfaceName = Path.GetRelativePath(netFolder, interfaceFolders[0]);
+ switch (device.ClassCode.Hex[..4]) {
+ case "0200":
+ case "0280":
+ case "0D11":
+ // Ask ethtool for the permanent address.
+ Task> task = Task.Run(() => EthtoolP(interfaceName));
+ bool foundMac = ParseMacAddressFromResults(out string mac, task);
+ if (foundMac) {
+ device.NetworkMac = Convert.FromHexString(mac);
+ }
+ break;
+ }
+ }
+ }
+
+ if (!devices.ContainsKey(device.ClassCode.Class)) {
+ devices.Add(device.ClassCode.Class, new List());
+ }
+ devices[device.ClassCode.Class].Add(device);
+ }
+
+ return true;
+ }
+
+ private static bool ParseMacAddressFromResults(out string mac, Task> task) {
+ mac = "";
+ bool result = false;
+
+ Tuple results = task.Result;
+ if (task.Exception == null) {
+ mac = results.Item3;
+ // Parse results of output
+ if (!string.IsNullOrWhiteSpace(mac)) {
+ mac = CleanMacAddress(mac);
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ public static string CleanMacAddress(string mac) {
+ mac = mac.Replace("Permanent address", "");
+ mac = mac.Replace(":", "");
+ mac = mac.Replace("-", "");
+ mac = mac.Trim();
+ return mac;
+ }
+
+ private static async Task> EthtoolP(string interfaceName) {
+ return await ShellHelper.Ethtool("-P " + interfaceName);
+ }
+ private static async Task> PowershellMAC(string interfaceId) {
+ return await ShellHelper.Powershell("Get-NetAdapter | where PNPDeviceID -eq '" + interfaceId + "' | select MacAddress -ExpandProperty MacAddress");
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/Pcie/src/PcieHardwareManifestPlugin.cs b/dotnet/ComponentClassRegistry/Pcie/src/PcieHardwareManifestPlugin.cs
new file mode 100644
index 0000000..bd48cba
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/src/PcieHardwareManifestPlugin.cs
@@ -0,0 +1,80 @@
+using HardwareManifestPlugin;
+using HardwareManifestProto;
+using OidsProto;
+using PcieLib;
+
+namespace Pcie;
+
+public sealed class PcieHardwareManifestPlugin : HardwareManifestPluginBase {
+ public static readonly string TraitDescription = "PCIe-based Component Class Registry";
+ public static readonly string TraitDescriptionUri = "https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCIe_Component_Class_Registry_v1_r18_pub10272021.pdf";
+ public static readonly string PluginName = "paccor.componentclassregistry.pcie";
+ public static readonly string PluginDescription = "Collect hardware identifiers according to the PCIe Component Class Registry.";
+
+ public PcieHardwareManifestPlugin() {
+ Name = PluginName;
+ Description = PluginDescription;
+ CollectsV2HardwareInformation = true;
+ CollectsV3HardwareInformation = false;
+ }
+
+ public override bool GatherHardwareIdentifiers() {
+ bool result = false;
+
+ Pcie pcie = Pcie.GetPcie();
+
+ if (!pcie.Valid) {
+ return result;
+ }
+
+ AddComponentsToManifestV2(pcie.Devices, ManifestV2);
+ ManifestV3 = HardwareManifestConverter.FromManifestV2(ManifestV2, TraitDescription, TraitDescriptionUri);
+
+ result = pcie.Valid;
+
+ return true;
+ }
+
+ public static void AddComponentsToManifestV2(IDictionary> devices, ManifestV2 manifest) {
+ string pcieRegistryOid = OidsUtils.Find(TCG_REGISTRY_COMPONENTCLASS_NODE.TcgRegistryComponentclassPcie).Oid;
+
+ foreach (int type in devices.Keys) {
+ foreach (PcieDevice device in devices[type]) {
+ ComponentIdentifier component = new() {
+ COMPONENTCLASS = new ComponentClass {
+ COMPONENTCLASSREGISTRY = pcieRegistryOid,
+ COMPONENTCLASSVALUE = "00" + device.ClassCode.Hex
+ },
+ MANUFACTURER = device.VendorId.Hex + ":" + device.SubsystemVendorId.Hex + ":" + device.VpdMn,
+ MODEL = device.DeviceId.Hex + ":" + device.SubsystemId.Hex + ":" + device.VpdPn,
+ REVISION = device.RevisionId.ToString("X2")
+ };
+ // Don't add SERIAL if both values are empty.
+ if (device.DeviceSerialNumber.Length > 0 || !string.IsNullOrEmpty(device.VpdSn)) {
+ component.SERIAL = Convert.ToHexString(device.DeviceSerialNumber) + ":" + device.VpdSn;
+ }
+ if (device.NetworkMac.Length != 0) {
+ string mac = Convert.ToHexString(device.NetworkMac).PadLeft(12, '0');
+ switch (device.ClassCode.Hex[..4]) {
+ case "0200": // Ethernet
+ component.ADDRESSES.Add(new Address() {
+ ETHERNETMAC = mac
+ });
+ break;
+ case "0D11": // Bluetooth
+ component.ADDRESSES.Add(new Address() {
+ BLUETOOTHMAC = mac
+ });
+ break;
+ case "0280": // Wireless
+ component.ADDRESSES.Add(new Address() {
+ WLANMAC = mac
+ });
+ break;
+ }
+ }
+ manifest.COMPONENTS.Add(component);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/Pcie/src/ShellHelper.cs b/dotnet/ComponentClassRegistry/Pcie/src/ShellHelper.cs
new file mode 100644
index 0000000..76014b1
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Pcie/src/ShellHelper.cs
@@ -0,0 +1,57 @@
+using System.Diagnostics;
+
+namespace Pcie;
+public static class ShellHelper {
+ public static Task> Ethtool(string arguments) {
+ ProcessStartInfo info = new() {
+ FileName = "ethtool",
+ Arguments = "-P '" + arguments + "'",
+ WorkingDirectory = Path.GetDirectoryName("ethtool")?.Replace("\\", "/"),
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ return Execute(info);
+ }
+ public static Task> Powershell(string arguments) {
+ char ch = '"'; // couldn't get escaping to work properly without this method
+ ProcessStartInfo info = new() {
+ FileName = "powershell.exe",
+ Arguments = "-NoProfile -ExecutionPolicy Bypass -Command " + ch + arguments + ch,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ return Execute(info);
+ }
+
+ private static Task> Execute(ProcessStartInfo info) {
+ TaskCompletionSource> source = new();
+ Process process = new() {
+ StartInfo = info,
+ EnableRaisingEvents = true
+ };
+
+ process.Exited += (sender, args) => {
+ source.SetResult(new Tuple(process.ExitCode, process.StandardError.ReadToEnd(), process.StandardOutput.ReadToEnd()));
+ if (process.ExitCode != 0) {
+ source.SetException(new Exception($"Command `{process.StartInfo.FileName} {process.StartInfo.Arguments}` failed with exit code `{process.ExitCode}`"));
+ }
+
+ process.Dispose();
+ };
+
+ try {
+ process.Start();
+ process.WaitForExit();
+ } catch (Exception e) {
+ source.SetException(e);
+ } finally {
+ process.Dispose();
+ }
+
+ return source.Task;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/PcieCli/PcieCli.csproj b/dotnet/ComponentClassRegistry/PcieCli/PcieCli.csproj
new file mode 100644
index 0000000..f60dd6f
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieCli/PcieCli.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ linux-x64;win-x64
+ PcieCli.PcieCli
+ true
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/PcieCli/Properties/PublishProfiles/FolderProfile.pubxml b/dotnet/ComponentClassRegistry/PcieCli/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..6b70635
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieCli/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,18 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\net8.0\publish\win-x64\
+ FileSystem
+ <_TargetId>Folder
+ net8.0
+ win-x64
+ true
+ false
+ false
+
+
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieCli/src/Program.cs b/dotnet/ComponentClassRegistry/PcieCli/src/Program.cs
new file mode 100644
index 0000000..8c1c754
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieCli/src/Program.cs
@@ -0,0 +1,41 @@
+using CliLib;
+using Pcie;
+using System.Runtime.InteropServices;
+
+namespace PcieCli;
+public class PcieCli {
+ public static int Main(string[] args) {
+ int returnCode = (int)ClientExitCodes.SUCCESS;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ // Linux requires sudo
+ int result = CliOptions.IsUserPrivileged();
+ if (result != (int)ClientExitCodes.SUCCESS) {
+ Console.WriteLine("PCI vpd and some other data retrieval on Linux requires admin privileges. Please run as root.");
+ return result;
+ }
+ }
+
+ CliOptions? cli = CliOptions.ParseArguments(args);
+
+ if (cli == null) {
+ return (int)ClientExitCodes.CLI_PARSE_ERROR;
+ }
+
+ PcieHardwareManifestPlugin plugin = new();
+ if (!plugin.GatherHardwareIdentifiers()) {
+ Console.WriteLine("Pci hardware information gathered was not valid.");
+ return (int)ClientExitCodes.GATHER_HW_MANIFEST_FAIL;
+ }
+
+ // All smbios data should be validated at this point.
+ if (cli.PrintV2 || (!cli.PrintV2 && !cli.PrintV3)) { // V2 should be printed by default not matter what
+ Console.WriteLine(plugin.ManifestV2.ToString());
+ }
+ if (cli.PrintV3) {
+ Console.WriteLine(plugin.ManifestV3.ToString());
+ }
+
+ return returnCode;
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieLib/PcieLib.csproj b/dotnet/ComponentClassRegistry/PcieLib/PcieLib.csproj
new file mode 100644
index 0000000..92f602f
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieLib/PcieLib.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net8.0
+ linux-x64;win-x64
+ enable
+ enable
+ NSA Cybersecurity Directorate
+ Apache-2.0
+
+
+
diff --git a/dotnet/ComponentClassRegistry/PcieLib/Properties/PublishProfiles/FolderProfile.pubxml b/dotnet/ComponentClassRegistry/PcieLib/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..c269f30
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieLib/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,18 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\net8.0\publish\win-x64\
+ FileSystem
+ <_TargetId>Folder
+ net8.0
+ win-x64
+ false
+ false
+ false
+
+
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieLib/src/ClassCode.cs b/dotnet/ComponentClassRegistry/PcieLib/src/ClassCode.cs
new file mode 100644
index 0000000..f34d788
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieLib/src/ClassCode.cs
@@ -0,0 +1,43 @@
+namespace PcieLib;
+
+public class ClassCode {
+ public byte[] Data {
+ get;
+ private set;
+ } = Array.Empty();
+ public byte Class {
+ get;
+ private set;
+ }
+ public byte SubClass {
+ get;
+ private set;
+ }
+ public byte ProgrammingInterface {
+ get;
+ private set;
+ }
+
+ public string Hex {
+ get;
+ private set;
+ } = "";
+
+ public ClassCode() {
+ }
+
+ public ClassCode(byte[] inData, bool littleEndian = true) {
+ if (inData.Length <= 0 || inData.Length > 3) {
+ return;
+ }
+
+ Data = inData;
+ if (littleEndian) {
+ Array.Reverse(Data);
+ }
+ Class = Data[0];
+ SubClass = Data[1];
+ ProgrammingInterface = Data[2];
+ Hex = Convert.ToHexString(Data).PadLeft(6, '0');
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/PcieLib/src/PcieDevice.cs b/dotnet/ComponentClassRegistry/PcieLib/src/PcieDevice.cs
new file mode 100644
index 0000000..68c5178
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieLib/src/PcieDevice.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace PcieLib;
+
+public class PcieDevice {
+ public byte[] Config {
+ get;
+ private set;
+ }
+ public byte[] Vpd {
+ get;
+ private set;
+ }
+
+ public int ConfigType {
+ get;
+ private set;
+ }
+ public ClassCode ClassCode {
+ get;
+ private set;
+ }
+ public PcieId VendorId {
+ get;
+ private set;
+ }
+ public PcieId DeviceId {
+ get;
+ private set;
+ }
+ public PcieId SubsystemVendorId {
+ get;
+ private set;
+ }
+ public PcieId SubsystemId {
+ get;
+ private set;
+ }
+ public byte RevisionId {
+ get;
+ private set;
+ }
+ public byte[] DeviceSerialNumber {
+ get;
+ private set;
+ }
+
+ public string VpdMn {
+ get;
+ private set;
+ } = "";
+ public string VpdPn {
+ get;
+ private set;
+ } = "";
+ public string VpdSn {
+ get;
+ private set;
+ } = "";
+
+ public byte[] NetworkMac { // Need a driver to fetch the address out of BAR.
+ get;
+ set; // Can be set after object is created.
+ } = Array.Empty();
+
+ public PcieDevice(byte[] inConfig, byte[] inVpd) : this(inConfig, inVpd, true) {
+ }
+
+ public PcieDevice(byte[] inConfig, byte[] inVpd, bool littleEndian) {
+ Config = inConfig.Length > 0 ? inConfig : Array.Empty();
+
+ ConfigType = (Config.Length > 0xD) ? Config[0xE] & 0x7 : 0;
+
+ VendorId = (Config.Length > 1) ? new PcieId(inConfig[..0x2], littleEndian) : new PcieId();
+ DeviceId = (Config.Length > 3) ? new PcieId(inConfig[0x2..0x4], littleEndian) : new PcieId();
+ RevisionId = (Config.Length > 7) ? inConfig[0x8] : (byte)0;
+ ClassCode = (Config.Length > 10) ? new ClassCode(inConfig[0x9..0xC], littleEndian) : new ClassCode();
+ SubsystemVendorId = (ConfigType == 0 && Config.Length > 0x2C) ? new PcieId(inConfig[0x2C..0x2E], littleEndian) : new PcieId();
+ SubsystemId = (ConfigType == 0 && Config.Length > 0x2E) ? new PcieId(inConfig[0x2E..0x30], littleEndian) : new PcieId();
+ DeviceSerialNumber = (Config.Length > 0x100) ? SeekDsn(inConfig[0x100..], littleEndian) : Array.Empty();
+
+ if (inVpd.Length <= 0) {
+ Vpd = Array.Empty();
+ return;
+ }
+
+ Vpd = inVpd;
+ ParseVpd(Vpd, out string pn, out string mn, out string sn, littleEndian);
+ VpdPn = pn;
+ VpdMn = mn;
+ VpdSn = sn;
+ }
+ public static byte[] SeekDsn(byte[] inData, bool littleEndian = true) {
+ byte[] dsn = Array.Empty();
+ int pos = 0;
+ while((pos+12) < inData.Length) {
+ byte[] capIdBytes = inData[pos..(pos + 2)];
+ if (littleEndian) {
+ Array.Reverse(capIdBytes);
+ }
+
+ if (capIdBytes[0] == 0 && capIdBytes[1] == 3) {
+ dsn = inData[(pos + 4)..(pos + 12)];
+ if (littleEndian) {
+ Array.Reverse(dsn);
+ }
+ break;
+ } else {
+ byte[] nextCapBytes = inData[(pos + 2)..(pos + 4)];
+ if (littleEndian) {
+ Array.Reverse(nextCapBytes);
+ }
+ ushort nextCap = BinaryPrimitives.ReadUInt16BigEndian(nextCapBytes);
+ pos = nextCap - 0x100;
+ }
+ }
+ return dsn;
+ }
+
+ public static void ParseVpd(byte[] inData, out string pn, out string mn, out string sn, bool littleEndian = true) {
+ pn = "";
+ mn = "";
+ sn = "";
+
+ int pos = 0;
+ byte tagId = 0;
+ ushort tagDataLength = 0;
+
+ // Search for VPD-R tag
+ while (pos < inData.Length) {
+ tagId = inData[pos];
+
+ if (tagId == 0x0F) { // End Tag; Stop
+ return;
+ }
+
+ tagDataLength = 0;
+ bool largeTag = (tagId & 0x80) == 0x80;
+ if (largeTag) { // Large Tag
+ byte[] tagDataLengthBytes = inData[(pos+1)..(pos + 3)];
+ if (littleEndian) {
+ Array.Reverse(tagDataLengthBytes);
+ }
+ tagDataLength = BinaryPrimitives.ReadUInt16BigEndian(tagDataLengthBytes);
+ } else {
+ tagDataLength = (ushort)(tagId & 0x3);
+ }
+
+ if (tagId == 0x90) { // VPD-R Tag
+ break;
+ }
+
+ pos = pos + 1 + tagDataLength;
+ if (largeTag) {
+ pos += 2;
+ }
+ }
+
+ if (tagId != 0x90) { // Stop if VPD-R not found
+ return;
+ }
+
+ // At this point, pos should be pointing at the VPD-R tag id byte
+ // and tagDataLength should be the length of the VPD-R tag
+ int tagIdPos = pos;
+ pos += 3;
+ int tagEnd = tagIdPos + 3 + tagDataLength;
+
+ // Search for desired keywords
+ while (pos < tagEnd) {
+ string keyword = Encoding.ASCII.GetString(inData[pos..(pos + 2)]);
+ int len = inData[pos+2];
+ int start = pos+3;
+ int end = start + len; // C# byte range end is not inclusive
+
+ if (end > inData.Length) {
+ break;
+ }
+
+ byte[] keywordDataBytes = inData[start..end];
+
+ switch (keyword) {
+ case "PN":
+ case "pn":
+ case "Pn":
+ case "pN":
+ pn = Encoding.ASCII.GetString(keywordDataBytes);
+ break;
+ case "MN":
+ case "mn":
+ case "Mn":
+ case "mN":
+ mn = Encoding.ASCII.GetString(keywordDataBytes);
+ break;
+ case "SN":
+ case "sn":
+ case "Sn":
+ case "sN":
+ sn = Encoding.ASCII.GetString(keywordDataBytes);
+ break;
+ case "RV":
+ case "rv":
+ case "Rv":
+ case "rV":
+ byte checksum = inData[pos+3];
+ byte calc = 0;
+ for (int i = 0; i < (pos + 3); i++) {
+ calc += inData[i];
+ }
+
+ if (checksum != calc) {
+ pn = "";
+ mn = "";
+ sn = "";
+ }
+
+ break;
+ }
+
+ pos = end;
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieLib/src/PcieId.cs b/dotnet/ComponentClassRegistry/PcieLib/src/PcieId.cs
new file mode 100644
index 0000000..43030db
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieLib/src/PcieId.cs
@@ -0,0 +1,34 @@
+namespace PcieLib;
+
+public class PcieId {
+ public byte[] Data {
+ get;
+ private set;
+ } = Array.Empty();
+
+ public ushort Id {
+ get;
+ private set;
+ }
+
+ public string Hex {
+ get;
+ private set;
+ } = "";
+
+ public PcieId() {
+ }
+
+ public PcieId(byte[] inData, bool littleEndian = true) {
+ if (inData.Length <= 0 || inData.All(singleByte => singleByte == 0)) {
+ return;
+ }
+
+ Data = inData;
+ if (littleEndian) {
+ Array.Reverse(Data);
+ }
+ Id = BitConverter.ToUInt16(Data);
+ Hex = Convert.ToHexString(Data).PadLeft(4, '0');
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieTests/PcieLibTests.csproj b/dotnet/ComponentClassRegistry/PcieTests/PcieLibTests.csproj
new file mode 100644
index 0000000..1f057c0
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieTests/PcieLibTests.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net8.0
+ enable
+ enable
+ NSA Cybersecurity Directorate
+ Apache-2.0
+
+ false
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/PcieTests/src/PcieDeviceTests.cs b/dotnet/ComponentClassRegistry/PcieTests/src/PcieDeviceTests.cs
new file mode 100644
index 0000000..5f0a3fc
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieTests/src/PcieDeviceTests.cs
@@ -0,0 +1,240 @@
+using HardwareManifestProto;
+using Pcie;
+using PcieLib;
+
+namespace PcieTests;
+public class PcieTests {
+ public static readonly string RegistryA2Sample1ConfigBase64 = "BAFnNAcEEAB4AggBAAAAAAAAAN4MAADAAAAAAAwAANAAAAAAAeAAAAAAAABvA6ckAAAA32AAAAAAAAAA/wEAAAMA4IB4eHh4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAwAE3ol94Et1kmQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ public static readonly string RegistryA2Sample1VpdBase64 = "ghQAU2FtcGxlIFBDSSBDb21wb25lbnSQjQBQThhTYW1wbGUgVlBEIFBhcnQgTnVtYmVyIDFNThtTYW1wbGUgVlBEIE1hbnVmYWN0dXJlIElEIDFTThpTYW1wbGUgVlBEIFNlcmlhbCBOdW1iZXIgMVYwKVNhbXBsZSBFeHRyYSBEYXRhIGluIFZQRCAgICAgICAgICAgICAgICAgVjIETi9BIFJWAdaRRwBWMRhTYW1wbGUgUlcgRGF0YSBpbiBWUEQgICBZQQNOL0FZQhB4eHh4eHh4eHh4eHh4eHh4WUMQeHh4eHh4eHh4eHh4eHh4eHg=";
+
+ // Registry Appendix A Sample 1 Components
+ public static readonly string RegistryA1Sample1ClassCodeHex = "010802";
+ public static readonly string RegistryA1Sample1VendorIdHex = "0104";
+ public static readonly string RegistryA1Sample1SubsystemVendorIdHex = "036F";
+ public static readonly string RegistryA1Sample1VpdMnHex = "Sample VPD Manufacture ID 1";
+ public static readonly string RegistryA1Sample1DeviceIdHex = "3467";
+ public static readonly string RegistryA1Sample1SubsystemIdHex = "24A7";
+ public static readonly string RegistryA1Sample1VpdPnHex = "Sample VPD Part Number 1";
+ public static readonly string RegistryA1Sample1DsnHex = "9964DD12785FA237";
+ public static readonly string RegistryA1Sample1VpdSnHex = "Sample VPD Serial Number 1";
+ public static readonly string RegistryA1Sample1RevisionIdHex = "78";
+
+ public static readonly string RegistryA2Sample2ConfigBase64 = "eQrrPAcEEAAAAAQGAACBAAAAAAAAAAAAAAAAAAAAANAAAAAAAAAAAEAAAABAAAAAAAAAAEAAAAAAAAAA/wECAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAREAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAOAAAAAwAfAAAAAAAAAAAACQAUAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAEABzFU//+mvS8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+ // Registry Appendix A Sample 2 Components
+ public static readonly string RegistryA1Sample2ClassCodeHex = "060400";
+ public static readonly string RegistryA1Sample2VendorIdHex = "0A79";
+ public static readonly string RegistryA1Sample2SubsystemVendorIdHex = "";
+ public static readonly string RegistryA1Sample2VpdMnHex = "";
+ public static readonly string RegistryA1Sample2DeviceIdHex = "3CEB";
+ public static readonly string RegistryA1Sample2SubsystemIdHex = "";
+ public static readonly string RegistryA1Sample2VpdPnHex = "";
+ public static readonly string RegistryA1Sample2DsnHex = "2FBDA6FFFF543107";
+ public static readonly string RegistryA1Sample2VpdSnHex = "";
+ public static readonly string RegistryA1Sample2RevisionIdHex = "00";
+
+ public static readonly string RegistryA2Sample3ConfigBase64 = "X21OKwcEEAAiAAACAAAAAAAAAN4MAADAAAAAAAwAANAAAAAAAeAAAAAAAACCQZeGAAAA32AAAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAFeIEABCDg/gAAAAAnQAAAEAASAOGNLAEwKQAAAz1FAEAAAREAAAAAAAAAAAAAAAAAAAAAEwgEAAAEAAAOAAAAAwAfAAAAAAAAAAAACQAUAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOCAeHh4eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
+ public static readonly string RegistryA2Sample3VpdBase64 = "ghQAU2FtcGxlIFBDSSBDb21wb25lbnSQbwBQThhTYW1wbGUgVlBEIFBhcnQgTnVtYmVyIDJTThpTYW1wbGUgVlBEIFNlcmlhbCBOdW1iZXIgMlYwKVNhbXBsZSBFeHRyYSBEYXRhIGluIFZQRCAgICAgICAgICAgICAgICAgVjIETi9BIFJWAf+RRwBWMRhTYW1wbGUgUlcgRGF0YSBpbiBWUEQgICBZQQNOL0FZQhB4eHh4eHh4eHh4eHh4eHh4WUMQeHh4eHh4eHh4eHh4eHh4eHg=";
+
+ // Registry Appendix A Sample 3 Components
+ public static readonly string RegistryA1Sample3ClassCodeHex = "020000";
+ public static readonly string RegistryA1Sample3VendorIdHex = "6D5F";
+ public static readonly string RegistryA1Sample3SubsystemVendorIdHex = "4182";
+ public static readonly string RegistryA1Sample3VpdMnHex = "";
+ public static readonly string RegistryA1Sample3DeviceIdHex = "2B4E";
+ public static readonly string RegistryA1Sample3SubsystemIdHex = "8697";
+ public static readonly string RegistryA1Sample3VpdPnHex = "Sample VPD Part Number 2";
+ public static readonly string RegistryA1Sample3DsnHex = "";
+ public static readonly string RegistryA1Sample3VpdSnHex = "Sample VPD Serial Number 2";
+ public static readonly string RegistryA1Sample3RevisionIdHex = "22";
+ public static readonly string RegistryA1Sample3AddressesOid = "2.23.133.17.1";
+ public static readonly string RegistryA1Sample3AddressNetworkMac = "112233445566";
+
+ public static readonly string RegistryA2Sample4ConfigBase64 = "KOUdxwcEEAAA/gMMAAAAAAAAAN4MAADAAAAAAAwAANAAAAAAAeAAAAAAAACaBr9DAAAAAAAAAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
+
+ // Registry Appendix A Sample 4 Components
+ public static readonly string RegistryA1Sample4ClassCodeHex = "0C03FE";
+ public static readonly string RegistryA1Sample4VendorIdHex = "E528";
+ public static readonly string RegistryA1Sample4SubsystemVendorIdHex = "069A";
+ public static readonly string RegistryA1Sample4VpdMnHex = "";
+ public static readonly string RegistryA1Sample4DeviceIdHex = "C71D";
+ public static readonly string RegistryA1Sample4SubsystemIdHex = "43BF";
+ public static readonly string RegistryA1Sample4VpdPnHex = "";
+ public static readonly string RegistryA1Sample4DsnHex = "";
+ public static readonly string RegistryA1Sample4VpdSnHex = "";
+ public static readonly string RegistryA1Sample4RevisionIdHex = "00";
+
+ // Registry Appendix A.1 Sample readout
+ public static readonly string[] ComponentIdentifiersInJson = new string[] {
+ // Registry Appendix A.1 Sample Component 1
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.4\"," +
+ " \"COMPONENTCLASSVALUE\": \"00" + RegistryA1Sample1ClassCodeHex +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA1Sample1VendorIdHex + ":" + RegistryA1Sample1SubsystemVendorIdHex + ":" + RegistryA1Sample1VpdMnHex + "\"," +
+ " \"MODEL\": \"" + RegistryA1Sample1DeviceIdHex + ":" + RegistryA1Sample1SubsystemIdHex + ":" + RegistryA1Sample1VpdPnHex + "\"," +
+ " \"SERIAL\": \"" + RegistryA1Sample1DsnHex + ":" + RegistryA1Sample1VpdSnHex + "\"," +
+ " \"REVISION\": \"" + RegistryA1Sample1RevisionIdHex +
+ "\" }",
+ // Registry Appendix A.1 Sample Component 2
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.4\"," +
+ " \"COMPONENTCLASSVALUE\": \"00" + RegistryA1Sample2ClassCodeHex +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA1Sample2VendorIdHex + ":" + RegistryA1Sample2SubsystemVendorIdHex + ":" + RegistryA1Sample2VpdMnHex + "\"," +
+ " \"MODEL\": \"" + RegistryA1Sample2DeviceIdHex + ":" + RegistryA1Sample2SubsystemIdHex + ":" + RegistryA1Sample2VpdPnHex + "\"," +
+ " \"SERIAL\": \"" + RegistryA1Sample2DsnHex + ":" + RegistryA1Sample2VpdSnHex + "\"," +
+ " \"REVISION\": \"" + RegistryA1Sample2RevisionIdHex +
+ "\" }",
+ // Registry Appendix A.1 Sample Component 3
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.4\"," +
+ " \"COMPONENTCLASSVALUE\": \"00" + RegistryA1Sample3ClassCodeHex +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA1Sample3VendorIdHex + ":" + RegistryA1Sample3SubsystemVendorIdHex + ":" + RegistryA1Sample3VpdMnHex + "\"," +
+ " \"MODEL\": \"" + RegistryA1Sample3DeviceIdHex + ":" + RegistryA1Sample3SubsystemIdHex + ":" + RegistryA1Sample3VpdPnHex + "\"," +
+ " \"SERIAL\": \"" + RegistryA1Sample3DsnHex + ":" + RegistryA1Sample3VpdSnHex + "\"," +
+ " \"REVISION\": \"" + RegistryA1Sample3RevisionIdHex + "\"," +
+ " \"ADDRESSES\": [ {" +
+ " \"ETHERNETMAC\": \"" + RegistryA1Sample3AddressNetworkMac +
+ "\" } ]" +
+ " }",
+ // Registry Appendix A.1 Sample Component 4
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.4\"," +
+ " \"COMPONENTCLASSVALUE\": \"00" + RegistryA1Sample4ClassCodeHex +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA1Sample4VendorIdHex + ":" + RegistryA1Sample4SubsystemVendorIdHex + ":" + RegistryA1Sample4VpdMnHex + "\"," +
+ " \"MODEL\": \"" + RegistryA1Sample4DeviceIdHex + ":" + RegistryA1Sample4SubsystemIdHex + ":" + RegistryA1Sample2VpdPnHex + "\"," +
+ " \"REVISION\": \"" + RegistryA1Sample4RevisionIdHex +
+ "\" }"
+ };
+
+
+ // Test PcieDevice
+ [Test]
+ public void TestRegistryASample1Components() {
+ byte[] config = Convert.FromBase64String(RegistryA2Sample1ConfigBase64);
+ byte[] vpd = Convert.FromBase64String(RegistryA2Sample1VpdBase64);
+ PcieDevice device = new(config, vpd, (bool)true);
+ Assert.Multiple(() => {
+ Assert.That(device.ClassCode.Hex, Is.EqualTo(RegistryA1Sample1ClassCodeHex));
+ Assert.That(device.VendorId.Hex, Is.EqualTo(RegistryA1Sample1VendorIdHex));
+ Assert.That(device.SubsystemVendorId.Hex, Is.EqualTo(RegistryA1Sample1SubsystemVendorIdHex));
+ Assert.That(device.VpdMn, Is.EqualTo(RegistryA1Sample1VpdMnHex));
+ Assert.That(device.DeviceId.Hex, Is.EqualTo(RegistryA1Sample1DeviceIdHex));
+ Assert.That(device.SubsystemId.Hex, Is.EqualTo(RegistryA1Sample1SubsystemIdHex));
+ Assert.That(device.VpdPn, Is.EqualTo(RegistryA1Sample1VpdPnHex));
+ Assert.That(device.VpdSn, Is.EqualTo(RegistryA1Sample1VpdSnHex));
+ Assert.That(device.DeviceSerialNumber, Is.EqualTo(Convert.FromHexString(RegistryA1Sample1DsnHex)));
+ Assert.That(device.RevisionId.ToString("X2"), Is.EqualTo(RegistryA1Sample1RevisionIdHex));
+ });
+ }
+
+ [Test]
+ public void TestRegistryASample2Components() {
+ byte[] config = Convert.FromBase64String(RegistryA2Sample2ConfigBase64);
+ byte[] vpd = Array.Empty();
+ PcieDevice device = new(config, vpd, (bool)true);
+ Assert.Multiple(() => {
+ Assert.That(device.ClassCode.Hex, Is.EqualTo(RegistryA1Sample2ClassCodeHex));
+ Assert.That(device.VendorId.Hex, Is.EqualTo(RegistryA1Sample2VendorIdHex));
+ Assert.That(device.SubsystemVendorId.Hex, Is.EqualTo(RegistryA1Sample2SubsystemVendorIdHex));
+ Assert.That(device.VpdMn, Is.EqualTo(RegistryA1Sample2VpdMnHex));
+ Assert.That(device.DeviceId.Hex, Is.EqualTo(RegistryA1Sample2DeviceIdHex));
+ Assert.That(device.SubsystemId.Hex, Is.EqualTo(RegistryA1Sample2SubsystemIdHex));
+ Assert.That(device.VpdPn, Is.EqualTo(RegistryA1Sample2VpdPnHex));
+ Assert.That(device.VpdSn, Is.EqualTo(RegistryA1Sample2VpdSnHex));
+ Assert.That(device.DeviceSerialNumber, Is.EqualTo(Convert.FromHexString(RegistryA1Sample2DsnHex)));
+ Assert.That(device.RevisionId.ToString("X2"), Is.EqualTo(RegistryA1Sample2RevisionIdHex));
+ });
+ }
+ [Test]
+ public void TestRegistryASample3Components() {
+ byte[] config = Convert.FromBase64String(RegistryA2Sample3ConfigBase64);
+ byte[] vpd = Convert.FromBase64String(RegistryA2Sample3VpdBase64);
+ PcieDevice device = new(config, vpd, (bool)true);
+ Assert.Multiple(() => {
+ Assert.That(device.ClassCode.Hex, Is.EqualTo(RegistryA1Sample3ClassCodeHex));
+ Assert.That(device.VendorId.Hex, Is.EqualTo(RegistryA1Sample3VendorIdHex));
+ Assert.That(device.SubsystemVendorId.Hex, Is.EqualTo(RegistryA1Sample3SubsystemVendorIdHex));
+ Assert.That(device.VpdMn, Is.EqualTo(RegistryA1Sample3VpdMnHex));
+ Assert.That(device.DeviceId.Hex, Is.EqualTo(RegistryA1Sample3DeviceIdHex));
+ Assert.That(device.SubsystemId.Hex, Is.EqualTo(RegistryA1Sample3SubsystemIdHex));
+ Assert.That(device.VpdPn, Is.EqualTo(RegistryA1Sample3VpdPnHex));
+ Assert.That(device.VpdSn, Is.EqualTo(RegistryA1Sample3VpdSnHex));
+ Assert.That(device.DeviceSerialNumber, Is.EqualTo(Convert.FromHexString(RegistryA1Sample3DsnHex)));
+ Assert.That(device.RevisionId.ToString("X2"), Is.EqualTo(RegistryA1Sample3RevisionIdHex));
+ });
+ }
+ [Test]
+ public void TestRegistryASample4Components() {
+ byte[] config = Convert.FromBase64String(RegistryA2Sample4ConfigBase64);
+ byte[] vpd = Array.Empty();
+ PcieDevice device = new(config, vpd, (bool)true);
+ Assert.Multiple(() => {
+ Assert.That(device.ClassCode.Hex, Is.EqualTo(RegistryA1Sample4ClassCodeHex));
+ Assert.That(device.VendorId.Hex, Is.EqualTo(RegistryA1Sample4VendorIdHex));
+ Assert.That(device.SubsystemVendorId.Hex, Is.EqualTo(RegistryA1Sample4SubsystemVendorIdHex));
+ Assert.That(device.VpdMn, Is.EqualTo(RegistryA1Sample4VpdMnHex));
+ Assert.That(device.DeviceId.Hex, Is.EqualTo(RegistryA1Sample4DeviceIdHex));
+ Assert.That(device.SubsystemId.Hex, Is.EqualTo(RegistryA1Sample4SubsystemIdHex));
+ Assert.That(device.VpdPn, Is.EqualTo(RegistryA1Sample4VpdPnHex));
+ Assert.That(device.VpdSn, Is.EqualTo(RegistryA1Sample4VpdSnHex));
+ Assert.That(device.DeviceSerialNumber, Is.EqualTo(Convert.FromHexString(RegistryA1Sample4DsnHex)));
+ Assert.That(device.RevisionId.ToString("X2"), Is.EqualTo(RegistryA1Sample4RevisionIdHex));
+ });
+ }
+
+ // Test PcieHardwareManifestPlugin
+ [Test]
+ public void TestRegistryAComponentList() {
+ ManifestV2 manifestV2 = new();
+ byte[] config1 = Convert.FromBase64String(RegistryA2Sample1ConfigBase64);
+ byte[] vpd1 = Convert.FromBase64String(RegistryA2Sample1VpdBase64);
+ byte[] config2 = Convert.FromBase64String(RegistryA2Sample2ConfigBase64);
+ byte[] vpd2 = Array.Empty();
+ byte[] config3 = Convert.FromBase64String(RegistryA2Sample3ConfigBase64);
+ byte[] vpd3 = Convert.FromBase64String(RegistryA2Sample3VpdBase64);
+ byte[] config4 = Convert.FromBase64String(RegistryA2Sample4ConfigBase64);
+ byte[] vpd4 = Array.Empty();
+ PcieDevice device1 = new(config1, vpd1, (bool)true);
+ PcieDevice device2 = new(config2, vpd2, (bool)true);
+ PcieDevice device3 = new(config3, vpd3, (bool)true);
+ PcieDevice device4 = new(config4, vpd4, (bool)true);
+ IList list = new List {
+ device1,
+ device2,
+ device3,
+ device4
+ };
+
+ device3.NetworkMac = Convert.FromHexString(RegistryA1Sample3AddressNetworkMac);
+
+ IDictionary> devices = CreateDictionary(list);
+
+ PcieHardwareManifestPlugin.AddComponentsToManifestV2(devices, manifestV2);
+ string jsonManifestV2 = manifestV2.ToString();
+
+ Assert.That(ComponentIdentifiersInJson, Has.Length.GreaterThan(0));
+
+ // Currently only the component with class code 0x02 will be collected
+ Assert.That(jsonManifestV2, Contains.Substring(ComponentIdentifiersInJson[2]));
+ }
+
+ public static IDictionary> CreateDictionary(IList list) {
+ IDictionary> devices = new Dictionary>();
+ foreach (PcieDevice device in list) {
+ if (!devices.ContainsKey(device.ClassCode.Class)) {
+ devices.Add(device.ClassCode.Class, new List());
+ }
+ devices[device.ClassCode.Class].Add(device);
+ }
+
+ return devices;
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieWin/PcieWinCfgMgr.csproj b/dotnet/ComponentClassRegistry/PcieWin/PcieWinCfgMgr.csproj
new file mode 100644
index 0000000..c8d4820
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/PcieWinCfgMgr.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net8.0
+ linux-x64;win-x64
+ enable
+ enable
+
+
+
diff --git a/dotnet/ComponentClassRegistry/PcieWin/Properties/PublishProfiles/FolderProfile.pubxml b/dotnet/ComponentClassRegistry/PcieWin/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..c269f30
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,18 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\net8.0\publish\win-x64\
+ FileSystem
+ <_TargetId>Folder
+ net8.0
+ win-x64
+ false
+ false
+ false
+
+
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/PcieWin/src/CfgConstants.cs b/dotnet/ComponentClassRegistry/PcieWin/src/CfgConstants.cs
new file mode 100644
index 0000000..52d1f76
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/src/CfgConstants.cs
@@ -0,0 +1,144 @@
+using System.Text.RegularExpressions;
+
+namespace PcieWinCfgMgr;
+public class CfgConstants {
+ public static readonly Guid GUID_DEVINTERFACE_DISK = new(0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); // ntddstor.h
+ public static readonly Guid GUID_PCI_DEVICE_DEVPKEY = new(0x3ab22e31, 0x8264, 0x4b4e, 0x9a, 0xf5, 0xa8, 0xd2, 0xd8, 0xe3, 0x3e, 0x62); // pciprop.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_PciDevice_BaseClass = new() { DEVPROPGUID = GUID_PCI_DEVICE_DEVPKEY, DEVPROPID = 0x03 }; // pciprop.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_PciDevice_SubClass = new() { DEVPROPGUID = GUID_PCI_DEVICE_DEVPKEY, DEVPROPID = 0x04 }; // pciprop.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_PciDevice_ProgIf = new() { DEVPROPGUID = GUID_PCI_DEVICE_DEVPKEY, DEVPROPID = 0x05 }; // pciprop.h, DEVPROP_TYPE_UINT32
+ public static readonly CfgStructs.DevPropKey DEVPKEY_PciDevice_SerialNumber = new() { DEVPROPGUID = GUID_PCI_DEVICE_DEVPKEY, DEVPROPID = 0x28 }; // pciprop.h
+ public static readonly Guid GUID_SPDRP_DEVICE_PROP_DEVPKEY = new(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0); // devpkey.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_Device_HardwareIds = new() { DEVPROPGUID = GUID_SPDRP_DEVICE_PROP_DEVPKEY, DEVPROPID = 0x03 }; // devpkey.h, DEVPROP_TYPE_STRING_LIST
+ public static readonly Guid GUID_DEVICE_RELATIONS_DEVPKEY = new(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7); // devpkey.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_Device_Parent = new() { DEVPROPGUID = GUID_DEVICE_RELATIONS_DEVPKEY, DEVPROPID = 0x08 }; // devpkey.h
+ public static readonly Guid GUID_DEVICE_OTHER_DEVPKEY = new(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2); // devpkey.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_Device_PhysicalDeviceLocation = new() { DEVPROPGUID = GUID_DEVICE_OTHER_DEVPKEY, DEVPROPID = 0x09 }; // devpkey.h
+ public static readonly Guid GUID_DEVICE_COMMON_DEVPKEY = new(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57); // devpkey.h
+ public static readonly CfgStructs.DevPropKey DEVPKEY_Device_InstanceId = new() { DEVPROPGUID = GUID_DEVICE_COMMON_DEVPKEY, DEVPROPID = 0x0100 }; // devpkey.h
+ public static readonly uint PCI_CONFIG_SIZE = 256;
+ public static readonly uint PCIE_CONFIG_SIZE = 4096;
+ public static readonly uint DEVPROP_BUFFER_SIZE = 2048;
+ public static readonly uint DEVPROP_TYPEMOD_ARRAY = 0x00001000; // devpropdef.h
+ public static readonly uint DEVPROP_TYPEMOD_LIST = 0x00002000; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_BYTE = 0x00000003; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_UINT16 = 0x00000005; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_UINT32 = 0x00000007; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_UINT64 = 0x00000009; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_STRING = 0x00000012; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_STRING_LIST = DEVPROP_TYPE_STRING | DEVPROP_TYPEMOD_LIST; // devpropdef.h
+ public static readonly uint DEVPROP_TYPE_BINARY = DEVPROP_TYPE_BYTE | DEVPROP_TYPEMOD_ARRAY; // devpropdef.h
+ public static readonly uint CM_GETIDLIST_FILTER_ENUMERATOR = 0x00000001; // cfgmfg32.h
+ public static readonly uint CM_GET_DEVICE_INTERFACE_LIST_PRESENT = 0x00000000; // cfgmfg32.h
+ public static readonly uint CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES = 0x00000001; // cfgmfg32.h
+ public static readonly uint CM_GET_DEVICE_INTERFACE_LIST_BITS = 0x00000001; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVNODE_NORMAL = 0x00000000; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVNODE_PHANTOM = 0x00000001; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVNODE_CANCELREMOVE = 0x00000002; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVNODE_NOVALIDATION = 0x00000004; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVNODE_BITS = 0x00000007; // cfgmfg32.h
+
+ public static readonly uint CM_LOCATE_DEVINST_NORMAL = CM_LOCATE_DEVNODE_NORMAL; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVINST_PHANTOM = CM_LOCATE_DEVNODE_PHANTOM; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVINST_CANCELREMOVE = CM_LOCATE_DEVNODE_CANCELREMOVE; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVINST_NOVALIDATION = CM_LOCATE_DEVNODE_NOVALIDATION; // cfgmfg32.h
+ public static readonly uint CM_LOCATE_DEVINST_BITS = CM_LOCATE_DEVNODE_BITS; // cfgmfg32.h
+
+ public enum DevpropTypeFixed : uint { // devpropdef.h
+ DEVPROP_TYPE_BYTE = 0x00000003,
+ DEVPROP_TYPE_UINT16 = 0x00000005,
+ DEVPROP_TYPE_UINT32 = 0x00000007,
+ DEVPROP_TYPE_UINT64 = 0x00000009,
+ }
+
+ public enum ConfigRet : uint { // cfgmfg32.h
+ CR_SUCCESS = 0x00000000,
+ CR_DEFAULT = 0x00000001,
+ CR_OUT_OF_MEMORY = 0x00000002,
+ CR_INVALID_POINTER = 0x00000003,
+ CR_INVALID_FLAG = 0x00000004,
+ CR_INVALID_DEVNODE = 0x00000005,
+ CR_INVALID_DEVINST = CR_INVALID_DEVNODE,
+ CR_INVALID_RES_DES = 0x00000006,
+ CR_INVALID_LOG_CONF = 0x00000007,
+ CR_INVALID_ARBITRATOR = 0x00000008,
+ CR_INVALID_NODELIST = 0x00000009,
+ CR_DEVNODE_HAS_REQS = 0x0000000A,
+ CR_DEVINST_HAS_REQS = CR_DEVNODE_HAS_REQS,
+ CR_INVALID_RESOURCEID = 0x0000000B,
+ CR_DLVXD_NOT_FOUND = 0x0000000C, // WIN 95 ONLY
+ CR_NO_SUCH_DEVNODE = 0x0000000D,
+ CR_NO_SUCH_DEVINST = CR_NO_SUCH_DEVNODE,
+ CR_NO_MORE_LOG_CONF = 0x0000000E,
+ CR_NO_MORE_RES_DES = 0x0000000F,
+ CR_ALREADY_SUCH_DEVNODE = 0x00000010,
+ CR_ALREADY_SUCH_DEVINST = CR_ALREADY_SUCH_DEVNODE,
+ CR_INVALID_RANGE_LIST = 0x00000011,
+ CR_INVALID_RANGE = 0x00000012,
+ CR_FAILURE = 0x00000013,
+ CR_NO_SUCH_LOGICAL_DEV = 0x00000014,
+ CR_CREATE_BLOCKED = 0x00000015,
+ CR_NOT_SYSTEM_VM = 0x00000016, // WIN 95 ONLY
+ CR_REMOVE_VETOED = 0x00000017,
+ CR_APM_VETOED = 0x00000018,
+ CR_INVALID_LOAD_TYPE = 0x00000019,
+ CR_BUFFER_SMALL = 0x0000001A,
+ CR_NO_ARBITRATOR = 0x0000001B,
+ CR_NO_REGISTRY_HANDLE = 0x0000001C,
+ CR_REGISTRY_ERROR = 0x0000001D,
+ CR_INVALID_DEVICE_ID = 0x0000001E,
+ CR_INVALID_DATA = 0x0000001F,
+ CR_INVALID_API = 0x00000020,
+ CR_DEVLOADER_NOT_READY = 0x00000021,
+ CR_NEED_RESTART = 0x00000022,
+ CR_NO_MORE_HW_PROFILES = 0x00000023,
+ CR_DEVICE_NOT_THERE = 0x00000024,
+ CR_NO_SUCH_VALUE = 0x00000025,
+ CR_WRONG_TYPE = 0x00000026,
+ CR_INVALID_PRIORITY = 0x00000027,
+ CR_NOT_DISABLEABLE = 0x00000028,
+ CR_FREE_RESOURCES = 0x00000029,
+ CR_QUERY_VETOED = 0x0000002A,
+ CR_CANT_SHARE_IRQ = 0x0000002B,
+ CR_NO_DEPENDENT = 0x0000002C,
+ CR_SAME_RESOURCES = 0x0000002D,
+ CR_NO_SUCH_REGISTRY_KEY = 0x0000002E,
+ CR_INVALID_MACHINENAME = 0x0000002F, // NT ONLY
+ CR_REMOTE_COMM_FAILURE = 0x00000030, // NT ONLY
+ CR_MACHINE_UNAVAILABLE = 0x00000031, // NT ONLY
+ CR_NO_CM_SERVICES = 0x00000032, // NT ONLY
+ CR_ACCESS_DENIED = 0x00000033, // NT ONLY
+ CR_CALL_NOT_IMPLEMENTED = 0x00000034,
+ CR_INVALID_PROPERTY = 0x00000035,
+ CR_DEVICE_INTERFACE_ACTIVE = 0x00000036,
+ CR_NO_SUCH_DEVICE_INTERFACE = 0x00000037,
+ CR_INVALID_REFERENCE_STRING = 0x00000038,
+ CR_INVALID_CONFLICT_LIST = 0x00000039,
+ CR_INVALID_INDEX = 0x0000003A,
+ CR_INVALID_STRUCTURE_SIZE = 0x0000003B,
+ CR_RESULTS = 0x0000003C
+ }
+
+ // String parsing functions for CM HardwareIds
+ public static readonly string PCI_DEVICEID_PREFIX = "PCI";
+ public static readonly string HARDWAREID_GROUPNAME_VEN = "ven";
+ public static readonly string HARDWAREID_GROUPNAME_DEV = "dev";
+ public static readonly string HARDWAREID_GROUPNAME_SUBSYSVEN = "subsysVen";
+ public static readonly string HARDWAREID_GROUPNAME_SUBSYSDEV = "subsysDev";
+ public static readonly string HARDWAREID_GROUPNAME_REV = "rev";
+ public static readonly string HARDWAREID_GROUPNAME_CC_CLASS = "cClass";
+ public static readonly string HARDWAREID_GROUPNAME_CC_SUBCLASS = "cSubClass";
+ public static readonly string HARDWAREID_GROUPNAME_CC_PROGIF = "cProgIf";
+
+ public static readonly string HARDWAREID_PATTERN_VEN = @"VEN_(?<" + HARDWAREID_GROUPNAME_VEN + @">[0-9A-Fa-f]{4})";
+ public static readonly string HARDWAREID_PATTERN_DEV = @"DEV_(?<" + HARDWAREID_GROUPNAME_DEV + @">[0-9A-Fa-f]{4})";
+ public static readonly string HARDWAREID_PATTERN_SUBSYS = @"SUBSYS_(?<" + HARDWAREID_GROUPNAME_SUBSYSDEV + @">[0-9A-Fa-f]{4})(?<" + HARDWAREID_GROUPNAME_SUBSYSVEN + @">[0-9A-Fa-f]{4})";
+ public static readonly string HARDWAREID_PATTERN_REV = @"REV_(?<" + HARDWAREID_GROUPNAME_REV + @">[0-9A-Fa-f]{2})";
+ public static readonly string HARDWAREID_PATTERN_CC = @"CC_(?<" + HARDWAREID_GROUPNAME_CC_CLASS + @">[0-9A-Fa-f]{2})(?<" + HARDWAREID_GROUPNAME_CC_SUBCLASS + @">[0-9A-Fa-f]{2})(?<" + HARDWAREID_GROUPNAME_CC_PROGIF + @">[0-9A-Fa-f]{2})";
+ public static readonly string HARDWAREID_PATTERN = $"({HARDWAREID_PATTERN_VEN})|({HARDWAREID_PATTERN_DEV})|({HARDWAREID_PATTERN_SUBSYS})|({HARDWAREID_PATTERN_REV})|({HARDWAREID_PATTERN_CC})";
+
+ public static readonly Regex HARDWAREID_REGEX = new Regex(HARDWAREID_PATTERN, RegexOptions.Compiled);
+
+ // Settings to identify config buffers regenerated by this library
+ public static readonly uint PCIEWINCFGMGR_CONFIG_SIG = 0x3909C18E;
+}
diff --git a/dotnet/ComponentClassRegistry/PcieWin/src/CfgHelpers.cs b/dotnet/ComponentClassRegistry/PcieWin/src/CfgHelpers.cs
new file mode 100644
index 0000000..d98e360
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/src/CfgHelpers.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PcieWinCfgMgr;
+public class CfgHelpers {
+ public static string ClassCodesToHexString(uint baseClassInt = 0, uint subClassInt = 0, uint progIfInt = 0) {
+ string cc = baseClassInt.ToString("X2") + subClassInt.ToString("X2") + progIfInt.ToString("X2");
+
+ return cc;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/PcieWin/src/CfgImports.cs b/dotnet/ComponentClassRegistry/PcieWin/src/CfgImports.cs
new file mode 100644
index 0000000..e37d069
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/src/CfgImports.cs
@@ -0,0 +1,36 @@
+using System.Runtime.InteropServices;
+using static PcieWinCfgMgr.CfgConstants;
+
+namespace PcieWinCfgMgr;
+
+internal class CfgImports {
+ public const string cfgmgrDll = "cfgmgr32.dll";
+
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Locate_DevNode(out IntPtr pdnDevInst, string pDeviceID, uint ulFlags);
+
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_Device_ID_List_Size(out uint pulLen, string pszFilter, uint ulFlags);
+
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_Device_ID_List(string pszFilter, IntPtr Buffer, uint BufferLen, uint ulFlags);
+
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_Device_Interface_List_SizeW(out uint pulLen, ref Guid interfaceClassGuid, IntPtr pDeviceID, uint ulFlags);
+
+ // Strings are UTF-16
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_Device_Interface_ListW(ref Guid interfaceClassGuid, IntPtr pDeviceID, IntPtr buffer, uint bufferLen, uint ulFlags);
+
+ // Strings are UTF-16
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_DevNode_PropertyW(IntPtr dnDevInst, in CfgStructs.DevPropKey propertyKey, out uint propertyType, byte[]? propertyBuffer, ref uint propertyBufferSize, uint mustBeZero);
+
+ // Strings are UTF-16
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_Device_Interface_Property_KeysW(IntPtr pszDeviceInterface, IntPtr propertyKeyArray, ref int propertyKeyCount, uint mustBeZero);
+
+ // Strings are UTF-16
+ [DllImport(cfgmgrDll, SetLastError = true)]
+ internal static extern ConfigRet CM_Get_Device_Interface_PropertyW(IntPtr pszDeviceInterface, in CfgStructs.DevPropKey propertyKey, out uint propertyType, byte[]? propertyBuffer, ref uint propertyBufferSize, uint mustBeZero);
+}
diff --git a/dotnet/ComponentClassRegistry/PcieWin/src/CfgStructs.cs b/dotnet/ComponentClassRegistry/PcieWin/src/CfgStructs.cs
new file mode 100644
index 0000000..cfa35de
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/src/CfgStructs.cs
@@ -0,0 +1,28 @@
+using System.Runtime.InteropServices;
+
+namespace PcieWinCfgMgr;
+
+// Of course Windows has to make reading static PCIe device information more complicated than necessary.
+public class CfgStructs {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SpDevinfoData { // SP_DEVINFO_DATA: setupapi.h
+ [MarshalAs(UnmanagedType.U4)] public uint cbSize;
+ public Guid ClassGuid;
+ [MarshalAs(UnmanagedType.U4)] public uint DevInst;
+ [MarshalAs(UnmanagedType.U8)] public ulong Reserved;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SpDeviceInterfaceData { // SP_DEVICE_INTERFACE_DATA: setupapi.h
+ [MarshalAs(UnmanagedType.U4)] public uint cbSize;
+ public Guid InterfaceClassGuid;
+ [MarshalAs(UnmanagedType.U4)] public uint Flags;
+ [MarshalAs(UnmanagedType.U8)] public ulong Reserved;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DevPropKey { // DEVPROPKEY: devpropdef.h
+ public Guid DEVPROPGUID;
+ [MarshalAs(UnmanagedType.U4)] public uint DEVPROPID;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/PcieWin/src/MainCanDelete.cs b/dotnet/ComponentClassRegistry/PcieWin/src/MainCanDelete.cs
new file mode 100644
index 0000000..279d63d
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/src/MainCanDelete.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.XPath;
+
+namespace PcieWinCfgMgr;
+
+public class MainCanDelete {
+ public static void Main(string[] args) {
+ bool result = PciWinCfgMgr.GetDiskDevInterfaces(out List diskDeviceInterfaceIdsW);
+
+ bool result2 = PciWinCfgMgr.GetDeviceInterfaceInstanceIds(out List deviceInterfaceInstanceIds, diskDeviceInterfaceIdsW);
+
+ bool result3 = PciWinCfgMgr.GetDeviceNodeOfParent(out IntPtr parentNodePtr, deviceInterfaceInstanceIds[0]);
+
+ bool result4 = PciWinCfgMgr.GetPciClassCodeFromDevNode(out byte Class, out byte SubClass, out byte ProgrammingInterface, parentNodePtr);
+
+ string cc = CfgHelpers.ClassCodesToHexString(Class, SubClass, ProgrammingInterface);
+
+ bool result5 = PciWinCfgMgr.ParseHardwareIdsFromDevNode(out ushort VendorId, out ushort DeviceId, out ushort SubsystemVendorId, out ushort SubsystemId, out byte Revision, out Class, out SubClass, out ProgrammingInterface, parentNodePtr);
+
+ bool result6 = PciWinCfgMgr.GetAllPciDeviceInstanceIds(out List pciDeviceInstanceIds);
+
+ foreach(string pciDeviceInstanceId in pciDeviceInstanceIds) {
+ bool result7 = PciWinCfgMgr.CreateMockConfigBufferFromPciDeviceInstanceId(out byte[] config, out bool isLittleEndian, pciDeviceInstanceId);
+
+ if (!result7) {
+ Console.WriteLine("PCI Device Instance ID: " + pciDeviceInstanceId);
+ }
+ }
+
+ Console.WriteLine();
+
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/PcieWin/src/PciWinCfgMgr.cs b/dotnet/ComponentClassRegistry/PcieWin/src/PciWinCfgMgr.cs
new file mode 100644
index 0000000..21e24e8
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/PcieWin/src/PciWinCfgMgr.cs
@@ -0,0 +1,371 @@
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace PcieWinCfgMgr;
+
+public class PciWinCfgMgr {
+ public static bool GetAllPciDeviceInstanceIds(out List pciDeviceInstanceIdsW) {
+ string filter = CfgConstants.PCI_DEVICEID_PREFIX;
+ uint flags = CfgConstants.CM_GETIDLIST_FILTER_ENUMERATOR;
+ return GetDeviceInstanceIds(out pciDeviceInstanceIdsW, filter, flags);
+ }
+
+ public static bool GetDeviceInstanceIds(out List deviceInstanceIdsW, string filter, uint flags) {
+ deviceInstanceIdsW = Array.Empty().ToList();
+
+ // Get the buffer size required to store all the device instance IDs
+ CfgConstants.ConfigRet response = CfgImports.CM_Get_Device_ID_List_Size(out uint dataLength, filter, flags);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS) {
+ return false;
+ }
+
+ IntPtr dataPtr = IntPtr.Zero;
+
+ try {
+ dataPtr = Marshal.AllocHGlobal((int)dataLength);
+
+ response = CfgImports.CM_Get_Device_ID_List(filter, dataPtr, dataLength, flags);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS) {
+ return false;
+ }
+
+ // Response was successful
+ string data = Marshal.PtrToStringUTF8(dataPtr, (int)dataLength);
+ string[] split = data.Split('\0');
+ deviceInstanceIdsW.AddRange(split);
+ deviceInstanceIdsW.RemoveAll(string.IsNullOrWhiteSpace);
+ } finally {
+ Marshal.FreeHGlobal(dataPtr);
+ }
+
+ return true;
+ }
+
+ public static bool GetDiskDevInterfaces(out List diskDeviceInterfaceIdsW) {
+ Guid guid = CfgConstants.GUID_DEVINTERFACE_DISK;
+ return GetDevInterfaces(out diskDeviceInterfaceIdsW, guid);
+ }
+
+ // Public so other libraries can use these ids as handles
+ public static bool GetDevInterfaces(out List deviceInterfaceIdsW, Guid guid) {
+ deviceInterfaceIdsW = Array.Empty().ToList();
+
+ IntPtr pDeviceId = IntPtr.Zero; // List all Interfaces
+ uint flags = CfgConstants.CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES;
+
+ // Get the buffer size required to store all the device interface IDs
+ CfgConstants.ConfigRet response = CfgImports.CM_Get_Device_Interface_List_SizeW(out uint dataLength, ref guid, pDeviceId, flags);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS) {
+ return false;
+ }
+
+ IntPtr dataPtr = IntPtr.Zero;
+ try {
+ // Allocate memory for the device interface IDs given the previous dataLength response
+ dataPtr = Marshal.AllocHGlobal((int)dataLength);
+
+ // Query the system for the device interface IDs
+ response = CfgImports.CM_Get_Device_Interface_ListW(ref guid, pDeviceId, dataPtr, dataLength, flags);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS) {
+ return false;
+ }
+
+ // Response was successful
+ string dataString = Marshal.PtrToStringUni(dataPtr, (int)dataLength);
+ string[] split = dataString.Split('\0');
+ deviceInterfaceIdsW.AddRange(split);
+ deviceInterfaceIdsW.RemoveAll(string.IsNullOrWhiteSpace);
+ } finally {
+ // Free allocated memory
+ Marshal.FreeHGlobal(dataPtr);
+ }
+
+ return true;
+ }
+
+ // Both lists should be the same size
+ public static bool GetDeviceInterfaceInstanceIds(out List deviceInterfaceInstanceIds, List deviceInterfaceIdsW) {
+ deviceInterfaceInstanceIds = Array.Empty().ToList();
+
+ foreach (string deviceInterfaceIdW in deviceInterfaceIdsW) {
+ if (!GetDeviceInterfaceInstanceId(out string deviceInterfaceInstanceId, deviceInterfaceIdW)) {
+ return false;
+ }
+
+ deviceInterfaceInstanceIds.Add(deviceInterfaceInstanceId);
+ }
+
+ return true;
+ }
+
+ public static bool GetDeviceInterfaceInstanceId(out string deviceInterfaceInstanceId, string deviceInterfaceIdW) {
+
+ deviceInterfaceInstanceId = "";
+
+ CfgStructs.DevPropKey instanceIdKey = CfgConstants.DEVPKEY_Device_InstanceId;
+ uint expectedPropertyType = CfgConstants.DEVPROP_TYPE_STRING;
+ uint bufferLength = CfgConstants.DEVPROP_BUFFER_SIZE;
+
+ IntPtr deviceInterfaceIdWPtr = Marshal.StringToHGlobalUni(deviceInterfaceIdW); // Need a pointer to the string
+
+ try {
+ byte[] buffer = new byte[bufferLength];
+
+ CfgConstants.ConfigRet response = CfgImports.CM_Get_Device_Interface_PropertyW(deviceInterfaceIdWPtr, in instanceIdKey, out uint propertyType, buffer, ref bufferLength, 0);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS || propertyType != expectedPropertyType) {
+ return false;
+ }
+
+ deviceInterfaceInstanceId = System.Text.Encoding.Unicode.GetString(buffer, 0, (int)bufferLength);
+ } finally {
+ Marshal.FreeHGlobal(deviceInterfaceIdWPtr);
+ }
+
+ return true;
+ }
+
+ public static bool CreateMockConfigBufferFromPciDeviceInstanceId(out byte[] config, out bool isLittleEndian, string deviceInstanceId) {
+ config = Array.Empty();
+ isLittleEndian = BitConverter.IsLittleEndian;
+
+ CfgConstants.ConfigRet response = CfgImports.CM_Locate_DevNode(out IntPtr devNodePtr, deviceInstanceId,
+ CfgConstants.CM_LOCATE_DEVNODE_NORMAL);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS) {
+ return false;
+ }
+
+ return CreateMockConfigBufferFromPciDevNode(out config, out isLittleEndian, devNodePtr);
+ }
+
+ // The dev node is expected to be for a PCI device
+ public static bool CreateMockConfigBufferFromPciDevNode(out byte[] config, out bool isLittleEndian, IntPtr devNodePtr) {
+ config = Array.Empty();
+ isLittleEndian = BitConverter.IsLittleEndian;
+
+ bool gotIds = ParseHardwareIdsFromDevNode(out ushort VendorId, out ushort DeviceId, out ushort SubsystemVendorId, out ushort SubsystemId, out byte Revision, out byte Class, out byte SubClass, out byte ProgrammingInterface, devNodePtr);
+ bool gotDsn = GetPciDeviceSerialNumberFromDevNode(out byte[] dsnBytes, devNodePtr);
+
+ if (!gotIds) {
+ return false;
+ }
+
+ if (Class == 0 && SubClass == 0 && ProgrammingInterface == 0) {
+ // Try another way to pull the class code from cfgmgr32
+ GetPciClassCodeFromDevNode(out Class, out SubClass, out ProgrammingInterface, devNodePtr);
+ }
+
+ byte[] ccBytes = new byte[3];
+ ccBytes[0] = Class;
+ ccBytes[1] = SubClass;
+ ccBytes[2] = ProgrammingInterface;
+ if (isLittleEndian) {
+ Array.Reverse(ccBytes);
+ }
+
+ config = new byte[gotDsn ? CfgConstants.PCIE_CONFIG_SIZE : CfgConstants.PCI_CONFIG_SIZE];
+ Array.Fill(config, 0);
+ Array.Copy(BitConverter.GetBytes(VendorId), 0, config, 0x0, 2);
+ Array.Copy(BitConverter.GetBytes(DeviceId), 0, config, 0x2, 2);
+ config[0x8] = Revision;
+ Array.Copy(ccBytes, 0, config, 0x9, 3);
+ Array.Copy(BitConverter.GetBytes(SubsystemVendorId), 0, config, 0x2C, 2);
+ Array.Copy(BitConverter.GetBytes(SubsystemId), 0, config, 0x2E, 2);
+
+ if (gotDsn && config.Length >= 0x10C && dsnBytes.Length == 8) {
+ config[0x100] = 0x03;
+ config[0x101] = 0x00;
+ config[0x102] = 0x00;
+ config[0x103] = 0x00;
+ if (isLittleEndian) {
+ Array.Reverse(dsnBytes);
+ }
+ Array.Copy(dsnBytes, 0, config, 0x104, dsnBytes.Length);
+ }
+
+ Array.Copy(BitConverter.GetBytes(CfgConstants.PCIEWINCFGMGR_CONFIG_SIG), 0, config, config.Length-5, 4);
+
+ return true;
+ }
+
+ public static bool GetDeviceNodeOfParent(out IntPtr parentNodePtr, string deviceInstanceId) {
+ parentNodePtr = IntPtr.Zero;
+
+ CfgConstants.ConfigRet response = CfgImports.CM_Locate_DevNode(out IntPtr devNodePtr, deviceInstanceId, CfgConstants.CM_LOCATE_DEVNODE_NORMAL);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS) {
+ return false;
+ }
+
+ CfgStructs.DevPropKey parentKey = CfgConstants.DEVPKEY_Device_Parent;
+ uint expectedPropertyType = CfgConstants.DEVPROP_TYPE_STRING;
+ uint bufferLength = CfgConstants.DEVPROP_BUFFER_SIZE;
+ byte[] buffer = new byte[bufferLength];
+
+ response = CfgImports.CM_Get_DevNode_PropertyW(devNodePtr, in parentKey, out uint propertyType, buffer, ref bufferLength, 0);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS || propertyType != expectedPropertyType) {
+ return false;
+ }
+
+ string parentDeviceId = System.Text.Encoding.Unicode.GetString(buffer, 0, (int)bufferLength);
+
+ response = CfgImports.CM_Locate_DevNode(out parentNodePtr, parentDeviceId, CfgConstants.CM_LOCATE_DEVNODE_NORMAL);
+
+ return response == CfgConstants.ConfigRet.CR_SUCCESS;
+ }
+
+ public static bool GetUint32FromDevNodeProperty(out uint propertyValue, CfgStructs.DevPropKey devpkey, IntPtr devNodePtr) {
+ propertyValue = 0;
+
+ bool result = GetByteArrayFromDevNodeProperty(out byte[] buffer, devpkey, CfgConstants.DevpropTypeFixed.DEVPROP_TYPE_UINT32, devNodePtr);
+
+ if (!result) {
+ return false;
+ }
+
+ propertyValue = BitConverter.ToUInt32(buffer);
+
+ return true;
+ }
+
+ private static bool GetByteArrayFromDevNodeProperty(out byte[] propertyValue, CfgStructs.DevPropKey devpkey, CfgConstants.DevpropTypeFixed expectedPropertyType, IntPtr devNodePtr) {
+ propertyValue = Array.Empty();
+
+ uint expectedBufferLength = 0;
+ uint bufferLength = CfgConstants.DEVPROP_BUFFER_SIZE;
+
+ expectedBufferLength = expectedPropertyType switch {
+ CfgConstants.DevpropTypeFixed.DEVPROP_TYPE_BYTE => 1,
+ CfgConstants.DevpropTypeFixed.DEVPROP_TYPE_UINT16 => 2,
+ CfgConstants.DevpropTypeFixed.DEVPROP_TYPE_UINT32 => 4,
+ CfgConstants.DevpropTypeFixed.DEVPROP_TYPE_UINT64 => 8,
+ _ => expectedBufferLength
+ };
+
+ byte[] buffer = new byte[bufferLength]; // DEVPROP_BUFFER_SIZE
+
+ CfgConstants.ConfigRet response = CfgImports.CM_Get_DevNode_PropertyW(devNodePtr, in devpkey, out uint propertyType, buffer, ref bufferLength, 0);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS || propertyType != (uint)expectedPropertyType || bufferLength != expectedBufferLength) {
+ return false;
+ }
+
+ propertyValue = new byte[bufferLength]; // expected length
+ Array.Copy(buffer, propertyValue, bufferLength);
+
+ return true;
+ }
+
+ public static bool GetStringListFromDevNodeProperty(out List propertyValue, CfgStructs.DevPropKey devpkey, IntPtr devNodePtr) {
+ propertyValue = new List();
+
+ uint expectedPropertyType = CfgConstants.DEVPROP_TYPE_STRING_LIST;
+ uint bufferLength = CfgConstants.DEVPROP_BUFFER_SIZE;
+ byte[] buffer = new byte[bufferLength];
+
+ CfgConstants.ConfigRet response = CfgImports.CM_Get_DevNode_PropertyW(devNodePtr, in devpkey, out uint propertyType, buffer, ref bufferLength, 0);
+
+ if (response != CfgConstants.ConfigRet.CR_SUCCESS || propertyType != expectedPropertyType || bufferLength == 0) {
+ return false;
+ }
+
+ string conv = System.Text.Encoding.Unicode.GetString(buffer, 0, (int)bufferLength); // buffer contains null-terminated UTF-16 strings
+ string[] split = conv.Split('\0');
+ propertyValue.AddRange(split);
+
+ return true;
+ }
+
+ public static bool GetPciClassCodeFromDevNode(out byte Class, out byte SubClass, out byte ProgrammingInterface, IntPtr devNodePtr) {
+ bool resultBaseClass = GetUint32FromDevNodeProperty(out uint baseClassInt, CfgConstants.DEVPKEY_PciDevice_BaseClass, devNodePtr);
+
+ bool resultSubClass = GetUint32FromDevNodeProperty(out uint subClassInt, CfgConstants.DEVPKEY_PciDevice_SubClass, devNodePtr);
+
+ bool resultProgIf = GetUint32FromDevNodeProperty(out uint progIfInt, CfgConstants.DEVPKEY_PciDevice_ProgIf, devNodePtr);
+
+ // The casts here will truncate the 3 MSBs
+ Class = (byte)baseClassInt;
+ SubClass = (byte)subClassInt;
+ ProgrammingInterface = (byte)progIfInt;
+
+ return resultBaseClass && resultSubClass && resultProgIf;
+ }
+
+ public static bool ParseHardwareIdsFromDevNode(out ushort VendorId, out ushort DeviceId, out ushort SubsystemVendorId, out ushort SubsystemId, out byte Revision, out byte Class, out byte SubClass, out byte ProgrammingInterface, IntPtr devNodePtr) {
+ VendorId = 0;
+ DeviceId = 0;
+ SubsystemVendorId = 0;
+ SubsystemId = 0;
+ Revision = 0;
+ Class = 0;
+ SubClass = 0;
+ ProgrammingInterface = 0;
+
+ CfgStructs.DevPropKey key = CfgConstants.DEVPKEY_Device_HardwareIds;
+
+ bool result = GetStringListFromDevNodeProperty(out List hardwareIds, key, devNodePtr);
+
+ foreach (string hardwareId in hardwareIds) {
+ if (string.IsNullOrWhiteSpace(hardwareId) || !hardwareId.StartsWith(@"PCI\")) {
+ continue;
+ }
+
+ Match match = CfgConstants.HARDWAREID_REGEX.Match(hardwareId);
+
+ while (match.Success) {
+ try {
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_VEN].Captures.Count > 0 && VendorId == 0) {
+ VendorId = Convert.ToUInt16(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_VEN].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_DEV].Captures.Count > 0 && DeviceId == 0) {
+ DeviceId = Convert.ToUInt16(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_DEV].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_SUBSYSVEN].Captures.Count > 0 && SubsystemVendorId == 0) {
+ SubsystemVendorId = Convert.ToUInt16(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_SUBSYSVEN].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_SUBSYSDEV].Captures.Count > 0 && SubsystemId == 0) {
+ SubsystemId = Convert.ToUInt16(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_SUBSYSDEV].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_REV].Captures.Count > 0 && Revision == 0) {
+ Revision = Convert.ToByte(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_REV].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_CC_CLASS].Captures.Count > 0 && Class == 0) {
+ Class = Convert.ToByte(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_CC_CLASS].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_CC_SUBCLASS].Captures.Count > 0 && SubClass == 0) {
+ SubClass = Convert.ToByte(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_CC_SUBCLASS].Value, 16);
+ }
+
+ if (match.Groups[CfgConstants.HARDWAREID_GROUPNAME_CC_PROGIF].Captures.Count > 0 && ProgrammingInterface == 0) {
+ ProgrammingInterface = Convert.ToByte(match.Groups[CfgConstants.HARDWAREID_GROUPNAME_CC_PROGIF].Value, 16);
+ }
+ } finally {
+ match = match.NextMatch();
+ }
+ }
+ }
+
+ Console.WriteLine();
+ return result;
+ }
+
+ public static bool GetPciDeviceSerialNumberFromDevNode(out byte[] dsnBytes, IntPtr devNodePtr) {
+ dsnBytes = Array.Empty();
+
+ bool result = GetByteArrayFromDevNodeProperty(out dsnBytes, CfgConstants.DEVPKEY_PciDevice_SerialNumber, CfgConstants.DevpropTypeFixed.DEVPROP_TYPE_UINT64, devNodePtr);
+
+ return result;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Smbios/Properties/PublishProfiles/FolderProfile.pubxml b/dotnet/ComponentClassRegistry/Smbios/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..3cd5745
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Smbios/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,15 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\net8.0\publish\
+ FileSystem
+ <_TargetId>Folder
+ net8.0
+ false
+
+
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/Smbios/Smbios.csproj b/dotnet/ComponentClassRegistry/Smbios/Smbios.csproj
new file mode 100644
index 0000000..33dd94d
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Smbios/Smbios.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ linux-x64;win-x64
+ enable
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/Smbios/src/LinuxImports.cs b/dotnet/ComponentClassRegistry/Smbios/src/LinuxImports.cs
new file mode 100644
index 0000000..5f2bcf1
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Smbios/src/LinuxImports.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Smbios;
+public class LinuxImports {
+ ///
+ /// This method is imported to query the Linux Kernel whether the program was run with privileges.
+ ///
+ /// The Euid.
+ [DllImport("libc", SetLastError = true)]
+ internal static extern uint geteuid();
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/Smbios/src/Smbios.cs b/dotnet/ComponentClassRegistry/Smbios/src/Smbios.cs
new file mode 100644
index 0000000..004fabb
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Smbios/src/Smbios.cs
@@ -0,0 +1,221 @@
+using System.Management;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Smbios {
+ public class Smbios {
+ public static readonly string WindowsQueryScope = @"root\\WMI";
+ public static readonly string WindowsQueryString = "SELECT * FROM MSSMBios_RawSMBiosTables";
+ public static readonly string LinuxPathEntryTable = "/sys/firmware/dmi/tables/smbios_entry_point";
+ public static readonly string LinuxPathStructures = "/sys/firmware/dmi/tables/DMI";
+
+ ///
+ /// The SMBIOS Major Version from the Entry Point.
+ ///
+ public int MajorVersion {
+ get;
+ private set;
+ }
+
+ ///
+ /// The SMBIOS Minor Version from the Entry Point.
+ ///
+ public int MinorVersion {
+ get;
+ private set;
+ }
+
+ ///
+ /// Smbios data organized by structure type.
+ ///
+ public IDictionary> Structures {
+ get;
+ private init;
+ } = new Dictionary> ();
+
+ ///
+ /// True if all structures were built with expected data lengths. False if any structure was not valid.
+ ///
+ public bool Valid {
+ get;
+ private set;
+ }
+
+ ///
+ /// Calls internal methods to gather raw SMBIOS data from the OS. Then parses that data into a dictionary of SmbiosTable objects.
+ ///
+ /// Smbios data organized by structure type.
+ public static Smbios GetSmbios() {
+ int majorVersion = 0;
+ int minorVersion = 0;
+ byte[] data = Array.Empty();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ GetSmbiosWindows(out majorVersion, out minorVersion, out data);
+ } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ GetSmbiosLinux(out majorVersion, out minorVersion, out data);
+ }
+
+ // Parse full smbios table into objects
+ Smbios smbios = new() {
+ MajorVersion = majorVersion,
+ MinorVersion = minorVersion,
+ Structures = ParseSmbiosData(data)
+ };
+
+ // Verify all tables have expected ranges
+ smbios.Valid = VerifyStructures(smbios.Structures);
+
+ return smbios;
+ }
+
+ ///
+ /// Each structure is built with a simple check to make sure the data length looks correct.
+ ///
+ /// Parsed smbios data.
+ /// This function returns true if all structures in the dictionary are valid. It will return false if any one of them is not valid.
+ private static bool VerifyStructures(IDictionary> structures) {
+ bool check = true;
+ foreach (IList list in structures.Values) {
+ foreach (SmbiosTable table in list) {
+ check = check && table.Valid;
+ if (!check) {
+ break;
+ }
+ }
+ if (!check) {
+ break;
+ }
+ }
+
+ return check;
+ }
+
+ ///
+ /// Turns raw SMBIOS data into SmbiosTable objects. Organizes them by structure type.
+ ///
+ /// Byte array of SMBIOS table data.
+ /// SmbiosTable objects organized by structure type.
+ public static Dictionary> ParseSmbiosData(byte[] smbiosData) {
+ Dictionary> structs = new();
+
+ if (smbiosData.Length == 0) {
+ return structs;
+ }
+
+ int pos = 0;
+ List strings = new();
+
+ // Change pos if entry point information is included.
+ if (smbiosData.Length > 4 && Encoding.ASCII.GetString(smbiosData[0..4]).Equals("_SM_")) {
+ pos = 0x1F;
+ }
+ if (smbiosData.Length > 5 && Encoding.ASCII.GetString(smbiosData[0..5]).Equals("_SM3_")) {
+ pos = 0x18;
+ }
+
+ while (pos < smbiosData.Length) {
+ int structureStart = pos;
+ int structureLength = smbiosData[structureStart + 1];
+ int structureEnd = structureStart + structureLength;
+ pos = structureEnd;
+
+ // Parse through strings section
+ while (smbiosData[pos] != 0) {
+ string newString = "";
+
+ while (smbiosData[pos] != 0) {
+ newString += (char)smbiosData[pos++];
+ }
+
+ strings.Add(newString);
+ pos++;
+ }
+
+ // Save table to dictionary
+ SmbiosTable table = new(smbiosData[structureStart..structureEnd], strings.ToArray());
+ if (!structs.ContainsKey(table.Type)) {
+ structs.Add(table.Type, new List());
+ }
+ structs[table.Type].Add(table);
+
+ // new structure
+ strings = new List();
+ pos++;
+
+ if (smbiosData[pos] == 0) {
+ pos++;
+ }
+ }
+
+ return structs;
+ }
+
+ ///
+ /// Performs the Windows-specific steps of gather the SMBIOS table data from the OS. Updates the parameter values from entry point data.
+ ///
+ /// Collect the SMBIOS Major Version from the Entry Point.
+ /// Collect the SMBIOS Minor Version from the Entry Point.
+ /// Collect the SMBIOS table data.
+ private static void GetSmbiosWindows(out int majorVersion, out int minorVersion, out byte[] data) {
+ majorVersion = 0;
+ minorVersion = 0;
+ data = Array.Empty();
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ return;
+ }
+
+ ManagementObjectCollection collection;
+ using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\WMI", "SELECT * FROM MSSMBios_RawSMBiosTables")) {
+ //using (ManagementObjectSearcher searcher = new(WindowsQueryScope, WindowsQueryString)) {
+ collection = searcher.Get();
+ }
+
+ foreach (ManagementBaseObject? o in collection) {
+ if (o == null) {
+ continue;
+ }
+
+ ManagementObject mo = (ManagementObject)o;
+ object dataObj = mo["SMBiosData"];
+ object majorVersionObj = mo["SmbiosMajorVersion"];
+ object minorVersionObj = mo["SmbiosMinorVersion"];
+ if (dataObj == null || majorVersionObj == null || minorVersionObj == null) {
+ continue;
+ }
+
+ majorVersion = (byte)majorVersionObj;
+ minorVersion = (byte)minorVersionObj;
+ data = (byte[])dataObj;
+ break;
+ }
+ }
+
+ ///
+ /// Performs the Linux-specific steps of gather the SMBIOS table data from the OS. Updates the parameter values from entry point data.
+ ///
+ /// Collect the SMBIOS Major Version from the Entry Point.
+ /// Collect the SMBIOS Minor Version from the Entry Point.
+ /// Collect the SMBIOS table data.
+ private static void GetSmbiosLinux(out int majorVersion, out int minorVersion, out byte[] data) {
+ majorVersion = 0;
+ minorVersion = 0;
+ data = Array.Empty();
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ return;
+ }
+
+ // Is this program running with elevated privileges?
+ if (LinuxImports.geteuid() != 0) {
+ return;
+ }
+
+ byte[] entryTable = File.ReadAllBytes(LinuxPathEntryTable);
+ majorVersion = entryTable[0x07];
+ minorVersion = entryTable[0x08];
+ data = File.ReadAllBytes(LinuxPathStructures);
+ }
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Smbios/src/SmbiosHardwareManifestPlugin.cs b/dotnet/ComponentClassRegistry/Smbios/src/SmbiosHardwareManifestPlugin.cs
new file mode 100644
index 0000000..1dbba9e
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Smbios/src/SmbiosHardwareManifestPlugin.cs
@@ -0,0 +1,156 @@
+using HardwareManifestPlugin;
+using HardwareManifestProto;
+using OidsProto;
+
+namespace Smbios;
+
+public sealed class SmbiosHardwareManifestPlugin : HardwareManifestPluginBase {
+ public static readonly string TraitDescription = "SMBIOS-based Component Class Registry";
+ public static readonly string TraitDescriptionUri = "https://trustedcomputinggroup.org/wp-content/uploads/SMBIOS-Component-Class-Registry_v1.01_finalpublication.pdf";
+ public static readonly string PluginName = "paccor.componentclassregistry.smbios";
+ public static readonly string PluginDescription = "Collect hardware identifiers according to the SMBIOS Component Class Registry.";
+
+ public SmbiosHardwareManifestPlugin() {
+ Name = PluginName;
+ Description = PluginDescription;
+ CollectsV2HardwareInformation = true;
+ CollectsV3HardwareInformation = false;
+ }
+
+ public override bool GatherHardwareIdentifiers() {
+ bool result = false;
+
+ Smbios smbios = Smbios.GetSmbios();
+
+ if (!smbios.Valid) {
+ return result;
+ }
+
+ AddComponentsToManifestV2(smbios.Structures, ManifestV2);
+ ManifestV3 = HardwareManifestConverter.FromManifestV2(ManifestV2, TraitDescription, TraitDescriptionUri);
+
+ result = smbios.Valid;
+
+ return result;
+ }
+
+ public static void AddComponentsToManifestV2(IDictionary> structures, ManifestV2 manifest) {
+ string dmtfRegistryOid = OidsUtils.Find(TCG_REGISTRY_COMPONENTCLASS_NODE.TcgRegistryComponentclassDmtf).Oid;
+
+ foreach (int type in structures.Keys) {
+ foreach (SmbiosTable table in structures[type]) {
+ ComponentIdentifier component = new() { COMPONENTCLASS = new ComponentClass() };
+ bool addComponent = false;
+
+ switch (type) {
+ case 0x0002: // BASEBOARD
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "00" + Value(table, 0x0D);
+ component.MANUFACTURER = Strref(table, 0x04);
+ component.MODEL = Strref(table, 0x05);
+ component.SERIAL = Strref(table, 0x07);
+ component.REVISION = Strref(table, 0x06);
+ component.FIELDREPLACEABLE = BitField(table, 0x09, 0x1C, 0) ? "true" : "false";
+ addComponent = true;
+ break;
+ case 0x0000: // BIOS
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + Value(table, 0x12, 2);
+ component.MANUFACTURER = Strref(table, 0x04);
+ component.MODEL = Strref(table, 0x05);
+ component.SERIAL = "";
+ component.REVISION = Value(table, 0x14, 2);
+ component.FIELDREPLACEABLE = "";
+ addComponent = true;
+ break;
+ case 0x0003: // CHASSIS
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "00" + Value(table, 0x05);
+ component.MANUFACTURER = Strref(table, 0x04);
+ component.MODEL = Value(table, 0x05);
+ component.SERIAL = Strref(table, 0x07);
+ component.REVISION = Strref(table, 0x06);
+ component.FIELDREPLACEABLE = "";
+ addComponent = true;
+ break;
+ case 0x0004: // PROCESSOR
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "00" + Value(table, 0x05);
+ component.MANUFACTURER = Strref(table, 0x07);
+ component.MODEL = Value(table, 0x06);
+ component.SERIAL = Strref(table, 0x20);
+ component.REVISION = Strref(table, 0x10);
+ component.FIELDREPLACEABLE = BitField(table, 0x19, 0x06) ? "true" : "false";
+ addComponent = true;
+ break;
+ case 0x0011: // RAM
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "00" + Value(table, 0x12);
+ component.MANUFACTURER = Strref(table, 0x17);
+ component.MODEL = Strref(table, 0x1A);
+ component.SERIAL = Strref(table, 0x18);
+ component.REVISION = Strref(table, 0x2B);
+ component.FIELDREPLACEABLE = "";
+ addComponent = true;
+ break;
+ case 0x0001: // SYSTEM
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "0000";
+ component.MANUFACTURER = Strref(table, 0x04);
+ component.MODEL = Strref(table, 0x05);
+ component.SERIAL = Strref(table, 0x07);
+ component.REVISION = Strref(table, 0x06);
+ component.FIELDREPLACEABLE = "";
+ addComponent = true;
+ break;
+ case 0x0027: // POWER SUPPLY
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "0000";
+ component.MANUFACTURER = Strref(table, 0x07);
+ component.MODEL = Strref(table, 0x0A);
+ component.SERIAL = Strref(table, 0x08);
+ component.REVISION = Strref(table, 0x0B);
+ component.FIELDREPLACEABLE = BitField(table, 0x0E, 0x01, 0) ? "true" : "false";
+ addComponent = true;
+ break;
+ case 0x002B: // TPM
+ component.COMPONENTCLASS.COMPONENTCLASSREGISTRY = dmtfRegistryOid;
+ component.COMPONENTCLASS.COMPONENTCLASSVALUE = "00" + Value(table, 0x00) + "0000";
+ component.MANUFACTURER = Value(table, 0x04, 4);
+ component.MODEL = Value(table, 0x08, 2);
+ component.SERIAL = "";
+ component.REVISION = Value(table, 0x0A, 8);
+ component.FIELDREPLACEABLE = "";
+ addComponent = true;
+ break;
+
+ }
+
+ if (addComponent) {
+ manifest.COMPONENTS.Add(component);
+ }
+ }
+ }
+ }
+
+ public static string Strref(SmbiosTable table, int offset) {
+ int index = table.Data[offset];
+ return index > 0 ? table.Strings[table.Data[offset]-1] : "";
+ }
+
+ public static string Value(SmbiosTable table, int offset) {
+ return Value(table, offset, 1);
+ }
+
+ public static string Value(SmbiosTable table, int offset, int length) {
+ return Convert.ToHexString(table.Data[offset..(offset+length)]);
+ }
+
+ public static bool BitField(SmbiosTable table, int offset, int testValue) {
+ return table.Data[offset] != testValue;
+ }
+
+ public static bool BitField(SmbiosTable table, int offset, int mask, int testValue) {
+ return (table.Data[offset] & mask) != testValue;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Smbios/src/SmbiosTable.cs b/dotnet/ComponentClassRegistry/Smbios/src/SmbiosTable.cs
new file mode 100644
index 0000000..81e244d
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Smbios/src/SmbiosTable.cs
@@ -0,0 +1,44 @@
+namespace Smbios {
+ public class SmbiosTable {
+ public int Type {
+ get;
+ private set;
+ }
+
+ public int Handle {
+ get;
+ private set;
+ }
+
+ public byte[] Data {
+ get;
+ private set;
+ }
+
+ public string[] Strings {
+ get;
+ private set;
+ }
+
+ public bool Valid {
+ get;
+ private set;
+ }
+
+ ///
+ ///
+ ///
+ /// The structure data. Not including strings.
+ /// The strings of the structure.
+ public SmbiosTable(byte[] inData, string[] inStrings) {
+ Data = inData.Length > 0 ? inData : Array.Empty();
+ Strings = inStrings.Length > 0 ? inStrings : Array.Empty();
+
+ if (inData.Length > 3 && inData[1] == inData.Length) {
+ Valid = true;
+ Type = inData[0];
+ Handle = BitConverter.ToInt16(inData, 2);
+ }
+ }
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/SmbiosCli/Properties/PublishProfiles/FolderProfile.pubxml b/dotnet/ComponentClassRegistry/SmbiosCli/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..6b70635
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/SmbiosCli/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,18 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\net8.0\publish\win-x64\
+ FileSystem
+ <_TargetId>Folder
+ net8.0
+ win-x64
+ true
+ false
+ false
+
+
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/SmbiosCli/SmbiosCli.csproj b/dotnet/ComponentClassRegistry/SmbiosCli/SmbiosCli.csproj
new file mode 100644
index 0000000..49cddbd
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/SmbiosCli/SmbiosCli.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ linux-x64;win-x64
+ SmbiosCli.SmbiosCli
+ true
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/SmbiosCli/src/Program.cs b/dotnet/ComponentClassRegistry/SmbiosCli/src/Program.cs
new file mode 100644
index 0000000..1019656
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/SmbiosCli/src/Program.cs
@@ -0,0 +1,41 @@
+using CliLib;
+using Smbios;
+using System.Runtime.InteropServices;
+
+namespace SmbiosCli;
+public class SmbiosCli {
+ public static int Main(string[] args) {
+ int returnCode = (int)ClientExitCodes.SUCCESS;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ // Linux requires sudo
+ int result = CliOptions.IsUserPrivileged();
+ if (result != (int)ClientExitCodes.SUCCESS) {
+ Console.WriteLine("SMBIOS data retrieval on Linux requires admin privileges. Please run as root.");
+ return result;
+ }
+ }
+
+ CliOptions? cli = CliOptions.ParseArguments(args);
+
+ if (cli == null) {
+ return (int)ClientExitCodes.CLI_PARSE_ERROR;
+ }
+
+ SmbiosHardwareManifestPlugin plugin = new();
+ if (!plugin.GatherHardwareIdentifiers()) {
+ Console.WriteLine("SMBIOS hardware information gathered was not valid.");
+ return (int)ClientExitCodes.GATHER_HW_MANIFEST_FAIL;
+ }
+
+ // All smbios data should be validated at this point.
+ if (cli.PrintV2 || (!cli.PrintV2 && !cli.PrintV3)) { // V2 should be printed by default not matter what
+ Console.WriteLine(plugin.ManifestV2.ToString());
+ }
+ if (cli.PrintV3) {
+ Console.WriteLine(plugin.ManifestV3.ToString());
+ }
+
+ return returnCode;
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/SmbiosTest/SmbiosTests.csproj b/dotnet/ComponentClassRegistry/SmbiosTest/SmbiosTests.csproj
new file mode 100644
index 0000000..c5d576a
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/SmbiosTest/SmbiosTests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ net8.0
+ enable
+ enable
+ NSA Cybersecurity Directorate
+ Apache-2.0
+
+ false
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/SmbiosTest/src/SmbiosTests.cs b/dotnet/ComponentClassRegistry/SmbiosTest/src/SmbiosTests.cs
new file mode 100644
index 0000000..b6680a3
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/SmbiosTest/src/SmbiosTests.cs
@@ -0,0 +1,130 @@
+using HardwareManifestProto;
+using Smbios;
+
+namespace SmbiosTests {
+ public class SmbiosTests {
+ public static readonly string RegistrySampleDataA2Base64 = "X1NNM1/cGAMCAAEAVAkAABgAAAAAAAAAABgAAAECAPADD4CYi38BAAAAMwcID///U2FtcGxlIEJp\r\nb3MgVmVuZG9yAFExLjA5Lnh5egAwOS8wMS8yMDIwAAABGwEAAQIDBAACAAMABAAFAAYABwAIAAkG\r\nBQZTYW1wbGUgU3lzdGVtIE1hbnVmYWN0dXJlcgBTYW1wbGUgU3lzdGVtIE1vZGVsAFNhbXBsZSBT\r\neXN0ZW0gUmV2aXNpb24AU2FtcGxlIFN5c3RlbSBTZXJpYWwgTnVtYmVyAFRvIEJlIEZpbGxlZCBC\r\neSBPLkUuTS4AVG8gQmUgRmlsbGVkIEJ5IE8uRS5NLgAAAg8CAAECAwQFCQYDAAoAU2FtcGxlIEJh\r\nc2Vib2FyZCBNYW51ZmFjdHVyZXIAU2FtcGxlIEJhc2Vib2FyZCBNb2RlbABTYW1wbGUgQmFzZWJv\r\nYXJkIFJldmlzaW9uAFNhbXBsZSBCYXNlYm9hcmQgU2VyaWFsIE51bWJlcgBUbyBCZSBGaWxsZWQg\r\nQnkgTy5FLk0uAFRvIEJlIEZpbGxlZCBCeSBPLkUuTS4AAAMVAwABAwIDBAMDAwMAAAAAAAEAAFNh\r\nbXBsZSBDaGFzc2lzIE1hbnVmYWN0dXJlcgBTYW1wbGUgQ2hhc3NpcyBSZXZpc2lvbgBTYW1wbGUg\r\nQ2hhc3NpcyBTZXJpYWwgTnVtYmVyAFRvIEJlIEZpbGxlZCBCeSBPLkUuTS4AAAQoBAABA8YCpQYB\r\nAP/7678DAIUAagpABkEBBQAGAAcABAUGBAQIBABDUFVTb2NrZXQAU2FtcGxlIFByb2Nlc3NvciBN\r\nYW51ZmFjdHVyZXIAU2FtcGxlIFByb2Nlc3NvciBSZXZpc2lvbgBTYW1wbGUgUHJvY2Vzc29yIFNl\r\ncmlhbCBOdW1iZXIAVG8gQmUgRmlsbGVkIEJ5IE8uRS5NLgBUbyBCZSBGaWxsZWQgQnkgTy5FLk0u\r\nAAAEKAQAAQPvAqUGAQD/++u/AwCFAGoKQAZBBgUABgAHAAQFBgQECAQAQ1BVU29ja2V0AFNhbXBs\r\nZSBDUFUgTWFudWZhY3R1cmVyAFNhbXBsZSBDUFUgUmV2aXNpb24AU2FtcGxlIENQVSBTZXJpYWwg\r\nTnVtYmVyAFRvIEJlIEZpbGxlZCBCeSBPLkUuTS4AVG8gQmUgRmlsbGVkIEJ5IE8uRS5NLgAABxMF\r\nAAGAAAABAAEBAAEAAAQDBUwxLUNhY2hlAAAHEwYAAYEAAAQABAEAAQAABQUHTDItQ2FjaGUAAAcT\r\nBwABggEAIAAgAQABAAAFBQhMMy1DYWNoZQAACQ0IAAEGBQMDAQAMAVBDSTEAAAkNCQABBgUDAwIA\r\nDAFQQ0kyAAAJDQoAAaUIBAMRAAwBUENJRTEAAAkNCwABpQ0DAxIADAFQQ0lFMgAACQ0MAAGlCAMD\r\nEwAMAVBDSUUzAAAJDQ0AAaUNBAMUAAwBUENJRTQAAAkNDgABpQoDAxUADAFQQ0lFNQAAEA8PAAMD\r\nBgAAAAz+/wYAAAATDxAAAAAAAP//vwAPAAQAABFUEQAPAP7/SABAAAAICQABAhgCADUFAwQFBgAA\r\nAAAAAAAAAAAAAAABAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERJ\r\nTU0wAEJBTkswAFNhbXBsZSBSQU0gTWFudWZhY3R1cmVyAFNhbXBsZSBSQU0gU2VyaWFsIE51bWJl\r\nciAxAEFzc2V0VGFnTnVtMABTYW1wbGUgUkFNIE1vZGVsAFNhbXBsZSBSQU0gUmV2aXNpb24AABQT\r\nEgAAAAAA//8fABEAEAABAAEAABFUEwAPAP7/SABAAAAICQABAhgCADUFAwQFAAAAAAAAAAAAAAAA\r\nAAABAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERJTU0xAEJBTksx\r\nAFNhbXBsZSBSQU0gTWFudWZhY3R1cmVyAFNhbXBsZSBSQU0gU2VyaWFsIE51bWJlciAyAEFzc2V0\r\nVGFnTnVtMQBTYW1wbGUgUkFNIE1vZGVsAFNhbXBsZSBSQU0gUmV2aXNpb24AABQTFAAAACAA//8/\r\nABMAEAABAAEAABFUFQAPAP7/SABAAAAICQABAhgCADUFAwQFBgAAAAAAAAAAAAAAAAABAAAHAAAA\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERJTU0yAEJBTksyAFNhbXBsZSBS\r\nQU0gTWFudWZhY3R1cmVyAFNhbXBsZSBSQU0gU2VyaWFsIE51bWJlciAzAEFzc2V0VGFnTnVtMgBT\r\nYW1wbGUgUkFNIE1vZGVsAFNhbXBsZSBSQU0gUmV2aXNpb24AABQTFgAAAEAA//9fABUAEAABAAEA\r\nABFUFwAPAP7/SABAAAAICQABAhgCADUFAwQFBgAAAAAAAAAAAAAAAAABAAAHAAAAAAAAAAAAAAAA\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERJTU0zAEJBTkszAFNhbXBsZSBSQU0gTWFudWZh\r\nY3R1cmVyAFNhbXBsZSBSQU0gU2VyaWFsIE51bWJlciA0AEFzc2V0VGFnTnVtMwBTYW1wbGUgUkFN\r\nIE1vZGVsAFNhbXBsZSBSQU0gUmV2aXNpb24AABQTGAAAAGAA//9/ABcAEAABAAEAACAUHQAAAAAA\r\nAAAAAAAAAAAAAAAAAAAnFiEAAAECAwQFBgeAABIJ////////U2FtcGxlIFBvd2VyIFN1cHBseSBM\r\nb2NhdGlvbgBTYW1wbGUgUG93ZXIgU3VwcGx5IERldmljZSBOYW1lAFNhbXBsZSBQb3dlciBTdXBw\r\nbHkgTWFudWZhY3R1cmVyAFNhbXBsZSBQb3dlciBTdXBwbHkgU2VyaWFsIE51bWJlcgBTYW1wbGUg\r\nUG93ZXIgU3VwcGx5IEFzc2V0IFRhZyBOdW1iZXIAU2FtcGxlIFBvd2VyIFN1cHBseSBNb2RlbCBQ\r\nYXJ0IE51bWJlcgBTYW1wbGUgUG93ZXIgU3VwcGx5IFJldmlzaW9uIExldmVsAAArHyMAQUJDAAIA\r\nAQIDBAUGBwgBBAAAAAAAAAAAAAAAU2FtcGxlIFRQTSBEZXNjcmlwdGlvbgAAfwQlAAAA";
+ public static readonly string[] ComponentIdentifiersInJson = new string[] {
+ // BIOS
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00003307\" }," +
+ " \"MANUFACTURER\": \"Sample Bios Vendor\"," +
+ " \"MODEL\": \"Q1.09.xyz\"," +
+ " \"REVISION\": \"080F\" }",
+ // SYSTEM
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00010000\" }," +
+ " \"MANUFACTURER\": \"Sample System Manufacturer\"," +
+ " \"MODEL\": \"Sample System Model\"," +
+ " \"SERIAL\": \"Sample System Serial Number\"," +
+ " \"REVISION\": \"Sample System Revision\" }",
+ // BASEBOARD
+ "{"+
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"0002000A\" }," +
+ " \"MANUFACTURER\": \"Sample Baseboard Manufacturer\"," +
+ " \"MODEL\": \"Sample Baseboard Model\"," +
+ " \"SERIAL\": \"Sample Baseboard Serial Number\"," +
+ " \"REVISION\": \"Sample Baseboard Revision\"," +
+ " \"FIELDREPLACEABLE\": \"true\" }",
+ // CHASSIS
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00030003\" }," +
+ " \"MANUFACTURER\": \"Sample Chassis Manufacturer\"," +
+ " \"MODEL\": \"03\"," +
+ " \"SERIAL\": \"Sample Chassis Serial Number\"," +
+ " \"REVISION\": \"Sample Chassis Revision\" }",
+ // PROCESSOR
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00040003\" }," +
+ " \"MANUFACTURER\": \"Sample Processor Manufacturer\"," +
+ " \"MODEL\": \"C6\"," +
+ " \"SERIAL\": \"Sample Processor Serial Number\"," +
+ " \"REVISION\": \"Sample Processor Revision\"," +
+ " \"FIELDREPLACEABLE\": \"true\" }",
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00040003\" }," +
+ " \"MANUFACTURER\": \"Sample CPU Manufacturer\"," +
+ " \"MODEL\": \"EF\"," +
+ " \"SERIAL\": \"Sample CPU Serial Number\"," +
+ " \"REVISION\": \"Sample CPU Revision\"," +
+ " \"FIELDREPLACEABLE\": \"false\" }",
+ // RAM
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00110018\" }," +
+ " \"MANUFACTURER\": \"Sample RAM Manufacturer\"," +
+ " \"MODEL\": \"Sample RAM Model\"," +
+ " \"SERIAL\": \"Sample RAM Serial Number 1\"," +
+ " \"REVISION\": \"Sample RAM Revision\" }",
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00110018\" }," +
+ " \"MANUFACTURER\": \"Sample RAM Manufacturer\"," +
+ " \"SERIAL\": \"Sample RAM Serial Number 2\"," +
+ " \"REVISION\": \"Sample RAM Revision\" }",
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00110018\" }," +
+ " \"MANUFACTURER\": \"Sample RAM Manufacturer\"," +
+ " \"MODEL\": \"Sample RAM Model\"," +
+ " \"SERIAL\": \"Sample RAM Serial Number 3\"," +
+ " \"REVISION\": \"Sample RAM Revision\" }",
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00110018\" }," +
+ " \"MANUFACTURER\": \"Sample RAM Manufacturer\"," +
+ " \"MODEL\": \"Sample RAM Model\"," +
+ " \"SERIAL\": \"Sample RAM Serial Number 4\"," +
+ " \"REVISION\": \"Sample RAM Revision\" }",
+ // POWER SUPPLY
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"00270000\" }," +
+ " \"MANUFACTURER\": \"Sample Power Supply Manufacturer\"," +
+ " \"MODEL\": \"Sample Power Supply Model Part Number\"," +
+ " \"SERIAL\": \"Sample Power Supply Serial Number\"," +
+ " \"REVISION\": \"Sample Power Supply Revision Level\"," +
+ " \"FIELDREPLACEABLE\": \"false\" }",
+ // TPM
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.3\"," +
+ " \"COMPONENTCLASSVALUE\": \"002B0000\" }," +
+ " \"MANUFACTURER\": \"41424300\"," +
+ " \"MODEL\": \"0200\"," +
+ " \"REVISION\": \"0102030405060708\" }"
+ };
+
+ [Test]
+ public void TestSampleDataA2() {
+ byte[] data = Convert.FromBase64String(RegistrySampleDataA2Base64);
+ ManifestV2 manifestV2 = new();
+ Dictionary> structures = Smbios.Smbios.ParseSmbiosData(data);
+ SmbiosHardwareManifestPlugin.AddComponentsToManifestV2(structures, manifestV2);
+ string jsonManifestV2 = manifestV2.ToString();
+
+ Assert.That(ComponentIdentifiersInJson, Has.Length.GreaterThan(0));
+
+ foreach (string componentJson in ComponentIdentifiersInJson) {
+ Assert.That(jsonManifestV2, Contains.Substring(componentJson));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/Storage/Storage.csproj b/dotnet/ComponentClassRegistry/Storage/Storage.csproj
new file mode 100644
index 0000000..659f80b
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/Storage.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageHardwareManifestPlugin.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageHardwareManifestPlugin.cs
new file mode 100644
index 0000000..b2e5e4c
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageHardwareManifestPlugin.cs
@@ -0,0 +1,74 @@
+using HardwareManifestPlugin;
+using HardwareManifestProto;
+using OidsProto;
+using StorageLib;
+using StorageNvme;
+using System.Data.Common;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace Storage;
+public class StorageHardwareManifestPlugin : HardwareManifestPluginBase {
+ public static readonly string TraitDescription = "Storage Component Class Registry";
+ public static readonly string TraitDescriptionUri = "https://trustedcomputinggroup.org/wp-content/uploads/Storage-Component-Class-Registry-Version-1.0-Revision-22_pub.pdf";
+ public static readonly string PluginName = "paccor.componentclassregistry.storage";
+ public static readonly string PluginDescription = "Collect hardware identifiers according to the Storage Component Class Registry.";
+
+ public StorageHardwareManifestPlugin() {
+ Name = PluginName;
+ Description = PluginDescription;
+ CollectsV2HardwareInformation = true;
+ CollectsV3HardwareInformation = false;
+ }
+
+ public override bool GatherHardwareIdentifiers() {
+ bool nvmeValid = StorageNvmeHelpers.CollectNvmeData(out List nvmeData);
+
+ if (!nvmeValid) {
+ return false;
+ }
+
+ List ataData = new();
+ List scsiData = new();
+ AddComponentsToManifestV2(nvmeData, ataData, scsiData, ManifestV2);
+ ManifestV3 = HardwareManifestConverter.FromManifestV2(ManifestV2, TraitDescription, TraitDescriptionUri);
+
+ return true;
+ }
+
+ public static void AddComponentsToManifestV2(List nvmeData, List ataData, List scsiData, ManifestV2 manifest) {
+ string storageRegistryOid = OidsUtils.Find(TCG_REGISTRY_COMPONENTCLASS_NODE.TcgRegistryComponentclassDisk).Oid;
+ foreach (StorageNvmeData data in nvmeData) {
+ ComponentIdentifier component = new() {
+ COMPONENTCLASS = new ComponentClass {
+ COMPONENTCLASSREGISTRY = storageRegistryOid,
+ COMPONENTCLASSVALUE = "02" + data.ClassCode.Hex
+ },
+ MANUFACTURER = NVMe_Val(data.NvmeCtrl.VID, true) + ":" + NVMe_Val(data.NvmeCtrl.SSVID, true) + ":" + NVMe_OUI(data.NvmeCtrl.IEEE),
+ MODEL = NVMe_String(data.NvmeCtrl.MN),
+ SERIAL = NVMe_Val(data.NvmeCtrl.FGUID, false) + ":" + NVMe_String(data.NvmeCtrl.SN) + ":" + NVMe_String(data.NvmeCtrl.SUBNQN),
+ REVISION = NVMe_Val(data.NvmeCtrl.VER, true) + ":" + NVMe_String(data.NvmeCtrl.FR)
+ };
+ manifest.COMPONENTS.Add(component);
+ }
+ }
+
+ public static string NVMe_Val(byte val) {
+ return NVMe_Val([val], false);
+ }
+
+ public static string NVMe_Val(byte[] val, bool littleEndianField) {
+ if (littleEndianField) {
+ Array.Reverse(val);
+ }
+ return Convert.ToHexString(val).PadLeft(val.Length / 2 + val.Length % 2, '0');
+ }
+
+ public static string NVMe_OUI(byte[] val) {
+ // These fields are specified to be little endian
+ return NVMe_Val(val, true);
+ }
+
+ public static string NVMe_String(byte[] val) {
+ return System.Text.Encoding.ASCII.GetString(val).TrimEnd(' ', '\0');
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageLinux.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageLinux.cs
new file mode 100644
index 0000000..73837a4
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageLinux.cs
@@ -0,0 +1,107 @@
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using StorageLib;
+
+namespace StorageWin;
+public class StorageLinux {
+ public static bool FindPciHeaderForNvmeDevName(out byte[] config, out byte[] vpd, string devName) {
+ bool result = false;
+ config = Array.Empty();
+ vpd = Array.Empty();
+
+ // devName ex /dev/nvme0n1
+ Regex findRegex = new Regex(@"^/dev/(nvme)([0-9A-Fa-f]+)n([0-9A-Fa-f]+)");
+ Match match = findRegex.Match(devName);
+ string findPath = "/" + match.Groups[1].Value + "/" + match.Groups[1].Value + match.Groups[2].Value;
+ string findName = match.Groups[1].Value + match.Groups[2].Value + "n" + match.Groups[3].Value;
+ //Directory.
+ // find /sys/bus/pci/devices/*/nvme/nvme0 -type d -name nvme0n1
+ // /sys/bus/pci/devices/0000:3b:00.0/nvme/nvme0/nvme0n1
+ Regex sysRegex = new Regex(@"^/sys/bus/pci/devices/[0-9A-Fa-f:.]+$");
+ string[] pciDevices = Directory.GetDirectories("/sys/bus/pci/devices");
+ foreach (string pciDevice in pciDevices) {
+ if (Directory.Exists(pciDevice + findPath + "/" + findName)) {
+ string pciDeviceConfigFile = pciDevice + "/config";
+ string pciDeviceVpdFile = pciDevice + "/vpd";
+ if (File.Exists(pciDeviceVpdFile)) {
+ vpd = File.ReadAllBytes(pciDeviceVpdFile);
+ }
+ if (File.Exists(pciDeviceConfigFile)) {
+ config = File.ReadAllBytes(pciDeviceConfigFile);
+ result = true;
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ public static bool Work() {
+ bool endResult = true;
+ Regex regex = new Regex(@"^(/dev/nvme[0-9A-Fa-f]+n[0-9A-Fa-f]+)p.*$");
+ string[] matches = Directory.EnumerateFileSystemEntries(@"/dev/").Where(f => regex.IsMatch(f)).Select(s => s.Split('p')[0]).Distinct().ToArray();
+
+ foreach (string devName in matches) {
+ endResult = endResult && Go(out StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl, devName);
+ endResult = endResult && FindPciHeaderForNvmeDevName(out byte[] config, out byte[] vpd, devName);
+ Console.WriteLine();
+ }
+ return endResult;
+ }
+
+ public static bool Go(out StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl, string devName) {
+ StorageLinuxStructs.NvmePassthruCmd passthru = StorageCommonHelpers.CreateStruct();
+ nvmeCtrl = new();
+ // open handle on nvme device
+ //SafeFileHandle handle = LinuxImports.open(devName, LinuxConstants.O_RDWR | LinuxConstants.O_NONBLOCK);
+ SafeFileHandle handle = File.OpenHandle(devName, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ if (handle.IsInvalid) {
+ return false;
+ }
+
+ IntPtr ptr = IntPtr.Zero;
+ bool endResult = false;
+ int allocationSize = Marshal.SizeOf();
+
+ // ioctl??
+ try {
+ passthru.adminCmd.opcode = 0x06;
+ passthru.adminCmd.dataLength = 4096;
+ passthru.adminCmd.cdw10 = 1;
+ passthru.timeout = 15000;
+ //int initialIoctl = LinuxImports.ioctl(handle, LinuxConstants.NVME_IOCTL_ID);
+
+ // Allocate memory for the buffer
+ ptr = Marshal.AllocHGlobal(allocationSize);
+
+ // Needed the starting address of the buffer for the next command to calculate the offset
+ passthru.adminCmd.addr = (ulong)IntPtr.Add(ptr, Marshal.OffsetOf("data").ToInt32());
+
+ // Copy the data from the managed object to the buffer
+ Marshal.StructureToPtr(passthru, ptr, true);
+
+ int result = StorageLinuxImports.ioctl(handle, StorageLinuxConstants.NVME_IOCTL_ADMIN_CMD, ptr);
+
+ if (result < 0) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ nvmeCtrl = Marshal.PtrToStructure(IntPtr.Add(ptr, Marshal.OffsetOf("data").ToInt32()));
+ endResult = true;
+ }
+ } finally {
+ handle.Dispose();
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return endResult;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxConstants.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxConstants.cs
new file mode 100644
index 0000000..0bf6844
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxConstants.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StorageWin;
+public class StorageLinuxConstants {
+ // fcntl.h
+ internal static readonly uint O_ACCMODE = 0x3;
+ internal static readonly uint O_RDONLY = 0x0;
+ internal static readonly uint O_WRONLY = 0x1;
+ internal static readonly uint O_RDWR = 0x2;
+ internal static readonly uint O_APPEND = 0x2000;
+ internal static readonly uint O_NONBLOCK = 0x0800;
+
+ // nvme_ioctl.h
+ internal static readonly uint NVME_IOCTL_ID = 0xC0484E40; //0x4E40;
+ internal static readonly uint NVME_IOCTL_ADMIN_CMD = 0xC0484E41; //0x4E41;
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxImports.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxImports.cs
new file mode 100644
index 0000000..8248054
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxImports.cs
@@ -0,0 +1,13 @@
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StorageWin;
+public class StorageLinuxImports {
+ [DllImport("libc", SetLastError = true)]
+ internal static extern int ioctl(SafeFileHandle fd, uint op, IntPtr data);
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxStructs.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxStructs.cs
new file mode 100644
index 0000000..6b85c1e
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageLinuxStructs.cs
@@ -0,0 +1,14 @@
+using StorageLib;
+using System.Runtime.InteropServices;
+
+namespace StorageWin;
+public class StorageLinuxStructs {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NvmePassthruCmd {
+ public StorageNvmeStructs.NvmeAdminCommand adminCmd;
+ [MarshalAs(UnmanagedType.U4)] public uint timeout;
+ [MarshalAs(UnmanagedType.U4)] public uint result;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)]
+ public byte[] data;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageWin.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageWin.cs
new file mode 100644
index 0000000..a8050fd
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageWin.cs
@@ -0,0 +1,227 @@
+using Microsoft.Win32.SafeHandles;
+using StorageLib;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace StorageWin;
+
+public class StorageWin {
+ public static bool QueryNvmeCnsThruIntelRstDriver(int deviceNumber = 0) {
+ bool result = QueryNvmeCnsThruIntelRstDriver(out StorageWinStructs.IntelNvmeIoctlPassthrough passThrough, deviceNumber, StorageWinConstants.NvmeCnsValue.IDENTIFY_CONTROLLER, 0);
+
+ if (result) {
+
+ }
+
+ return result;
+ }
+
+ public static bool ConvertIntelNvmeIoctlPassthroughDataToNvmeControllerData(out StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl, StorageWinStructs.IntelNvmeIoctlPassthrough passThrough) {
+ bool result = StorageCommonHelpers.ConvertBufferToNvmeControllerData(out nvmeCtrl, passThrough.data);
+
+ return result;
+ }
+
+
+ // If Identify Namespace Data is needed: QueryNvmeCnsThruIntelRstDriver(out StorageWinStructs.IntelNvmeIoctlPassthrough passThrough2, deviceNumber, StorageWinConstants.NvmeCnsValue.IDENTIFY_NAMESPACE_FOR_NSID, 1);
+ public static bool QueryNvmeCnsThruIntelRstDriver(out StorageWinStructs.IntelNvmeIoctlPassthrough passThrough, int deviceNumber = 0, StorageWinConstants.NvmeCnsValue cnsValue = StorageWinConstants.NvmeCnsValue.IDENTIFY_CONTROLLER, uint nsid = 0) {
+ string deviceString = string.Format(StorageWinConstants.DISK_HANDLE_SCSI, deviceNumber);
+
+ passThrough = StorageCommonHelpers.CreateStruct();
+
+ if (cnsValue == StorageWinConstants.NvmeCnsValue.IDENTIFY_NAMESPACE_FOR_NSID && nsid == 0) {
+ return false;
+ }
+
+ SafeFileHandle handle = StorageWinHelpers.OpenDevice(deviceString);
+
+ if (handle.IsInvalid) {
+ return false;
+ }
+
+ bool endResult = false;
+ IntPtr ptr = IntPtr.Zero;
+
+ int allocationSize = Marshal.SizeOf();
+ int srbHeaderSize = Marshal.SizeOf();
+ int dataBufferOffset = Marshal.OffsetOf("data").ToInt32();
+
+ try {
+ passThrough.Header.HeaderLength = (uint)srbHeaderSize;
+ passThrough.Header.Signature = Encoding.ASCII.GetBytes(StorageWinConstants.INTELNVM_SIGNATURE);
+ passThrough.Header.Timeout = 15;
+ passThrough.Header.ControlCode = StorageWinConstants.IOCTL_INTEL_NVME_PASSTHROUGH;
+ passThrough.Header.Length = (uint)allocationSize - (uint)srbHeaderSize;
+ passThrough.Version = StorageWinConstants.INTEL_NVME_PASS_THROUGH_VERSION;
+ passThrough.TargetId = 0;
+ passThrough.Lun = 0;
+
+ // MAY NEED TO SET SCSI Handle and PathId
+ // Handle = \\.\Scsi{deviceNumber}:
+ // PathId = 2
+ passThrough.PathId = 2; // TODO Pass in path id, possibly pass in object
+
+ passThrough.Parameters.Command.Admin.opcode = (byte)StorageWinConstants.NvmeAdminCommandOpcode.IDENTIFY;
+ passThrough.Parameters.IsIOCommandSet = 0; // 0 = Admin command set
+ passThrough.Parameters.Command.Admin.nsid = nsid;
+ passThrough.Parameters.Command.Admin.cdw10 = (uint)cnsValue;
+
+ passThrough.Parameters.DataBufferLength = 4096;
+ passThrough.Parameters.DataBufferOffset = (uint)dataBufferOffset;
+
+ // Allocate memory for the buffer
+ ptr = Marshal.AllocHGlobal(allocationSize);
+
+ // Needed the starting address of the buffer for the next command to calculate the offset
+ passThrough.Parameters.Command.Admin.addr = (ulong)IntPtr.Add(ptr, (int)passThrough.Parameters.DataBufferOffset).ToInt64();
+
+ // Copy the data from the managed object to the buffer
+ Marshal.StructureToPtr(passThrough, ptr, true);
+
+ // Prepare to talk to the device
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_SCSI_MINIPORT, ptr, allocationSize, ptr, allocationSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ passThrough = Marshal.PtrToStructure(ptr);
+ }
+
+ Console.WriteLine();
+ } finally {
+ Marshal.FreeHGlobal(ptr);
+
+ if (!handle.IsClosed) {
+ handle.Close();
+ }
+ }
+
+ return endResult;
+ }
+
+
+ // device handle assumed to be open and valid
+ public static bool GetScsiAddress(out StorageWinStructs.ScsiAddress scsiAddress, SafeFileHandle handle) {
+ bool endResult = false;
+ IntPtr ptr = IntPtr.Zero;
+
+ try {
+ scsiAddress = StorageCommonHelpers.CreateStruct();
+ int nOutBufferSize = Marshal.SizeOf();
+ ptr = Marshal.AllocHGlobal(nOutBufferSize);
+
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_SCSI_GET_ADDRESS, IntPtr.Zero, 0, ptr, nOutBufferSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ scsiAddress = Marshal.PtrToStructure(ptr);
+ endResult = true;
+ }
+ } finally {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return endResult;
+ }
+
+ // device handle assumed to be open already
+ public static bool QueryStorageProperty(out IntPtr ptr, SafeFileHandle handle, StorageWinConstants.StoragePropertyId propertyId) {
+ bool endResult = false;
+ IntPtr inPtr = IntPtr.Zero;
+ IntPtr outPtr = IntPtr.Zero;
+ ptr = IntPtr.Zero;
+
+ try {
+ int querySize = Marshal.SizeOf();
+ int headerSize = Marshal.SizeOf();
+ StorageWinStructs.StoragePropertyQuery query = StorageCommonHelpers.CreateStruct();
+ query.PropertyId = propertyId;
+ query.QueryType = StorageWinConstants.StorageQueryType.PropertyStandardQuery;
+
+ // Allocate memory for the buffer
+ inPtr = Marshal.AllocHGlobal(querySize);
+ outPtr = Marshal.AllocHGlobal(headerSize);
+
+ // Copy the data from the managed object to the buffer
+ Marshal.StructureToPtr(query, inPtr, true);
+
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ uint blah = StorageWinConstants.IOCTL_STORAGE_QUERY_PROPERTY;
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_STORAGE_QUERY_PROPERTY, inPtr, querySize, outPtr, headerSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ StorageWinStructs.StorageDescriptorHeader header = Marshal.PtrToStructure(outPtr);
+ if (header.Size > 0) {
+ headerSize = (int)header.Size;
+ }
+
+ Marshal.FreeHGlobal(outPtr);
+ outPtr = Marshal.AllocHGlobal(headerSize);
+
+ validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_STORAGE_QUERY_PROPERTY, inPtr, querySize, outPtr, headerSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ ptr = outPtr;
+ endResult = true;
+ }
+ }
+ } finally {
+ Marshal.FreeHGlobal(inPtr);
+ if (!endResult) {
+ Marshal.FreeHGlobal(outPtr); // Don't remove out buffer if result was valid. It still need to be converted into a structure.
+ }
+
+ }
+
+ return endResult;
+ }
+
+ // device handle assumed to be open and valid
+ public static bool QueryStorageDeviceProperty(out StorageWinStructs.StorageDeviceDescriptor descriptor, SafeFileHandle handle) {
+ descriptor = StorageCommonHelpers.CreateStruct();
+ bool endResult = QueryStorageProperty(out IntPtr ptr, handle, StorageWinConstants.StoragePropertyId.StorageDeviceProperty);
+
+ if (endResult) {
+ descriptor = Marshal.PtrToStructure(ptr);
+ }
+
+ Marshal.FreeHGlobal(ptr);
+
+ return endResult;
+ }
+
+
+ // device handle assumed to be open and valid
+ public static bool QueryStorageAdapterProperty(out StorageWinStructs.StorageAdapterDescriptor descriptor, SafeFileHandle handle) {
+ descriptor = StorageCommonHelpers.CreateStruct();
+ bool endResult = QueryStorageProperty(out IntPtr ptr, handle, StorageWinConstants.StoragePropertyId.StorageAdapterProperty);
+
+ if (endResult) {
+ descriptor = Marshal.PtrToStructure(ptr);
+ }
+
+ Marshal.FreeHGlobal(ptr);
+
+ return endResult;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageWinConstants.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageWinConstants.cs
new file mode 100644
index 0000000..dd4dc7a
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageWinConstants.cs
@@ -0,0 +1,95 @@
+namespace StorageWin;
+
+public class StorageWinConstants {
+ // NVMe Constants
+ // NVM Express® Base Specification, Revision 2.1, 5.1.13.1 Figure 310: Identify – CNS Values
+ public enum NvmeCnsValue : uint {
+ IDENTIFY_NAMESPACE_FOR_NSID = 0x0, // Identify Namespace data structure for the specified NSID
+ IDENTIFY_CONTROLLER = 0x01, // Identify Controller data structure for the controller processing the command
+ IDENTIFY_ACTIVE_NAMESPACE_LIST = 0x02, // Active Namespace ID list
+ IDENTIFY_NSID_DESCRIPTOR_FOR_NSID = 0x03 // Namespace Identification Descriptor list for the specified NSID.
+ }
+
+ // NVM Express® Base Specification, Revision 2.1, 3.1.3.4 Command Support Requirements
+ public enum NvmeAdminCommandOpcode : byte {
+ GET_LOG_PAGE = 0x02,
+ IDENTIFY = 0x06
+ }
+
+ // Windows Constants
+ public static readonly string DISK_HANDLE_PD = @"\\.\PhysicalDrive{0}"; // PD
+ public static readonly string DISK_HANDLE_SCSI = @"\\.\Scsi{0}:"; // SCSI
+ public static readonly uint NVME_PASS_THROUGH_SRB_IO_CODE = 0xe0002000;
+ public static readonly uint IOCTL_SCSI_MINIPORT = 0x0004D008;
+ public static readonly uint NVME_STORPORT_DRIVER = 0xE000;
+ public static readonly uint FILE_DEVICE_CONTROLLER = 0x00000004; // winioctl.h
+ public static readonly uint FILE_DEVICE_MASS_STORAGE = 0x0000002d; // winioctl.h
+ public static readonly uint FILE_DEVICE_UNKNOWN = 0x00000022; // winioctl.h
+ public static readonly uint IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE; // winioctl.h
+ public static readonly uint IOCTL_STORAGE_QUERY_PROPERTY = StorageWinHelpers.CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS); // winioctl.h
+ public static readonly uint IOCTL_STORAGE_GET_DEVICE_NUMBER = StorageWinHelpers.CTL_CODE(IOCTL_STORAGE_BASE, 0x0420, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS); // winioctl.h
+ public static readonly uint IOCTL_SCSI_BASE = FILE_DEVICE_CONTROLLER; // ntddscsi.h
+ public static readonly uint IOCTL_PCI_READ_CONFIG = StorageWinHelpers.CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS);
+ public static readonly uint IOCTL_SCSI_GET_ADDRESS = StorageWinHelpers.CTL_CODE(IOCTL_SCSI_BASE, 0x0406, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS); // ntddscsi.h
+
+ // Intel RST Constants
+ public static readonly string INTELNVM_SIGNATURE = "IntelNvm";
+ public static readonly uint IOCTL_INTEL_NVME_PASSTHROUGH = StorageWinHelpers.CTL_CODE(0xF000, 0xA02, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS);//0xf0002808;
+ public static readonly byte INTEL_NVME_PASS_THROUGH_VERSION = 1;
+
+ // Windows IOCTL Buffered Methods: winioctl.h
+ public enum IoctlMethodCodes {
+ METHOD_BUFFERED = 0,
+ METHOD_IN_DIRECT = 1,
+ METHOD_OUT_DIRECT = 2,
+ METHOD_NEITHER = 3
+ }
+ // Windows IOCTL File Access: winioctl.h
+ [Flags]
+ public enum IoctlFileAccess {
+ FILE_ANY_ACCESS = 0,
+ FILE_READ_ACCESS = 1,
+ FILE_WRITE_ACCESS = 2
+ }
+
+ // Windows STORAGE_PROPERTY_ID: winioctl.h
+ public enum StoragePropertyId : uint {
+ StorageDeviceProperty = 0,
+ StorageAdapterProperty
+ }
+
+ // Windows STORAGE_QUERY_TYPE: winioctl.h
+ public enum StorageQueryType : uint {
+ PropertyStandardQuery = 0,
+ PropertyExistsQuery,
+ PropertyMaskQuery,
+ PropertyQueryMaxDefined
+ }
+
+ // Windows STORAGE_BUS_TYPE: winioctl.h
+ public enum StorageBusType : byte {
+ BusTypeUnknown = 0x00,
+ BusTypeScsi,
+ BusTypeAtapi,
+ BusTypeAta,
+ BusType1394,
+ BusTypeSsa,
+ BusTypeFibre,
+ BusTypeUsb,
+ BusTypeRAID,
+ BusTypeiScsi,
+ BusTypeSas,
+ BusTypeSata,
+ BusTypeSd,
+ BusTypeMmc,
+ BusTypeVirtual,
+ BusTypeFileBackedVirtual,
+ BusTypeSpaces,
+ BusTypeNvme,
+ BusTypeSCM,
+ BusTypeUfs,
+ BusTypeNvmeof,
+ BusTypeMax,
+ BusTypeMaxReserved = 0x7F
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageWinHelpers.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageWinHelpers.cs
new file mode 100644
index 0000000..9bda365
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageWinHelpers.cs
@@ -0,0 +1,24 @@
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+namespace StorageWin;
+public class StorageWinHelpers {
+ public static uint CTL_CODE(uint deviceType, uint function, uint method, uint access) {
+ return ((deviceType << 16) | (access << 14) | (function << 2) | method);
+ }
+ public static uint CTL_CODE(uint deviceType, uint function, StorageWinConstants.IoctlMethodCodes method, StorageWinConstants.IoctlFileAccess access) {
+ return CTL_CODE(deviceType, function, (uint)method, (uint)access);
+ }
+
+ public static SafeFileHandle OpenDevice(string devicePath) {
+ //SafeFileHandle hDevice = MyWorkingKernelImports.CreateFile(devicePath, (uint)FileAccess.ReadWrite, (uint)FileShare.ReadWrite,
+ // IntPtr.Zero, (uint)FileMode.Open, (uint)FileAttributes.Normal, IntPtr.Zero);
+ SafeFileHandle hDevice = new();
+ try {
+ hDevice = File.OpenHandle(devicePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+ } catch (Exception e) { // Any error should result in the handle being set to invalid
+ hDevice.SetHandleAsInvalid();
+ }
+ return hDevice;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageWinImports.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageWinImports.cs
new file mode 100644
index 0000000..30b4992
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageWinImports.cs
@@ -0,0 +1,26 @@
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StorageWin;
+
+public class StorageWinImports {
+ [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
+ internal static extern bool DeviceIoControl(
+ SafeFileHandle hDevice,
+ uint dwIoControlCode,
+ IntPtr lpInBuffer,
+ int nInBufferSize,
+ IntPtr lpOutBuffer,
+ int nOutBufferSize,
+ ref int lpBytesReturned,
+ ref NativeOverlapped lpOverlapped
+ );
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
+}
diff --git a/dotnet/ComponentClassRegistry/Storage/src/StorageWinStructs.cs b/dotnet/ComponentClassRegistry/Storage/src/StorageWinStructs.cs
new file mode 100644
index 0000000..5a8fcb8
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/Storage/src/StorageWinStructs.cs
@@ -0,0 +1,137 @@
+using StorageLib;
+using System.Runtime.InteropServices;
+
+namespace StorageWin;
+
+public class StorageWinStructs {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CompletionQueueEntry {
+ // COMPLETION_QUEUE_ENTRY
+ [MarshalAs(UnmanagedType.U4)] public uint dw0;
+ [MarshalAs(UnmanagedType.U4)] public uint dw1;
+ [MarshalAs(UnmanagedType.U4)] public uint dw2;
+ [MarshalAs(UnmanagedType.U4)] public uint dw3;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct IntelNvmePassthroughParameters {
+ // NVME_PASS_THROUGH_PARAMETERS
+ public StorageNvmeStructs.NvmeCommand Command;
+ public byte IsIOCommandSet; // False/0 for Admin queue
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] // IsIOCommandSet must conform to DWORD alignment.
+ public byte[]
+ IsIOCommandSetPadding; // If the size of IsIOCommandSet changes, this padding might need to be adjusted.
+
+ public CompletionQueueEntry Completion;
+
+ [MarshalAs(UnmanagedType.U4)]
+ public uint DataBufferOffset; // Must be DWORD aligned offset from beginning of SRB_IO_CONTROL
+
+ [MarshalAs(UnmanagedType.U4)] public uint DataBufferLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
+ public byte[] Reserved;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct IntelNvmeIoctlPassthrough {
+ // Intel NVME_IOCTL_PASS_THROUGH
+ public SrbIoControl Header;
+ public byte Version;
+ public byte PathId;
+ public byte TargetId;
+ public byte Lun;
+ public IntelNvmePassthroughParameters Parameters;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)]
+ public byte[] data;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SrbIoControl {
+ // SRB_IO_CONTROL
+ [MarshalAs(UnmanagedType.U4)] public uint HeaderLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public byte[] Signature;
+
+ [MarshalAs(UnmanagedType.U4)] public uint Timeout;
+ [MarshalAs(UnmanagedType.U4)] public uint ControlCode;
+ [MarshalAs(UnmanagedType.U4)] public uint ReturnCode;
+ [MarshalAs(UnmanagedType.U4)] public uint Length;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageAdapterDescriptor {
+ // STORAGE_ADAPTER_DESCRIPTOR: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint Version;
+ [MarshalAs(UnmanagedType.U4)] public uint Size;
+ [MarshalAs(UnmanagedType.U4)] public uint MaximumTransferLength;
+ [MarshalAs(UnmanagedType.U4)] public uint MaximumPhysicalPages;
+ [MarshalAs(UnmanagedType.U4)] public uint AlignmentMask;
+ public byte AdapterUsesPio;
+ public byte AdapterScansDown;
+ public byte CommandQueueing;
+ public byte AcceleratedTransfer;
+ [MarshalAs(UnmanagedType.U1)] public StorageWinConstants.StorageBusType BusType;
+ [MarshalAs(UnmanagedType.U2)] public ushort BusMajorVersion;
+ [MarshalAs(UnmanagedType.U2)] public ushort BusMinorVersion;
+ public byte SrbType;
+ public byte AddressType;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageDeviceDescriptor {
+ // STORAGE_DEVICE_DESCRIPTOR: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint Version;
+ [MarshalAs(UnmanagedType.U4)] public uint Size;
+ [MarshalAs(UnmanagedType.U1)] public byte DeviceType;
+ [MarshalAs(UnmanagedType.U1)] public byte DeviceTypeModifier;
+ [MarshalAs(UnmanagedType.U1)] public byte RemovableMedia;
+ [MarshalAs(UnmanagedType.U1)] public byte CommandQueueing;
+ [MarshalAs(UnmanagedType.U4)] public uint VendorIdOffset;
+ [MarshalAs(UnmanagedType.U4)] public uint ProductIdOffset;
+ [MarshalAs(UnmanagedType.U4)] public uint ProductRevisionOffset;
+ [MarshalAs(UnmanagedType.U4)] public uint SerialNumberOffset;
+ [MarshalAs(UnmanagedType.U1)] public StorageWinConstants.StorageBusType BusType;
+ [MarshalAs(UnmanagedType.U4)] public uint RawPropertiesLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
+ public byte[] RawDeviceProperties;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StoragePropertyQuery {
+ // STORAGE_PROPERTY_QUERY: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public StorageWinConstants.StoragePropertyId PropertyId;
+ [MarshalAs(UnmanagedType.U4)] public StorageWinConstants.StorageQueryType QueryType;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
+ public byte[] AdditionalParameters;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageDescriptorHeader {
+ // STORAGE_DESCRIPTOR_HEADER: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint Version;
+ [MarshalAs(UnmanagedType.U4)] public uint Size;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ScsiAddress {
+ [MarshalAs(UnmanagedType.U4)] public uint Length;
+ public byte PortNumber;
+ public byte PathId;
+ public byte TargetId;
+ public byte Lun;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageDeviceNumber {
+ // STORAGE_DEVICE_NUMBER: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint DeviceType;
+ [MarshalAs(UnmanagedType.U4)] public uint DeviceNumber;
+ [MarshalAs(UnmanagedType.U4)] public uint PartitionNumber;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageCli/StorageCli.csproj b/dotnet/ComponentClassRegistry/StorageCli/StorageCli.csproj
new file mode 100644
index 0000000..f9dbb0e
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageCli/StorageCli.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/StorageCli/src/Program.cs b/dotnet/ComponentClassRegistry/StorageCli/src/Program.cs
new file mode 100644
index 0000000..2fcc389
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageCli/src/Program.cs
@@ -0,0 +1,41 @@
+using CliLib;
+using Storage;
+using System.Runtime.InteropServices;
+
+namespace StorageCli;
+public class StorageCli {
+ public static int Main(string[] args) {
+ int returnCode = (int)ClientExitCodes.SUCCESS;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ // Linux requires sudo
+ int result = CliOptions.IsUserPrivileged();
+ if (result != (int)ClientExitCodes.SUCCESS) {
+ Console.WriteLine("Storage data retrieval on Linux requires admin privileges. Please run as root.");
+ return result;
+ }
+ }
+
+ CliOptions? cli = CliOptions.ParseArguments(args);
+
+ if (cli == null) {
+ return (int)ClientExitCodes.CLI_PARSE_ERROR;
+ }
+
+ StorageHardwareManifestPlugin plugin = new();
+ if (!plugin.GatherHardwareIdentifiers()) {
+ Console.WriteLine("Storage hardware information gathered was not valid.");
+ return (int)ClientExitCodes.GATHER_HW_MANIFEST_FAIL;
+ }
+
+ // All smbios data should be validated at this point.
+ if (cli.PrintV2 || (!cli.PrintV2 && !cli.PrintV3)) { // V2 should be printed by default not matter what
+ Console.WriteLine(plugin.ManifestV2.ToString());
+ }
+ if (cli.PrintV3) {
+ Console.WriteLine(plugin.ManifestV3.ToString());
+ }
+
+ return returnCode;
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/StorageLib/StorageLib.csproj b/dotnet/ComponentClassRegistry/StorageLib/StorageLib.csproj
new file mode 100644
index 0000000..bafd05b
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/StorageLib.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxConstants.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxConstants.cs
new file mode 100644
index 0000000..972ab69
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxConstants.cs
@@ -0,0 +1,3 @@
+namespace StorageLib.Linux;
+public class StorageLinuxConstants {
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxImports.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxImports.cs
new file mode 100644
index 0000000..626c060
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxImports.cs
@@ -0,0 +1,11 @@
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+namespace StorageLib.Linux;
+
+public class StorageLinuxImports {
+ public const string libName = "libc";
+
+ [DllImport(libName, SetLastError = true)]
+ public static extern int ioctl(SafeFileHandle fd, uint op, IntPtr data);
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxStructs.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxStructs.cs
new file mode 100644
index 0000000..bdf13a2
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Linux/StorageLinuxStructs.cs
@@ -0,0 +1,3 @@
+namespace StorageLib.Linux;
+public class StorageLinuxStructs {
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonConstants.cs b/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonConstants.cs
new file mode 100644
index 0000000..af1615a
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonConstants.cs
@@ -0,0 +1,6 @@
+namespace StorageLib;
+
+public class StorageCommonConstants {
+
+
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonHelpers.cs b/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonHelpers.cs
new file mode 100644
index 0000000..4abaf60
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonHelpers.cs
@@ -0,0 +1,83 @@
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+namespace StorageLib;
+public class StorageCommonHelpers {
+ // Expects unmanaged memory of given len to be allocated to ptr
+ internal static void ZeroMemory(IntPtr ptr, int len) {
+ for (int i = 0; i < len; i++) {
+ Marshal.WriteByte(ptr, i, 0);
+ }
+ }
+
+ public static T CreateStruct() where T : struct {
+ int size = Marshal.SizeOf();
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try {
+ // Initialize memory to zero
+ ZeroMemory(ptr, size);
+ T result = Marshal.PtrToStructure(ptr); // This object becomes managed and doesn't need to be freed later
+ return result;
+ } finally {
+ Marshal.FreeHGlobal(ptr); // The buffer can be safely freed after the structure is created
+ }
+ }
+
+ public static T CreateStruct(byte[] buffer) where T : struct {
+ int size = Marshal.SizeOf();
+
+ // if buffer is larger than size, the extra bytes from buffer are ignored
+ // if buffer is smaller than size, the extra bytes in size are zeroed
+
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try {
+ ZeroMemory(ptr, size);
+ Marshal.Copy(buffer, 0, ptr, size);
+ T result = Marshal.PtrToStructure(ptr); // This object becomes managed and doesn't need to be freed later
+ return result;
+ } finally {
+ Marshal.FreeHGlobal(ptr); // The buffer can be safely freed after the structure is created
+ }
+ }
+
+ public static byte[] CreateByteArray(T obj) where T : struct {
+ int size = Marshal.SizeOf();
+
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try {
+ ZeroMemory(ptr, size);
+ Marshal.StructureToPtr(obj, ptr, true);
+ byte[] buffer = ConvertIntPtrToByteArray(ptr, size);
+ return buffer;
+ } finally {
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+
+ public static byte[] ConvertIntPtrToByteArray(IntPtr ptr, int size) {
+ if (ptr == IntPtr.Zero || size == 0) {
+ return Array.Empty();
+ }
+ byte[] buffer = new byte[size];
+ Marshal.Copy(ptr, buffer, 0, size);
+ return buffer;
+ }
+
+ public static SafeFileHandle OpenDevice(string devicePath) {
+ SafeFileHandle handle = new();
+ try {
+ handle = File.OpenHandle(devicePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
+ } catch (FileNotFoundException e) { // Any error should result in the handle being set to invalid
+ handle.SetHandleAsInvalid();
+ }
+
+ if (!IsDeviceHandleReady(handle)) { // Also ensure handle is not closed
+ handle.SetHandleAsInvalid();
+ }
+ return handle;
+ }
+
+ public static bool IsDeviceHandleReady(SafeFileHandle handle) {
+ return handle is { IsInvalid: false, IsClosed: false };
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonStructs.cs b/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonStructs.cs
new file mode 100644
index 0000000..4386ead
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/StorageCommonStructs.cs
@@ -0,0 +1,6 @@
+using System.Runtime.InteropServices;
+
+namespace StorageLib;
+public class StorageCommonStructs {
+
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWin.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWin.cs
new file mode 100644
index 0000000..b77ac1b
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWin.cs
@@ -0,0 +1,199 @@
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+namespace StorageLib.Windows;
+
+public class StorageWin {
+ public static uint CTL_CODE(uint deviceType, uint function, uint method, uint access) {
+ return ((deviceType << 16) | (access << 14) | (function << 2) | method);
+ }
+ public static uint CTL_CODE(uint deviceType, uint function, StorageWinConstants.IoctlMethodCodes method, StorageWinConstants.IoctlFileAccess access) {
+ return CTL_CODE(deviceType, function, (uint)method, (uint)access);
+ }
+
+ public static bool GetStorageDeviceNumber(out StorageWinStructs.StorageDeviceNumber sdn, int physicalDiskNumber) {
+ string pdHandle = string.Format(StorageWinConstants.DISK_HANDLE_PD, physicalDiskNumber);
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(pdHandle);
+ return GetStorageDeviceNumber(out sdn, handle);
+ }
+
+ public static bool GetStorageDeviceNumber(out StorageWinStructs.StorageDeviceNumber sdn, SafeFileHandle handle) {
+ bool endResult = false;
+ sdn = StorageCommonHelpers.CreateStruct();
+
+ // device handle assumed to be open already
+ if (!StorageCommonHelpers.IsDeviceHandleReady(handle)) {
+ return false;
+ }
+
+ IntPtr outPtr = IntPtr.Zero;
+
+ // from cfgmgr32.h
+ // DEVINST = uint
+ //
+ int bufferSize = Marshal.SizeOf();
+ try {
+ // Allocate memory for the buffer
+ outPtr = Marshal.AllocHGlobal(bufferSize);
+
+ // Prepare to talk to the device
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_STORAGE_GET_DEVICE_NUMBER, IntPtr.Zero, 0, outPtr, bufferSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ sdn = Marshal.PtrToStructure(outPtr);
+ endResult = true;
+ }
+ } finally {
+ Marshal.FreeHGlobal(outPtr);
+ }
+
+ return endResult;
+ }
+
+ public static bool GetScsiAddress(out StorageWinStructs.ScsiAddress scsiAddress, string deviceHandle) {
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(deviceHandle);
+ return GetScsiAddress(out scsiAddress, handle);
+ }
+
+ // device handle assumed to be open and valid
+ public static bool GetScsiAddress(out StorageWinStructs.ScsiAddress scsiAddress, SafeFileHandle handle) {
+ bool endResult = false;
+ scsiAddress = StorageCommonHelpers.CreateStruct();
+ IntPtr ptr = IntPtr.Zero;
+
+ // device handle assumed to be open already
+ if (!StorageCommonHelpers.IsDeviceHandleReady(handle)) {
+ return false;
+ }
+
+ try {
+ scsiAddress = StorageCommonHelpers.CreateStruct();
+ int nOutBufferSize = Marshal.SizeOf();
+ ptr = Marshal.AllocHGlobal(nOutBufferSize);
+
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_SCSI_GET_ADDRESS, IntPtr.Zero, 0, ptr, nOutBufferSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ scsiAddress = Marshal.PtrToStructure(ptr);
+ endResult = true;
+ }
+ } finally {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return endResult;
+ }
+
+ public static bool QueryStorageProperty(out byte[] buffer, string deviceHandle, StorageWinConstants.StoragePropertyId propertyId) {
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(deviceHandle);
+ return QueryStorageProperty(out buffer, handle, propertyId);
+ }
+
+ public static bool QueryStorageProperty(out byte[] buffer, SafeFileHandle handle, StorageWinConstants.StoragePropertyId propertyId) {
+ bool endResult = false;
+ IntPtr inPtr = IntPtr.Zero;
+ IntPtr outPtr = IntPtr.Zero;
+ buffer = Array.Empty();
+
+ // device handle assumed to be open already
+ if (!StorageCommonHelpers.IsDeviceHandleReady(handle)) {
+ return false;
+ }
+
+ try {
+ int querySize = Marshal.SizeOf();
+ int headerSize = Marshal.SizeOf();
+ StorageWinStructs.StoragePropertyQuery query = StorageCommonHelpers.CreateStruct();
+ query.PropertyId = propertyId;
+ query.QueryType = StorageWinConstants.StorageQueryType.PropertyStandardQuery;
+
+ // Allocate memory for the buffer
+ inPtr = Marshal.AllocHGlobal(querySize);
+ outPtr = Marshal.AllocHGlobal(headerSize);
+
+ // Copy the data from the managed object to the buffer
+ Marshal.StructureToPtr(query, inPtr, true);
+
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ uint blah = StorageWinConstants.IOCTL_STORAGE_QUERY_PROPERTY;
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_STORAGE_QUERY_PROPERTY, inPtr, querySize, outPtr, headerSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ StorageWinStructs.StorageDescriptorHeader header = Marshal.PtrToStructure(outPtr);
+ if (header.Size > 0) {
+ headerSize = (int)header.Size;
+ }
+
+ Marshal.FreeHGlobal(outPtr);
+ outPtr = Marshal.AllocHGlobal(headerSize);
+
+ validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_STORAGE_QUERY_PROPERTY, inPtr, querySize, outPtr, headerSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ buffer = StorageCommonHelpers.ConvertIntPtrToByteArray(outPtr, headerSize);
+ endResult = true;
+ }
+ }
+ } finally {
+ Marshal.FreeHGlobal(inPtr);
+ Marshal.FreeHGlobal(outPtr);
+ }
+
+ return endResult;
+ }
+
+ public static bool QueryStorageDeviceProperty(out StorageWinStructs.StorageDeviceDescriptor descriptor, string deviceHandle) {
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(deviceHandle);
+ return QueryStorageDeviceProperty(out descriptor, handle);
+ }
+
+ public static bool QueryStorageDeviceProperty(out StorageWinStructs.StorageDeviceDescriptor descriptor, SafeFileHandle handle) {
+ descriptor = StorageCommonHelpers.CreateStruct();
+ bool endResult = QueryStorageProperty(out byte[] buffer, handle, StorageWinConstants.StoragePropertyId.StorageDeviceProperty);
+
+ if (endResult) {
+ descriptor = StorageCommonHelpers.CreateStruct(buffer);
+ }
+
+ return endResult;
+ }
+
+ public static bool QueryStorageAdapterProperty(out StorageWinStructs.StorageAdapterDescriptor descriptor, string deviceHandle) {
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(deviceHandle);
+ return QueryStorageAdapterProperty(out descriptor, handle);
+ }
+
+ public static bool QueryStorageAdapterProperty(out StorageWinStructs.StorageAdapterDescriptor descriptor, SafeFileHandle handle) {
+ descriptor = StorageCommonHelpers.CreateStruct();
+ bool endResult = QueryStorageProperty(out byte[] buffer, handle, StorageWinConstants.StoragePropertyId.StorageAdapterProperty);
+
+ if (endResult) {
+ descriptor = StorageCommonHelpers.CreateStruct(buffer);
+ }
+
+ return endResult;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinConstants.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinConstants.cs
new file mode 100644
index 0000000..46fe0b2
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinConstants.cs
@@ -0,0 +1,76 @@
+using StorageLib;
+
+namespace StorageLib.Windows;
+
+public class StorageWinConstants {// Windows Constants
+ public static readonly string DISK_HANDLE_PD = @"\\.\PhysicalDrive{0}"; // PD
+ public static readonly string DISK_HANDLE_SCSI = @"\\.\Scsi{0}:"; // SCSI
+ public static readonly uint NVME_PASS_THROUGH_SRB_IO_CODE = 0xe0002000;
+ public static readonly uint IOCTL_SCSI_MINIPORT = 0x0004D008;
+ public static readonly uint NVME_STORPORT_DRIVER = 0xE000;
+ public static readonly uint FILE_DEVICE_CONTROLLER = 0x00000004; // winioctl.h
+ public static readonly uint FILE_DEVICE_MASS_STORAGE = 0x0000002d; // winioctl.h
+ public static readonly uint FILE_DEVICE_UNKNOWN = 0x00000022; // winioctl.h
+ public static readonly uint IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE; // winioctl.h
+ public static readonly uint IOCTL_STORAGE_QUERY_PROPERTY = StorageWin.CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS); // winioctl.h
+ public static readonly uint IOCTL_STORAGE_GET_DEVICE_NUMBER = StorageWin.CTL_CODE(IOCTL_STORAGE_BASE, 0x0420, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS); // winioctl.h
+ public static readonly uint IOCTL_SCSI_BASE = FILE_DEVICE_CONTROLLER; // ntddscsi.h
+ public static readonly uint IOCTL_PCI_READ_CONFIG = StorageWin.CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS);
+ public static readonly uint IOCTL_SCSI_GET_ADDRESS = StorageWin.CTL_CODE(IOCTL_SCSI_BASE, 0x0406, IoctlMethodCodes.METHOD_BUFFERED, IoctlFileAccess.FILE_ANY_ACCESS); // ntddscsi.h
+
+ // Windows IOCTL Buffered Methods: winioctl.h
+ public enum IoctlMethodCodes {
+ METHOD_BUFFERED = 0,
+ METHOD_IN_DIRECT = 1,
+ METHOD_OUT_DIRECT = 2,
+ METHOD_NEITHER = 3
+ }
+ // Windows IOCTL File Access: winioctl.h
+ [Flags]
+ public enum IoctlFileAccess {
+ FILE_ANY_ACCESS = 0,
+ FILE_READ_ACCESS = 1,
+ FILE_WRITE_ACCESS = 2
+ }
+
+ // Windows STORAGE_PROPERTY_ID: winioctl.h
+ public enum StoragePropertyId : uint {
+ StorageDeviceProperty = 0,
+ StorageAdapterProperty
+ }
+
+ // Windows STORAGE_QUERY_TYPE: winioctl.h
+ public enum StorageQueryType : uint {
+ PropertyStandardQuery = 0,
+ PropertyExistsQuery,
+ PropertyMaskQuery,
+ PropertyQueryMaxDefined
+ }
+
+ // Windows STORAGE_BUS_TYPE: winioctl.h
+ public enum StorageBusType : byte {
+ BusTypeUnknown = 0x00,
+ BusTypeScsi,
+ BusTypeAtapi,
+ BusTypeAta,
+ BusType1394,
+ BusTypeSsa,
+ BusTypeFibre,
+ BusTypeUsb,
+ BusTypeRAID,
+ BusTypeiScsi,
+ BusTypeSas,
+ BusTypeSata,
+ BusTypeSd,
+ BusTypeMmc,
+ BusTypeVirtual,
+ BusTypeFileBackedVirtual,
+ BusTypeSpaces,
+ BusTypeNvme,
+ BusTypeSCM,
+ BusTypeUfs,
+ BusTypeNvmeof,
+ BusTypeMax,
+ BusTypeMaxReserved = 0x7F
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinImports.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinImports.cs
new file mode 100644
index 0000000..f0f6d76
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinImports.cs
@@ -0,0 +1,55 @@
+
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace StorageLib.Windows;
+
+public class StorageWinImports {
+ public const string kernelDll = "kernel32.dll";
+
+ [DllImport(kernelDll, ExactSpelling = true, SetLastError = true)]
+ public static extern bool DeviceIoControl(SafeFileHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer, int nInBufferSize, IntPtr lpOutBuffer, int nOutBufferSize, ref int lpBytesReturned, ref NativeOverlapped lpOverlapped);
+
+ public static Task> Powershell(string arguments) {
+ char ch = '"'; // couldn't get escaping to work properly without this method
+ ProcessStartInfo info = new() {
+ FileName = "powershell.exe",
+ Arguments = "-NoProfile -ExecutionPolicy Bypass -Command " + ch + arguments + ch,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ return Execute(info);
+ }
+
+ private static Task> Execute(ProcessStartInfo info) {
+ TaskCompletionSource> source = new();
+ Process process = new() {
+ StartInfo = info,
+ EnableRaisingEvents = true
+ };
+
+ process.Exited += (sender, args) => {
+ source.SetResult(new Tuple(process.ExitCode, process.StandardError.ReadToEnd(), process.StandardOutput.ReadToEnd()));
+ if (process.ExitCode != 0) {
+ source.SetException(new Exception($"Command `{process.StartInfo.FileName} {process.StartInfo.Arguments}` failed with exit code `{process.ExitCode}`"));
+ }
+
+ process.Dispose();
+ };
+
+ try {
+ process.Start();
+ process.WaitForExit();
+ } catch (Exception e) {
+ source.SetException(e);
+ } finally {
+ process.Dispose();
+ }
+
+ return source.Task;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinStructs.cs b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinStructs.cs
new file mode 100644
index 0000000..cf3ad3e
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLib/src/Windows/StorageWinStructs.cs
@@ -0,0 +1,99 @@
+using StorageLib;
+using System.Runtime.InteropServices;
+
+namespace StorageLib.Windows;
+public class StorageWinStructs {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SrbIoControl {
+ // SRB_IO_CONTROL
+ [MarshalAs(UnmanagedType.U4)] public uint HeaderLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public byte[] Signature;
+
+ [MarshalAs(UnmanagedType.U4)] public uint Timeout;
+ [MarshalAs(UnmanagedType.U4)] public uint ControlCode;
+ [MarshalAs(UnmanagedType.U4)] public uint ReturnCode;
+ [MarshalAs(UnmanagedType.U4)] public uint Length;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageAdapterDescriptor {
+ // STORAGE_ADAPTER_DESCRIPTOR: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint Version;
+ [MarshalAs(UnmanagedType.U4)] public uint Size;
+ [MarshalAs(UnmanagedType.U4)] public uint MaximumTransferLength;
+ [MarshalAs(UnmanagedType.U4)] public uint MaximumPhysicalPages;
+ [MarshalAs(UnmanagedType.U4)] public uint AlignmentMask;
+ public byte AdapterUsesPio;
+ public byte AdapterScansDown;
+ public byte CommandQueueing;
+ public byte AcceleratedTransfer;
+ [MarshalAs(UnmanagedType.U1)] public StorageWinConstants.StorageBusType BusType;
+ [MarshalAs(UnmanagedType.U2)] public ushort BusMajorVersion;
+ [MarshalAs(UnmanagedType.U2)] public ushort BusMinorVersion;
+ public byte SrbType;
+ public byte AddressType;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageDeviceDescriptor {
+ // STORAGE_DEVICE_DESCRIPTOR: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint Version;
+ [MarshalAs(UnmanagedType.U4)] public uint Size;
+ [MarshalAs(UnmanagedType.U1)] public byte DeviceType;
+ [MarshalAs(UnmanagedType.U1)] public byte DeviceTypeModifier;
+ [MarshalAs(UnmanagedType.U1)] public byte RemovableMedia;
+ [MarshalAs(UnmanagedType.U1)] public byte CommandQueueing;
+ [MarshalAs(UnmanagedType.U4)] public uint VendorIdOffset;
+ [MarshalAs(UnmanagedType.U4)] public uint ProductIdOffset;
+ [MarshalAs(UnmanagedType.U4)] public uint ProductRevisionOffset;
+ [MarshalAs(UnmanagedType.U4)] public uint SerialNumberOffset;
+ [MarshalAs(UnmanagedType.U1)] public StorageWinConstants.StorageBusType BusType;
+ [MarshalAs(UnmanagedType.U4)] public uint RawPropertiesLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
+ public byte[] RawDeviceProperties;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StoragePropertyQuery {
+ // STORAGE_PROPERTY_QUERY: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public StorageWinConstants.StoragePropertyId PropertyId;
+ [MarshalAs(UnmanagedType.U4)] public StorageWinConstants.StorageQueryType QueryType;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
+ public byte[] AdditionalParameters;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageDescriptorHeader {
+ // STORAGE_DESCRIPTOR_HEADER: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint Version;
+ [MarshalAs(UnmanagedType.U4)] public uint Size;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ScsiAddress {
+ [MarshalAs(UnmanagedType.U4)] public uint Length;
+ public byte PortNumber;
+ public byte PathId;
+ public byte TargetId;
+ public byte Lun;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StorageDeviceNumber {
+ // STORAGE_DEVICE_NUMBER: winioctl.h
+ [MarshalAs(UnmanagedType.U4)] public uint DeviceType;
+ [MarshalAs(UnmanagedType.U4)] public uint DeviceNumber;
+ [MarshalAs(UnmanagedType.U4)] public uint PartitionNumber;
+ public static bool operator ==(StorageDeviceNumber c1, StorageDeviceNumber c2) {
+ return c1.Equals(c2);
+ }
+
+ public static bool operator !=(StorageDeviceNumber c1, StorageDeviceNumber c2) {
+ return !c1.Equals(c2);
+ }
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageLibTests/StorageTests.csproj b/dotnet/ComponentClassRegistry/StorageLibTests/StorageTests.csproj
new file mode 100644
index 0000000..7a2f633
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLibTests/StorageTests.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net8.0
+ enable
+ enable
+ NSA Cybersecurity Directorate
+ Apache-2.0
+
+ false
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/StorageLibTests/src/StorageTests.cs b/dotnet/ComponentClassRegistry/StorageLibTests/src/StorageTests.cs
new file mode 100644
index 0000000..30278b1
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageLibTests/src/StorageTests.cs
@@ -0,0 +1,236 @@
+using HardwareManifestProto;
+using PcieLib;
+using Storage;
+using StorageLib;
+using StorageNvme;
+using System.Text;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace StorageLibTests;
+public class StorageTests {
+ public static readonly string RegistryA21AtaComponentSample1Page3Base64 = "gAAAAAADAAGAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAFq83vESIzRFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ public static readonly string RegistryA21AtaComponentSample1Page5Base64 = "gAAAAAAFAAFTYW1wbGUgU2VyaWFsIDEgICAgIAAAAABGVyBWZXIgMQAAAAAAAAAAU2FtcGxlIE1vZGVsIE51bWJlciAxICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ public static readonly string RegistryA31ScsiComponentSample1InquiryDataBase64 = "fwAHAAAAAABFWEFNUExFIFNDU0kgTW9kZWwgMSAgICBTViBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSgFiMXZwXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ public static readonly string RegistryA31ScsiComponentSample1Page80Base64 = "f4AAG1NhbXBsZSBTQ1NJIFNlcmlhbCBOdW1iZXIgMQ==";
+ public static readonly string RegistryA32ScsiComponentSample2InquiryDataBase64 = "fwAHAAAAAABFWEFNUExFIFNDU0kgTW9kZWwgMiAgICBTViBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSgFiMXZwXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ public static readonly string RegistryA32ScsiComponentSample2Page80Base64 = "f4AAG1NhbXBsZSBTQ1NJIFNlcmlhbCBOdW1iZXIgMg==";
+ public static readonly string RegistryA32ScsiComponentSample2Page83Base64 = "f4MALgIBAB5FWEFNUExFIFNhbXBsZSBTZXJpYWwgTnVtYmVyIDMBAwAIMSNFZ4mrze8=";
+ public static readonly string RegistryA41NvmeComponentSample1IdentifyControllerBase64 = "zas0ElNOMSAgICAgICAgICAgICAgICAgTTIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZXMyAgICAgAO/NqwAAAAAABAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEiM0RVZneIq83vmaq7zN0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbnFuLjIwMTQtMDguY29tLmV4YW1wbGU6bnZtZTpudm0tc3Vic3lzdGVtLXNuLWQ3ODQzMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
+
+ /*
+ SEQUENCE (1 elem)
+ [0] (3 elem)
+ SEQUENCE (5 elem)
+ SEQUENCE (2 elem)
+ OBJECT IDENTIFIER 2.23.133.18.3.5
+ OCTET STRING (4 byte) 00000002
+ UTF8String ABCDEF
+ UTF8String Sample Model Number 1
+ [0] (25 byte) Sample Serial 1:112233445
+ [1] (8 byte) FW Ver 1
+ SEQUENCE (5 elem)
+ SEQUENCE (2 elem)
+ OBJECT IDENTIFIER 2.23.133.18.3.5
+ OCTET STRING (4 byte) 0100007F
+ UTF8String EXAMPLE
+ UTF8String SCSI Model 1
+ [0] (28 byte) :Sample SCSI Serial Number 1
+ [1] (4 byte) SV A
+ SEQUENCE (5 elem)
+ SEQUENCE (2 elem)
+ OBJECT IDENTIFIER 2.23.133.18.3.5
+ OCTET STRING (4 byte) 0100007F
+ UTF8String EXAMPLE
+ UTF8String SCSI Model 2
+ [0] (43 byte) 123456789ABCDEF:Sample SCSI Serial Number 2
+ [1] (4 byte) SV B
+ SEQUENCE (5 elem)
+ SEQUENCE (2 elem)
+ OBJECT IDENTIFIER 2.23.133.18.3.5
+ OCTET STRING (4 byte) 02010802
+ UTF8String ABCD:1234:ABCDEF
+ UTF8String M2
+ [0] (89 byte) 1122334455667788ABCDEF99AABBCCDD:SN1:nqn.2014-08.com.example:nvme:nvm-subsystem-sn-d78432
+ [1] (3 byte) FW3
+ */
+
+ public static readonly string ComponentClassValuePrefixAta = "00";
+ public static readonly string ComponentClassValuePrefixScsi = "01";
+ public static readonly string ComponentClassValuePrefixNvme = "02";
+
+
+ // Registry Appendix A.2 ATA Sample 1
+ public static readonly string RegistryA21AtaComponentSample1FormFactor = "02";
+ public static readonly string RegistryA21AtaComponentSample1Aoi = "ABCDEF";
+ public static readonly string RegistryA21AtaComponentSample1ModelNumber = "Sample Model Number 1";
+ public static readonly string RegistryA21AtaComponentSample1SerialNumber = "Sample Serial 1";
+ public static readonly string RegistryA21AtaComponentSample1UniqueId = "112233445";
+ public static readonly string RegistryA21AtaComponentSample1FirmwareRevision = "FW Ver 1";
+ // Registry Appendix A.2 ATA Sample 1 readout
+ public static readonly string[] RegistryA21AtaComponentSample1IdentifiersInJson = new string[] {
+ // Registry Appendix A.2 ATA Sample 1 Component 1
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.5\"," +
+ " \"COMPONENTCLASSVALUE\": \"" + ComponentClassValuePrefixAta + "0000" + RegistryA21AtaComponentSample1FormFactor +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA21AtaComponentSample1Aoi + "\"," +
+ " \"MODEL\": \"" + RegistryA21AtaComponentSample1ModelNumber + "\"," +
+ " \"SERIAL\": \"" + RegistryA21AtaComponentSample1SerialNumber + ":" + RegistryA21AtaComponentSample1UniqueId + "\"," +
+ " \"REVISION\": \"" + RegistryA21AtaComponentSample1FirmwareRevision +
+ "\" }"
+ };
+
+ // Registry Appendix A.2 SCSI Sample 1
+ public static readonly string RegistryA31ScsiComponentSample1InquiryClass = "7F";
+ public static readonly string RegistryA31ScsiComponentSample1T10VendorIdentification = "EXAMPLE";
+ public static readonly string RegistryA31ScsiComponentSample1ProductIdentification = "SCSI Model 1";
+ public static readonly string RegistryA31ScsiComponentSample1VpdUniqueId = "";
+ public static readonly string RegistryA31ScsiComponentSample1VpdSn = "Sample SCSI Serial Number 1";
+ public static readonly string RegistryA31ScsiComponentSample1RevisionLevel = "SV A";
+ // Registry Appendix A.2 SCSI Sample 1 readout
+ public static readonly string[] RegistryA32ScsiComponentSample1IdentifiersInJson = new string[] {
+ // Registry Appendix A.2 SCSI Sample 1 Component 1
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.5\"," +
+ " \"COMPONENTCLASSVALUE\": \"" + ComponentClassValuePrefixScsi + "0000" + RegistryA31ScsiComponentSample1InquiryClass +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA31ScsiComponentSample1T10VendorIdentification + "\"," +
+ " \"MODEL\": \"" + RegistryA31ScsiComponentSample1ProductIdentification + "\"," +
+ " \"SERIAL\": \"" + RegistryA31ScsiComponentSample1VpdUniqueId + ":" + RegistryA31ScsiComponentSample1VpdSn + "\"," +
+ " \"REVISION\": \"" + RegistryA31ScsiComponentSample1RevisionLevel +
+ "\" }"
+ };
+
+ // Registry Appendix A.2 SCSI Sample 2
+ public static readonly string RegistryA32ScsiComponentSample2InquiryClass = "7F";
+ public static readonly string RegistryA32ScsiComponentSample2T10VendorIdentification = "EXAMPLE";
+ public static readonly string RegistryA32ScsiComponentSample2ProductIdentification = "SCSI Model 2";
+ public static readonly string RegistryA32ScsiComponentSample2VpdUniqueId = "123456789ABCDEF";
+ public static readonly string RegistryA32ScsiComponentSample2VpdSn = "Sample SCSI Serial Number 2";
+ public static readonly string RegistryA32ScsiComponentSample2RevisionLevel = "SV B";
+ // Registry Appendix A.2 SCSI Sample 2 readout
+ public static readonly string[] RegistryA32ScsiComponentSample2IdentifiersInJson = new string[] {
+ // Registry Appendix A.2 SCSI Sample 2 Component 1
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.5\"," +
+ " \"COMPONENTCLASSVALUE\": \"" + ComponentClassValuePrefixScsi + "0000" + RegistryA32ScsiComponentSample2InquiryClass +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA32ScsiComponentSample2T10VendorIdentification + "\"," +
+ " \"MODEL\": \"" + RegistryA32ScsiComponentSample2ProductIdentification + "\"," +
+ " \"SERIAL\": \"" + RegistryA32ScsiComponentSample2VpdUniqueId + ":" + RegistryA32ScsiComponentSample2VpdSn + "\"," +
+ " \"REVISION\": \"" + RegistryA32ScsiComponentSample2RevisionLevel +
+ "\" }"
+ };
+
+ // Registry Appendix A.2 NVMe Sample 1
+ public static readonly string RegistryA41NvmeComponentSample1PciClassCode = "010802";
+ public static readonly string RegistryA41NvmeComponentSample1Vid = "ABCD";
+ public static readonly string RegistryA41NvmeComponentSample1Ssvid = "1234";
+ public static readonly string RegistryA41NvmeComponentSample1Ieee = "ABCDEF";
+ public static readonly string RegistryA41NvmeComponentSample1Mn = "M2";
+ public static readonly string RegistryA41NvmeComponentSample1FGuid = "1122334455667788ABCDEF99AABBCCDD";
+ public static readonly string RegistryA41NvmeComponentSample1Sn = "SN1";
+ public static readonly string RegistryA41NvmeComponentSample1Subnqn = "nqn.2014-08.com.example:nvme:nvm-subsystem-sn-d78432";
+ public static readonly string RegistryA41NvmeComponentSample1Ver = "00010400";
+ public static readonly string RegistryA41NvmeComponentSample1Fr = "FW3";
+ // Registry Appendix A.2 NVMe Sample 1 readout
+ public static readonly string[] RegistryA41NvmeComponentSample1IdentifiersInJson = new string[] {
+ // Registry Appendix A.2 NVMe Sample 1 Component 1
+ "{" +
+ " \"COMPONENTCLASS\": {" +
+ " \"COMPONENTCLASSREGISTRY\": \"2.23.133.18.3.5\"," +
+ " \"COMPONENTCLASSVALUE\": \"" + ComponentClassValuePrefixNvme + RegistryA41NvmeComponentSample1PciClassCode +
+ "\" }," +
+ " \"MANUFACTURER\": \"" + RegistryA41NvmeComponentSample1Vid + ":" + RegistryA41NvmeComponentSample1Ssvid + ":" + RegistryA41NvmeComponentSample1Ieee + "\"," +
+ " \"MODEL\": \"" + RegistryA41NvmeComponentSample1Mn + "\"," +
+ " \"SERIAL\": \"" + RegistryA41NvmeComponentSample1FGuid + ":" + RegistryA41NvmeComponentSample1Sn + ":" + RegistryA41NvmeComponentSample1Subnqn + "\"," +
+ " \"REVISION\": \"" + RegistryA41NvmeComponentSample1Ver + ":" + RegistryA41NvmeComponentSample1Fr +
+ "\" }"
+ };
+
+
+ [Test]
+ public void TestRegistryA21Sample1() {
+ byte[] page3 = Convert.FromBase64String(RegistryA21AtaComponentSample1Page3Base64);
+ byte[] page5 = Convert.FromBase64String(RegistryA21AtaComponentSample1Page5Base64);
+
+ Assert.Fail("ATA Not yet complete");
+ }
+
+ [Test]
+ public void TestRegistryA31Sample1() {
+ byte[] inquiryData = Convert.FromBase64String(RegistryA31ScsiComponentSample1InquiryDataBase64);
+ byte[] page80 = Convert.FromBase64String(RegistryA31ScsiComponentSample1Page80Base64);
+
+ Assert.Fail("SCSI Not yet complete");
+ }
+
+ [Test]
+ public void TestRegistryA32Sample2() {
+ byte[] inquiryData = Convert.FromBase64String(RegistryA32ScsiComponentSample2InquiryDataBase64);
+ byte[] page80 = Convert.FromBase64String(RegistryA32ScsiComponentSample2Page80Base64);
+ byte[] page83 = Convert.FromBase64String(RegistryA32ScsiComponentSample2Page83Base64);
+
+ Assert.Fail("SCSI Not yet complete");
+ }
+
+ [Test]
+ public void TestRegistryA41Sample2() {
+ byte[] idCtrlData = Convert.FromBase64String(RegistryA41NvmeComponentSample1IdentifyControllerBase64);
+ StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl = StorageCommonHelpers.CreateStruct(idCtrlData);
+ byte[] classCodeData = Convert.FromHexString(RegistryA41NvmeComponentSample1PciClassCode);
+ ClassCode classCode = new(classCodeData, false);
+ List nvmeData = [new StorageNvmeData(nvmeCtrl, classCode)];
+
+ ManifestV2 manifestV2 = new();
+ StorageHardwareManifestPlugin.AddComponentsToManifestV2(nvmeData, [], [], manifestV2);
+
+ string jsonManifestV2 = manifestV2.ToString();
+
+ Assert.That(RegistryA41NvmeComponentSample1IdentifiersInJson, Has.Length.GreaterThan(0));
+
+ foreach (string componentJson in RegistryA41NvmeComponentSample1IdentifiersInJson) {
+ Assert.That(jsonManifestV2, Contains.Substring(componentJson));
+ }
+ }
+
+ [Test]
+ public void TestNvmeVal() {
+ byte[] idCtrlData = Convert.FromBase64String(RegistryA41NvmeComponentSample1IdentifyControllerBase64);
+
+ StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl = StorageCommonHelpers.CreateStruct(idCtrlData);
+
+ Assert.Multiple(() => {
+ Assert.That(StorageHardwareManifestPlugin.NVMe_Val(nvmeCtrl.VID, true), Is.EqualTo(RegistryA41NvmeComponentSample1Vid));
+ Assert.That(StorageHardwareManifestPlugin.NVMe_Val(nvmeCtrl.SSVID, true), Is.EqualTo(RegistryA41NvmeComponentSample1Ssvid));
+ Assert.That(StorageHardwareManifestPlugin.NVMe_Val(nvmeCtrl.FGUID, false), Is.EqualTo(RegistryA41NvmeComponentSample1FGuid));
+ Assert.That(StorageHardwareManifestPlugin.NVMe_Val(nvmeCtrl.VER, true), Is.EqualTo(RegistryA41NvmeComponentSample1Ver));
+ });
+ }
+
+ [Test]
+ public void TestNvmeOui() {
+ byte[] idCtrlData = Convert.FromBase64String(RegistryA41NvmeComponentSample1IdentifyControllerBase64);
+
+ StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl = StorageCommonHelpers.CreateStruct(idCtrlData);
+ Assert.That(StorageHardwareManifestPlugin.NVMe_OUI(nvmeCtrl.IEEE), Is.EqualTo(RegistryA41NvmeComponentSample1Ieee));
+
+ }
+
+ [Test]
+ public void TestNvmeString() {
+ byte[] idCtrlData = Convert.FromBase64String(RegistryA41NvmeComponentSample1IdentifyControllerBase64);
+
+ StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl = StorageCommonHelpers.CreateStruct(idCtrlData);
+
+ Assert.Multiple(() => {
+ Assert.That(StorageHardwareManifestPlugin.NVMe_String(nvmeCtrl.MN), Is.EqualTo(RegistryA41NvmeComponentSample1Mn));
+ Assert.That(StorageHardwareManifestPlugin.NVMe_String(nvmeCtrl.SN), Is.EqualTo(RegistryA41NvmeComponentSample1Sn));
+ Assert.That(StorageHardwareManifestPlugin.NVMe_String(nvmeCtrl.SUBNQN), Is.EqualTo(RegistryA41NvmeComponentSample1Subnqn));
+ Assert.That(StorageHardwareManifestPlugin.NVMe_String(nvmeCtrl.FR), Is.EqualTo(RegistryA41NvmeComponentSample1Fr));
+ });
+ }
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/StorageNvme.csproj b/dotnet/ComponentClassRegistry/StorageNvme/StorageNvme.csproj
new file mode 100644
index 0000000..e427bec
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/StorageNvme.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/IStorageNvme.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/IStorageNvme.cs
new file mode 100644
index 0000000..919f182
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/IStorageNvme.cs
@@ -0,0 +1,4 @@
+namespace StorageNvme;
+public interface IStorageNvme {
+ bool CollectNvmeData(out List list);
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeConstants.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeConstants.cs
new file mode 100644
index 0000000..4699520
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeConstants.cs
@@ -0,0 +1,19 @@
+namespace StorageNvme;
+public class StorageNvmeConstants {
+ public static readonly uint NVME_IDENTIFY_DATA_BUFFER_SIZE = 4096;
+
+ // NVMe Constants
+ // NVM Express® Base Specification, Revision 2.1, 5.1.13.1 Figure 310: Identify – CNS Values
+ public enum NvmeCnsValue : uint {
+ IDENTIFY_NAMESPACE_FOR_NSID = 0x0, // Identify Namespace data structure for the specified NSID
+ IDENTIFY_CONTROLLER = 0x01, // Identify Controller data structure for the controller processing the command
+ IDENTIFY_ACTIVE_NAMESPACE_LIST = 0x02, // Active Namespace ID list
+ IDENTIFY_NSID_DESCRIPTOR_FOR_NSID = 0x03 // Namespace Identification Descriptor list for the specified NSID.
+ }
+
+ // NVM Express® Base Specification, Revision 2.1, 3.1.3.4 Command Support Requirements
+ public enum NvmeAdminCommandOpcode : byte {
+ GET_LOG_PAGE = 0x02,
+ IDENTIFY = 0x06
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeData.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeData.cs
new file mode 100644
index 0000000..c6648fa
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeData.cs
@@ -0,0 +1,12 @@
+using PcieLib;
+
+namespace StorageNvme;
+public class StorageNvmeData(StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl, ClassCode classCode) {
+ public StorageNvmeStructs.NvmeIdentifyControllerData NvmeCtrl {
+ get;
+ } = nvmeCtrl;
+
+ public ClassCode ClassCode {
+ get;
+ } = classCode;
+}
\ No newline at end of file
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeHelpers.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeHelpers.cs
new file mode 100644
index 0000000..7b2a9a8
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeHelpers.cs
@@ -0,0 +1,25 @@
+using System.Runtime.InteropServices;
+
+namespace StorageNvme;
+public class StorageNvmeHelpers {
+ public static bool CollectNvmeData(out List list) {
+ list = new();
+ bool result = false;
+
+ IStorageNvme? nvme = null;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+ nvme = new Linux.StorageNvmeLinux();
+ } else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ nvme = new Windows.StorageNvmeWin();
+ }
+
+ if (nvme == null) {
+ return false;
+ }
+
+ result = nvme.CollectNvmeData(out list);
+
+ return result;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinux.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinux.cs
new file mode 100644
index 0000000..88fd454
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinux.cs
@@ -0,0 +1,104 @@
+using Microsoft.Win32.SafeHandles;
+using PcieLib;
+using StorageLib;
+using StorageLib.Linux;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace StorageNvme.Linux;
+public class StorageNvmeLinux : IStorageNvme {
+ public bool CollectNvmeData(out List list) {
+ list = new();
+ bool noProblems = true;
+ Regex regex = new Regex(@"^(/dev/nvme[0-9A-Fa-f]+n[0-9A-Fa-f]+)p.*$");
+ string[] matches = Directory.EnumerateFileSystemEntries(@"/dev/").Where(f => regex.IsMatch(f)).Select(s => s.Split('p')[0]).Distinct().ToArray();
+
+ foreach (string devName in matches) {
+ bool nvmeCtrlRead = QueryNvmeCns(out StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl, devName);
+ bool pciRead = FindPciHeaderForNvmeDevName(out byte[] config, out byte[] vpd, devName);
+ if (nvmeCtrlRead && pciRead) {
+ PcieDevice pciDev = new(config, vpd);
+ StorageNvmeData nvmeData = new(nvmeCtrl, pciDev.ClassCode);
+ list.Add(nvmeData);
+ }
+ noProblems = noProblems && nvmeCtrlRead && pciRead;
+ }
+ return noProblems;
+ }
+
+ public static bool FindPciHeaderForNvmeDevName(out byte[] config, out byte[] vpd, string devName) {
+ bool result = false;
+ config = Array.Empty();
+ vpd = Array.Empty();
+
+ // devName ex /dev/nvme0n1
+ Regex findRegex = new Regex(@"^/dev/(nvme)([0-9A-Fa-f]+)n([0-9A-Fa-f]+)");
+ Match match = findRegex.Match(devName);
+ string findPath = "/" + match.Groups[1].Value + "/" + match.Groups[1].Value + match.Groups[2].Value;
+ string findName = match.Groups[1].Value + match.Groups[2].Value + "n" + match.Groups[3].Value;
+ //Directory.
+ // find /sys/bus/pci/devices/*/nvme/nvme0 -type d -name nvme0n1
+ // /sys/bus/pci/devices/0000:3b:00.0/nvme/nvme0/nvme0n1
+ Regex sysRegex = new Regex(@"^/sys/bus/pci/devices/[0-9A-Fa-f:.]+$");
+ string[] pciDevices = Directory.GetDirectories("/sys/bus/pci/devices");
+ foreach (string pciDevice in pciDevices) {
+ if (Directory.Exists(pciDevice + findPath + "/" + findName)) {
+ string pciDeviceConfigFile = pciDevice + "/config";
+ string pciDeviceVpdFile = pciDevice + "/vpd";
+ if (File.Exists(pciDeviceVpdFile)) {
+ vpd = File.ReadAllBytes(pciDeviceVpdFile);
+ }
+ if (File.Exists(pciDeviceConfigFile)) {
+ config = File.ReadAllBytes(pciDeviceConfigFile);
+ result = true;
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+
+
+
+ public static bool QueryNvmeCns(out StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl, string devName) {
+ StorageNvmeLinuxStructs.NvmePassthruCmd passthru = StorageCommonHelpers.CreateStruct();
+ nvmeCtrl = new();
+ // open handle on nvme device
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(devName);
+
+ IntPtr ptr = IntPtr.Zero;
+ bool endResult = false;
+ int allocationSize = Marshal.SizeOf();
+
+ passthru.adminCmd.opcode = (byte)StorageNvmeConstants.NvmeAdminCommandOpcode.IDENTIFY;
+ passthru.adminCmd.dataLength = 4096;
+ passthru.adminCmd.cdw10 = (uint)StorageNvmeConstants.NvmeCnsValue.IDENTIFY_CONTROLLER;
+ passthru.timeout = 15000;
+
+ try {
+ // Allocate memory for the buffer
+ ptr = Marshal.AllocHGlobal(allocationSize);
+
+ // Needed the starting address of the buffer for the next command to calculate the offset
+ passthru.adminCmd.addr = (ulong)IntPtr.Add(ptr, Marshal.OffsetOf("data").ToInt32());
+
+ // Copy the data from the managed object to the buffer
+ Marshal.StructureToPtr(passthru, ptr, true);
+
+ int result = StorageLinuxImports.ioctl(handle, StorageNvmeLinuxConstants.NVME_IOCTL_ADMIN_CMD, ptr);
+
+ if (result < 0) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ nvmeCtrl = Marshal.PtrToStructure(IntPtr.Add(ptr, Marshal.OffsetOf("data").ToInt32()));
+ endResult = true;
+ }
+ } finally {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return endResult;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinuxConstants.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinuxConstants.cs
new file mode 100644
index 0000000..6275e4d
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinuxConstants.cs
@@ -0,0 +1,6 @@
+namespace StorageNvme.Linux;
+public class StorageNvmeLinuxConstants {
+ // Linux nvme_ioctl.h
+ internal static readonly uint NVME_IOCTL_ID = 0xC0484E40;
+ internal static readonly uint NVME_IOCTL_ADMIN_CMD = 0xC0484E41;
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinuxStructs.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinuxStructs.cs
new file mode 100644
index 0000000..f2cade1
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeLinux/StorageNvmeLinuxStructs.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace StorageNvme.Linux;
+public class StorageNvmeLinuxStructs {
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NvmePassthruCmd {
+ public StorageNvmeStructs.NvmeAdminCommand adminCmd;
+ [MarshalAs(UnmanagedType.U4)] public uint timeout;
+ [MarshalAs(UnmanagedType.U4)] public uint result;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)]
+ public byte[] data;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeStructs.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeStructs.cs
new file mode 100644
index 0000000..40155e0
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeStructs.cs
@@ -0,0 +1,487 @@
+using StorageLib;
+using System.Runtime.InteropServices;
+
+namespace StorageNvme;
+public class StorageNvmeStructs {
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NvmeIdentifyControllerData {
+ // Identify Controller Data Structure : 4096 bytes
+ // NVM Express® Base Specification, Revision 2.1, 5.1.13.2.1 Figure 312: Identify – Identify Controller Data Structure
+ // Elements not relevant to the Storage CCR are marked internal and labeled outside scope
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ public byte[] VID; // byte 0:1 PCI Vendor ID (VID), little-endian
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ public byte[] SSVID; // byte 2:3 PCI Subsystem Vendor ID (SSVID), little-endian
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
+ public byte[] SN; // byte 4:23 Serial Number (SN), string
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
+ public byte[] MN; // byte 24:63 Model Number (MN), string
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public byte[] FR; // byte 64:71 Firmware Revision (FR), string
+
+ internal byte RAB; // byte 72 Recommended Arbitration Burst (RAB) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
+ public byte[] IEEE; // byte 73:75 IEEE OUI Identifier (IEEE). Controller Vendor code, big endian
+
+ internal byte CMIC; // byte 76 Controller Multi-Path I/O and Namespace Sharing Capabilities (CMIC) outside scope
+
+ internal byte MDTS; // byte 77 Maximum Data Transfer Size (MDTS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CNTLID; // byte 78:79 Controller ID (CNTLID) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ public byte[] VER; // byte 80:83 Version (VER), little endian
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] RTD3R; // byte 84:87 RTD3 Resume Latency (RTD3R) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] RTD3E; // byte 88:91 RTD3 Entry Latency (RTD3E) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] OAES; // byte 92:95 Optional Asynchronous Events Supported (OAES) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] CTRATT; // byte 96:99 Controller Attributes (CTRATT) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] RRLS; // byte 100:101 Read Recovery Levels Supported (RRLS) outside scope
+
+ internal byte BPCAP; // byte 102 Boot Partition Capabilities (BPCAP) outside scope
+
+ internal byte Reserved103; // byte 103 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] NSSL; // byte 104:107 NVM Subsystem Shutdown Latency (NSSL) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] Reserved108; // byte 108:109 Reserved outside scope
+
+ internal byte PLSI; // byte 110 Power Loss Signaling Information (PLSI) outside scope
+
+ internal byte CNTRLTYPE; // byte 111 Controller Type (CNTRLTYPE) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+ public byte[] FGUID; // byte 112:127 FRU Globally Unique Identifier (FGUID)
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CRDT1; // byte 128:129 Command Retry Delay Time 1 (CRDT1) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CRDT2; // byte 130:131 Command Retry Delay Time 2 (CRDT2) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CRDT3; // byte 132:133 Command Retry Delay Time 3 (CRDT3) outside scope
+
+ internal byte CRCAP; // byte 134 Controller Reachability Capabilities (CRCAP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 105)]
+ internal byte[] Reserved135; // byte 135:239 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)]
+ internal byte[] Reserved240; // byte 240:252 Reserved for the NVMe Management Interface
+
+ internal byte NVMSR; // byte 253 NVM Subsystem Report (NVMSR) outside scope outside scope
+
+ internal byte VWCI; // byte 254 VPD Write Cycle Information (VWCI) outside scope
+
+ internal byte MEC; // byte 255 Management Endpoint Capabilities (MEC) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] OACS; // byte 256:257 Optional Admin Command Support (OACS) outside scope
+
+ internal byte ACL; // byte 258 Abort Command Limit (ACL) outside scope
+
+ internal byte AERL; // byte 259 Asynchronous Event Request Limit (AERL) outside scope
+
+ internal byte FRMW; // byte 260 Firmware Updates (FRMW) outside scope
+
+ internal byte LPA; // byte 261 Log Page Attributes (LPA) outside scope
+
+ internal byte ELPE; // byte 262 Error Log Page Entries (ELPE) outside scope
+
+ internal byte NPSS; // byte 263 Number of Power States Support (NPSS) outside scope
+
+ internal byte AVSCC; // byte 264 Admin Vendor Specific Command Configuration (AVSCC) outside scope
+
+ internal byte APSTA; // byte 265 Autonomous Power State Transition Attributes (APSTA) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] WCTEMP; // byte 266:267 Warning Composite Temperature Threshold (WCTEMP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CCTEMP; // byte 268:269 Critical Composite Temperature Threshold (CCTEMP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MTFA; // byte 270:271 Maximum Time for Firmware Activation (MTFA) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] HMPRE; // byte 272:275 Host Memory Buffer Preferred Size (HMPRE) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] HMMIN; // byte 276:279 Host Memory Buffer Minimum Size (HMMIN) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+ internal byte[] TNVMCAP; // byte 280:295 Total NVM Capacity (TNVMCAP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+ internal byte[] UNVMCAP; // byte 296:311 Unallocated NVM Capacity (UNVMCAP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] RPMBS; // byte 312:315 Replay Protected Memory Block Support (RPMBS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] EDSTT; // byte 316:317 Extended Device Self-test Time (EDSTT) outside scope
+
+ internal byte DSTO; // byte 318 Device Self-test Options (DSTO) outside scope
+
+ internal byte FWUG; // byte 319 Firmware Update Granularity (FWUG) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] KAS; // byte 320:321 Keep Alive Support (KAS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] HCTMA; // byte 322:323 Host Controlled Thermal Management Attributes (HCTMA) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MNTMT; // byte 324:325 Minimum Thermal Management Temperature (MNTMT) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MXTMT; // byte 326:327 Maximum Thermal Management Temperature (MXTMT) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] SANICAP; // byte 328:331 Sanitize Capabilities (SANICAP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] HMMINDS; // byte 332:335 Host Memory Buffer Minimum Descriptor Entry Size (HMMINDS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] HMMAXD; // byte 336:337 Host Memory Maximum Descriptors Entries (HMMAXD) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] NSETIDMAX; // byte 338:339 NVM Set Identifier Maximum (NSETIDMAX) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] ENDGIDMAX; // byte 340:341 Endurance Group Identifier Maximum (ENDGIDMAX) outside scope
+
+ internal byte ANATT; // byte 342 ANA Transition Time (ANATT) outside scope
+
+ internal byte ANACAP; // byte 343 Asymmetric Namespace Access Capabilities (ANACAP) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] ANAGRPMAX; // byte 344:347 ANA Group Identifier Maximum (ANAGRPMAX) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] NANAGRPID; // byte 348:351 Number of ANA Group Identifiers (NANAGRPID) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] PELS; // byte 352:355 Persistent Event Log Size (PELS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] DID; // byte 356:357 Domain Identifier (DID) outside scope
+
+ internal byte KPIOC; // byte 358 Key Per I/O Capabilities (KPIOC) outside scope
+
+ internal byte Reserved359; // byte 359 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MPTFAWR; // byte 360:361 Maximum Processing Time for Firmware Activation Without Reset (MPTFAWR) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
+ internal byte[] Reserved362; // byte 362:367 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+ internal byte[] MEGCAP; // byte 368:383 Max Endurance Group Capacity (MEGCAP) outside scope
+
+ internal byte TMPTHHA; // byte 384 Temperature Threshold Hysteresis Attributes (TMPTHHA) outside scope
+
+ internal byte Reserved385; // byte 385 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CQT; // byte 386:387 Command Quiesce Time (CQT) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 124)]
+ internal byte[] Reserved388; // byte 388:511 Reserved outside scope
+
+ internal byte SQES; // byte 512 Submission Queue Entry Size (SQES) outside scope
+
+ internal byte CQES; // byte 513 Completion Queue Entry Size (CQES) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MAXCMD; // byte 514:515 Maximum Outstanding Commands (MAXCMD) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] NN; // byte 516:519 Number of Namespaces (NN) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] ONCS; // byte 520:521 Optional NVM Command Support (ONCS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] FUSES; // byte 522:523 Fused Operation Support (FUSES) outside scope
+
+ internal byte FNA; // byte 524 Format NVM Attributes (FNA) outside scope
+
+ internal byte VWC; // byte 525 Volatile Write Cache (VWC) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] AWUN; // byte 526:527 Atomic Write Unit Normal (AWUN) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] AWUPF; // byte 528:529 Atomic Write Unit Power Fail (AWUPF) outside scope
+
+ internal byte ICSVSCC; // byte 530 I/O Command Set Vendor Specific Command Configuration (ICSVSCC) outside scope
+
+ internal byte NWPC; // byte 531 Namespace Write Protection Capabilities (NWPC) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] ACWU; // byte 532:533 Atomic Compare & Write Unit (ACWU) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CDFS; // byte 534:535 Copy Descriptor Formats Supported (CDFS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] SGLS; // byte 536:539 SGL Support (SGLS) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] MNAN; // byte 540:543 Maximum Number of Allowed Namespaces (MNAN) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+ internal byte[] MAXDNA; // byte 544:559 Maximum Domain Namespace Attachments (MAXDNA) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] MAXCNA; // byte 560:563 Maximum I/O Controller Namespace Attachments (MAXCNA) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] OAQD; // byte 564:567 Optimal Aggregated Queue Depth (OAQD) outside scope
+
+ internal byte RHIRI; // byte 568 Recommended Host-Initiated Refresh Interval (RHIRI) outside scope
+
+ internal byte HIRT; // byte 569 Host-Initiated Refresh Time (HIRT) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] CMMRTD; // byte 570:571 Controller Maximum Memory Range Tracking Descriptors (CMMRTD) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] NMMRTD; // byte 572:573 NVM Subsystem Maximum Memory Range Tracking Descriptors (NMMRTD) outside scope
+
+ internal byte MINMRTG; // byte 574 Minimum Memory Range Tracking Granularity (MINMRTG) outside scope
+
+ internal byte MAXMRTG; // byte 575 Maximum Memory Range Tracking Granularity (MAXMRTG) outside scope
+
+ internal byte TRATTR; // byte 576 Tracking Attributes (TRATTR) outside scope
+
+ internal byte Reserved577; // byte 577 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MCUDMQ; // byte 578:579 Maximum Controller User Data Migration Queues (MCUDMQ) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MNSUDMQ; // byte 580:581 Maximum NVM Subsystem User Data Migration Queues (MNSUDMQ) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MCMR; // byte 582:583 Maximum CDQ Memory Ranges (MCMR) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] NMCMR; // byte 584:585 NVM Subsystem Maximum CDQ Memory Ranges (NMCMR) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] MCDQPC; // byte 586:587 Maximum Controller Data Queue PRP Count (MCDQPC) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 180)]
+ internal byte[] Reserved588; // byte 588:767 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
+ public byte[] SUBNQN; // byte 768:1023 NVM Subsystem NVMe Qualified Name (SUBNQN)
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 768)]
+ internal byte[] Reserved1024; // byte 1024:1791 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] IOCCSZ; // byte 1792:1795 I/O Queue Command Capsule Supported Size (IOCCSZ) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal byte[] IORCSZ; // byte 1796:1799 I/O Queue Response Capsule Supported Size (IORCSZ) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] ICDOFF; // byte 1800:1801 In Capsule Data Offset (ICDOFF) outside scope
+
+ internal byte FCATT; // byte 1802 Fabrics Controller Attributes (FCATT) outside scope
+
+ internal byte MSDBD; // byte 1803 Maximum SGL Data Block Descriptors (MSDBD) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ internal byte[] OFCS; // byte 1804:1805 Optional Fabrics Commands Support (OFCS) outside scope
+
+ internal byte DCTYPE; // byte 1806 Discovery Controller Type (DCTYPE) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 241)]
+ internal byte[] Reserved1807; // byte 1807:2047 Reserved outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD0; // byte 2048:2079 Power State 0 Descriptor (PSD0) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD1; // byte 2080:2111 Power State 1 Descriptor (PSD1) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD2; // byte 2112:2143 Power State 2 Descriptor (PSD2) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD3; // byte 2144:2175 Power State 3 Descriptor (PSD3) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD4; // byte 2176:2207 Power State 4 Descriptor (PSD4) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD5; // byte 2208:2239 Power State 5 Descriptor (PSD5) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD6; // byte 2240:2271 Power State 6 Descriptor (PSD6) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD7; // byte 2272:2303 Power State 7 Descriptor (PSD7) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD8; // byte 2304:2335 Power State 8 Descriptor (PSD8) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD9; // byte 2336:2367 Power State 9 Descriptor (PSD9) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD10; // byte 2368:2399 Power State 10 Descriptor (PSD10) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD11; // byte 2400:2431 Power State 11 Descriptor (PSD11) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD12; // byte 2432:2463 Power State 12 Descriptor (PSD12) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD13; // byte 2464:2495 Power State 13 Descriptor (PSD13) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD14; // byte 2496:2527 Power State 14 Descriptor (PSD14) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD15; // byte 2528:2559 Power State 15 Descriptor (PSD15) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD16; // byte 2560:2591 Power State 16 Descriptor (PSD16) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD17; // byte 2592:2623 Power State 17 Descriptor (PSD17) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD18; // byte 2624:2655 Power State 18 Descriptor (PSD18) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD19; // byte 2656:2687 Power State 19 Descriptor (PSD19) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD20; // byte 2688:2719 Power State 20 Descriptor (PSD20) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD21; // byte 2720:2751 Power State 21 Descriptor (PSD21) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD22; // byte 2752:2783 Power State 22 Descriptor (PSD22) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD23; // byte 2784:2815 Power State 23 Descriptor (PSD23) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD24; // byte 2816:2847 Power State 24 Descriptor (PSD24) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD25; // byte 2848:2879 Power State 25 Descriptor (PSD25) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD26; // byte 2880:2911 Power State 26 Descriptor (PSD26) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD27; // byte 2912:2943 Power State 27 Descriptor (PSD27) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD28; // byte 2944:2975 Power State 28 Descriptor (PSD28) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD29; // byte 2976:3007 Power State 29 Descriptor (PSD29) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD30; // byte 3008:3039 Power State 30 Descriptor (PSD30) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+ internal byte[] PSD31; // byte 3040:3071 Power State 31 Descriptor (PSD31) outside scope
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]
+ internal byte[] VS; // byte 3072:4095 Vendor Specific (VS) outside scope
+
+
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NvmeCommandDwords {
+ [MarshalAs(UnmanagedType.U4)] public uint DWord0;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord1;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord2;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord3;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord4;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord5;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord6;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord7;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord8;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord9;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord10;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord11;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord12;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord13;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord14;
+ [MarshalAs(UnmanagedType.U4)] public uint DWord15;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NvmeAdminCommand {
+ public byte opcode;
+ public byte flags;
+ [MarshalAs(UnmanagedType.U2)] public ushort rsvd1;
+ [MarshalAs(UnmanagedType.U4)] public uint nsid;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw2;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw3;
+ [MarshalAs(UnmanagedType.U8)] public ulong metadata;
+ [MarshalAs(UnmanagedType.U8)] public ulong addr;
+ [MarshalAs(UnmanagedType.U4)] public uint metadataLength;
+ [MarshalAs(UnmanagedType.U4)] public uint dataLength;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw10;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw11;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw12;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw13;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw14;
+ [MarshalAs(UnmanagedType.U4)] public uint cdw15;
+ }
+
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct NvmeCommand {
+ // Both options here must be the same size (16 x 4 bytes = 64 bytes)
+ // Technically ADMIN_COMMAND only makes sense if NVME_PASS_THROUGH_PARAMETERS.IsIOCommandSet is false
+ [FieldOffset(0)] public NvmeCommandDwords Generic;
+ [FieldOffset(0)] public NvmeAdminCommand Admin;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CompletionQueueEntry {
+ // COMPLETION_QUEUE_ENTRY
+ [MarshalAs(UnmanagedType.U4)] public uint dw0;
+ [MarshalAs(UnmanagedType.U4)] public uint dw1;
+ [MarshalAs(UnmanagedType.U4)] public uint dw2;
+ [MarshalAs(UnmanagedType.U4)] public uint dw3;
+ }
+
+
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWin.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWin.cs
new file mode 100644
index 0000000..684090f
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWin.cs
@@ -0,0 +1,228 @@
+using Microsoft.Win32.SafeHandles;
+using PcieLib;
+using PcieWinCfgMgr;
+using StorageLib;
+using StorageLib.Windows;
+using StorageNvme;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace StorageNvme.Windows;
+public class StorageNvmeWin : IStorageNvme {
+ public bool CollectNvmeData(out List list) {
+ list = new();
+ bool noProblems = true;
+
+ int numPhysicalDisks = GetNumPhysicalDisks();
+ for (int i = 0; i < numPhysicalDisks; i++) {
+ string pdHandle = string.Format(StorageWinConstants.DISK_HANDLE_PD, i);
+
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(pdHandle);
+
+ if (!StorageCommonHelpers.IsDeviceHandleReady(handle)) {
+ continue;
+ }
+
+ bool adapterDescriptorSuccess = StorageWin.QueryStorageAdapterProperty(out StorageWinStructs.StorageAdapterDescriptor adapterDescriptor, handle);
+
+ if (!adapterDescriptorSuccess) {
+ continue;
+ }
+
+ bool deviceDescriptorSuccess = StorageWin.QueryStorageDeviceProperty(out StorageWinStructs.StorageDeviceDescriptor deviceDescriptor, handle);
+
+ if (!deviceDescriptorSuccess) {
+ continue;
+ }
+
+ StorageNvmeStructs.NvmeIdentifyControllerData nvmeCtrl = new();
+ bool nvmeCtrlRead = false;
+
+ if (deviceDescriptor.BusType == StorageWinConstants.StorageBusType.BusTypeNvme) {
+
+ if (adapterDescriptor.BusType == StorageWinConstants.StorageBusType.BusTypeRAID) {
+ bool scsiAddressSuccess = StorageWin.GetScsiAddress(out StorageWinStructs.ScsiAddress scsiAddress, handle);
+
+ if (!scsiAddressSuccess) {
+ continue;
+ }
+
+ // Needed to collect identify namespace data
+ int nsid = scsiAddress.Lun + 1;
+
+ // Try to use Intel Passthrough
+ bool nvmePassthroughSuccess = QueryNvmeCnsThruIntelRstDriver(out StorageNvmeWinStructs.IntelNvmeIoctlPassthrough passThrough, i, scsiAddress, StorageNvmeConstants.NvmeCnsValue.IDENTIFY_CONTROLLER, 0);
+
+ if (nvmePassthroughSuccess) {
+ nvmeCtrl = StorageCommonHelpers.CreateStruct(passThrough.data);
+ nvmeCtrlRead = true;
+ }
+ } else {
+ // Standard Query
+ }
+ }
+
+ if (!nvmeCtrlRead) {
+ continue;
+ }
+
+ bool pciRead = GetPciConfigFromCfgMgr(out byte[] config, i);
+
+ if (nvmeCtrlRead && pciRead) {
+ PcieDevice pciDev = new(config, Array.Empty());
+ StorageNvmeData nvmeData = new(nvmeCtrl, pciDev.ClassCode);
+ list.Add(nvmeData);
+ }
+ noProblems = noProblems && nvmeCtrlRead && pciRead;
+ }
+
+ return noProblems;
+ }
+
+ public static int GetNumPhysicalDisks() {
+ int num = 2048;
+ Task> task = Task.Run(() => PowershellNumPhysicalDisks());
+ Tuple results = task.Result;
+ if (task.Exception == null) {
+ try {
+ num = int.Parse(results.Item3);
+ } catch (Exception e) {
+ // go with default
+ }
+ }
+
+ return num;
+ }
+
+ private static async Task> PowershellNumPhysicalDisks() {
+ return await StorageWinImports.Powershell("((Get-PhysicalDisk).DeviceId).Count");
+ }
+
+ public static bool GetPciConfigFromCfgMgr(out byte[] config, int diskNumber) {
+ config = Array.Empty();
+
+ bool sdnFound = StorageWin.GetStorageDeviceNumber(out StorageWinStructs.StorageDeviceNumber sdn, diskNumber);
+
+ if (!sdnFound) {
+ return false;
+ }
+
+ bool pciListFound = PciWinCfgMgr.GetDiskDevInterfaces(out List diskDeviceInterfaceIdsW);
+
+ if (!pciListFound) {
+ return false;
+ }
+
+ bool foundDeviceInterfaceId = false;
+ string instanceId = string.Empty;
+ // Find the device instance id that matches the storage device number
+ // Open each disk device interface
+ // Query its storage device number
+ // If a match is found, save the device interface id that matches the disk number
+ foreach (string deviceInterfaceIdW in diskDeviceInterfaceIdsW) {
+ using SafeFileHandle diskHandle = StorageCommonHelpers.OpenDevice(deviceInterfaceIdW);
+ bool functionWorked = StorageWin.GetStorageDeviceNumber(out StorageWinStructs.StorageDeviceNumber candidateSdn, diskHandle);
+
+ if (sdnFound && functionWorked && sdn == candidateSdn) {
+ foundDeviceInterfaceId = true;
+ instanceId = deviceInterfaceIdW;
+ break;
+ }
+ }
+
+ if (!foundDeviceInterfaceId) {
+ return false;
+ }
+
+ bool gotInstanceId = PciWinCfgMgr.GetDeviceInterfaceInstanceId(out string deviceInstanceId, instanceId);
+
+ if (!gotInstanceId) {
+ return false;
+ }
+
+ // The parent of the nvme disk device is its PCI interface
+ bool gotParent = PciWinCfgMgr.GetDeviceNodeOfParent(out IntPtr parentNodePtr, deviceInstanceId);
+
+ if (!gotParent) {
+ return false;
+ }
+
+ bool gotConfig = PciWinCfgMgr.CreateMockConfigBufferFromPciDevNode(out config, out bool isLittleEndian, parentNodePtr);
+
+ return gotConfig;
+ }
+
+ // To get Identify Controller Data use:QueryNvmeCnsThruIntelRstDriver(out StorageWinStructs.IntelNvmeIoctlPassthrough passThrough, deviceNumber, scsiAddr, StorageWinConstants.NvmeCnsValue.IDENTIFY_CONTROLLER, 0);
+ // To get Identify Namespace Data use: QueryNvmeCnsThruIntelRstDriver(out StorageWinStructs.IntelNvmeIoctlPassthrough passThrough, deviceNumber, scsiAddr, StorageWinConstants.NvmeCnsValue.IDENTIFY_NAMESPACE_FOR_NSID, 1);
+ public static bool QueryNvmeCnsThruIntelRstDriver(out StorageNvmeWinStructs.IntelNvmeIoctlPassthrough passThrough, int deviceNumber, StorageWinStructs.ScsiAddress scsiAddr, StorageNvmeConstants.NvmeCnsValue cnsValue, uint nsid) {
+ string deviceString = string.Format(StorageWinConstants.DISK_HANDLE_SCSI, deviceNumber);
+
+ passThrough = StorageCommonHelpers.CreateStruct();
+
+ if (cnsValue == StorageNvmeConstants.NvmeCnsValue.IDENTIFY_NAMESPACE_FOR_NSID && nsid == 0) {
+ return false;
+ }
+
+ using SafeFileHandle handle = StorageCommonHelpers.OpenDevice(deviceString);
+
+ if (!StorageCommonHelpers.IsDeviceHandleReady(handle)) {
+ return false;
+ }
+
+ bool endResult = false;
+ IntPtr ptr = IntPtr.Zero;
+
+ int allocationSize = Marshal.SizeOf();
+ int srbHeaderSize = Marshal.SizeOf();
+ int dataBufferOffset = Marshal.OffsetOf("data").ToInt32();
+
+ passThrough.Header.HeaderLength = (uint)srbHeaderSize;
+ passThrough.Header.Signature = Encoding.ASCII.GetBytes(StorageNvmeWinConstants.INTELNVM_SIGNATURE);
+ passThrough.Header.Timeout = 15;
+ passThrough.Header.ControlCode = StorageNvmeWinConstants.IOCTL_INTEL_NVME_PASSTHROUGH;
+ passThrough.Header.Length = (uint)allocationSize - (uint)srbHeaderSize;
+ passThrough.Version = StorageNvmeWinConstants.INTEL_NVME_PASS_THROUGH_VERSION;
+ passThrough.TargetId = scsiAddr.TargetId;
+ passThrough.Lun = scsiAddr.Lun;
+ passThrough.PathId = scsiAddr.PathId;
+
+ passThrough.Parameters.Command.Admin.opcode = (byte)StorageNvmeConstants.NvmeAdminCommandOpcode.IDENTIFY;
+ passThrough.Parameters.IsIOCommandSet = 0; // 0 = Admin command set
+ passThrough.Parameters.Command.Admin.nsid = nsid;
+ passThrough.Parameters.Command.Admin.cdw10 = (uint)cnsValue;
+
+ passThrough.Parameters.DataBufferLength = 4096;
+ passThrough.Parameters.DataBufferOffset = (uint)dataBufferOffset;
+
+ try {
+ // Allocate memory for the buffer
+ ptr = Marshal.AllocHGlobal(allocationSize);
+
+ // Need the starting address of the buffer for the next command to calculate the offset
+ passThrough.Parameters.Command.Admin.addr = (ulong)IntPtr.Add(ptr, (int)passThrough.Parameters.DataBufferOffset).ToInt64();
+
+ // Copy the data from the managed object to the pointer
+ Marshal.StructureToPtr(passThrough, ptr, true);
+
+ // Prepare to talk to the device
+ NativeOverlapped overlapped = new();
+ int returnedLength = 0;
+ Marshal.SetLastSystemError(0);
+
+ bool validTransfer = StorageWinImports.DeviceIoControl(handle, StorageWinConstants.IOCTL_SCSI_MINIPORT, ptr, allocationSize, ptr, allocationSize, ref returnedLength, ref overlapped);
+
+ if (!validTransfer) {
+ int systemerror = Marshal.GetLastSystemError();
+ endResult = false;
+ } else {
+ passThrough = Marshal.PtrToStructure(ptr);
+ endResult = true;
+ }
+ } finally {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return endResult;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWinConstants.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWinConstants.cs
new file mode 100644
index 0000000..4809f30
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWinConstants.cs
@@ -0,0 +1,9 @@
+using StorageLib.Windows;
+
+namespace StorageNvme.Windows;
+public class StorageNvmeWinConstants {
+ // Intel RST Constants
+ public static readonly string INTELNVM_SIGNATURE = "IntelNvm";
+ public static readonly uint IOCTL_INTEL_NVME_PASSTHROUGH = StorageWin.CTL_CODE(0xF000, 0xA02, StorageWinConstants.IoctlMethodCodes.METHOD_BUFFERED, StorageWinConstants.IoctlFileAccess.FILE_ANY_ACCESS);//0xf0002808;
+ public static readonly byte INTEL_NVME_PASS_THROUGH_VERSION = 1;
+}
diff --git a/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWinStructs.cs b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWinStructs.cs
new file mode 100644
index 0000000..499aad7
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageNvme/src/StorageNvmeWindows/StorageNvmeWinStructs.cs
@@ -0,0 +1,40 @@
+using StorageLib.Windows;
+using System.Runtime.InteropServices;
+
+namespace StorageNvme.Windows;
+public class StorageNvmeWinStructs {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct IntelNvmePassthroughParameters {
+ // NVME_PASS_THROUGH_PARAMETERS
+ public StorageNvmeStructs.NvmeCommand Command;
+ public byte IsIOCommandSet; // False/0 for Admin queue
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] // IsIOCommandSet must conform to DWORD alignment.
+ public byte[]
+ IsIOCommandSetPadding; // If the size of IsIOCommandSet changes, this padding might need to be adjusted.
+
+ public StorageNvmeStructs.CompletionQueueEntry Completion;
+
+ [MarshalAs(UnmanagedType.U4)]
+ public uint DataBufferOffset; // Must be DWORD aligned offset from beginning of SRB_IO_CONTROL
+
+ [MarshalAs(UnmanagedType.U4)] public uint DataBufferLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
+ public byte[] Reserved;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct IntelNvmeIoctlPassthrough {
+ // Intel NVME_IOCTL_PASS_THROUGH
+ public StorageWinStructs.SrbIoControl Header;
+ public byte Version;
+ public byte PathId;
+ public byte TargetId;
+ public byte Lun;
+ public IntelNvmePassthroughParameters Parameters;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)]
+ public byte[] data;
+ }
+}
diff --git a/dotnet/ComponentClassRegistry/StorageWin/StorageWin.csproj b/dotnet/ComponentClassRegistry/StorageWin/StorageWin.csproj
new file mode 100644
index 0000000..bb23fb7
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageWin/StorageWin.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/dotnet/ComponentClassRegistry/StorageWin/src/StorageWinStructs.cs b/dotnet/ComponentClassRegistry/StorageWin/src/StorageWinStructs.cs
new file mode 100644
index 0000000..333b9ab
--- /dev/null
+++ b/dotnet/ComponentClassRegistry/StorageWin/src/StorageWinStructs.cs
@@ -0,0 +1,4 @@
+namespace StorageLib.Windows;
+public class StorageWinStructs {
+
+}