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 { + +}