diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 741ccef..6e5e97a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,21 +3,21 @@ "isRoot": true, "tools": { "csharpier": { - "version": "1.1.2", + "version": "1.2.6", "commands": [ "csharpier" ], "rollForward": false }, "dotnet-outdated-tool": { - "version": "4.6.8", + "version": "4.7.1", "commands": [ "dotnet-outdated" ], "rollForward": false }, "husky": { - "version": "0.7.2", + "version": "0.9.1", "commands": [ "husky" ], diff --git a/src/.csharpierrc.json b/.csharpierrc.json similarity index 100% rename from src/.csharpierrc.json rename to .csharpierrc.json diff --git a/.editorconfig b/.editorconfig index e287c60..9697655 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# Root .editorconfig as of 2025-02-03 +# Root .editorconfig as of 2026-06-07 root = true @@ -35,3 +35,599 @@ end_of_line = crlf # XML [*.xml] indent_size = 2 + +# === .NET/C# coding conventions (merged from src/.editorconfig) === +# Visual Studio Solutions +[*.sln] +indent_style = tab + +# Visual Studio Project Files +[*.csproj] +indent_size = 2 + +# C# files +[{src,smoketests}/**/*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +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:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +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:warning + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = None + +#### C# Coding Conventions #### +# var preferences +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning + + +# 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:suggestion +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_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = 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:warning +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:warning +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_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 = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +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 = no_change +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 = true +csharp_preserve_single_line_statements = true +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_prefer_extended_property_pattern = true:suggestion + +#### Naming styles #### +# Naming rules +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion +dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces +dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase + +dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion +dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters +dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase + +dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods +dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties +dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.events_should_be_pascalcase.symbols = events +dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables +dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase + +dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase + +dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion +dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters +dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase + +dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields +dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion +dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields +dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase + +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = _camelcase + +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums +dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase + +# Symbol specifications + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interfaces.required_modifiers = + +dotnet_naming_symbols.enums.applicable_kinds = enum +dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enums.required_modifiers = + +dotnet_naming_symbols.events.applicable_kinds = event +dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.events.required_modifiers = + +dotnet_naming_symbols.methods.applicable_kinds = method +dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.methods.required_modifiers = + +dotnet_naming_symbols.properties.applicable_kinds = property +dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.properties.required_modifiers = + +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_fields.required_modifiers = + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_fields.required_modifiers = + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum +dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types_and_namespaces.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 = + +dotnet_naming_symbols.type_parameters.applicable_kinds = namespace +dotnet_naming_symbols.type_parameters.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters.required_modifiers = + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_variables.applicable_kinds = local +dotnet_naming_symbols.local_variables.applicable_accessibilities = local +dotnet_naming_symbols.local_variables.required_modifiers = + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_symbols.parameters.applicable_accessibilities = * +dotnet_naming_symbols.parameters.required_modifiers = + +dotnet_naming_symbols.public_constant_fields.applicable_kinds = field +dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_constant_fields.required_modifiers = const + +dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function +dotnet_naming_symbols.local_functions.applicable_accessibilities = * +dotnet_naming_symbols.local_functions.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.capitalization = pascal_case + +dotnet_naming_style.ipascalcase.required_prefix = I +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.capitalization = pascal_case + +dotnet_naming_style.tpascalcase.required_prefix = T +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.capitalization = pascal_case + +dotnet_naming_style._camelcase.required_prefix = _ +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.capitalization = camel_case + +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.capitalization = camel_case + +dotnet_naming_style.s_camelcase.required_prefix = s_ +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.capitalization = camel_case +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_allow_multiple_blank_lines_experimental = true:suggestion +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent + +# AnalyzerId= AsyncUsageAnalyzers RuleNamespace= AsyncUsageAnalyzers +dotnet_diagnostic.UseConfigureAwait.severity = Warning + +# AnalyzerId= DocumentationAnalyzers RuleNamespace= DocumentationAnalyzers +dotnet_diagnostic.DOC100.severity = Warning +dotnet_diagnostic.DOC101.severity = Warning +dotnet_diagnostic.DOC102.severity = Warning +dotnet_diagnostic.DOC103.severity = Warning +dotnet_diagnostic.DOC104.severity = Warning +dotnet_diagnostic.DOC105.severity = Warning +dotnet_diagnostic.DOC106.severity = Warning +dotnet_diagnostic.DOC107.severity = Warning +dotnet_diagnostic.DOC108.severity = Warning +dotnet_diagnostic.DOC200.severity = Warning +dotnet_diagnostic.DOC201.severity = Warning +dotnet_diagnostic.DOC202.severity = Warning +dotnet_diagnostic.DOC203.severity = Warning +dotnet_diagnostic.DOC204.severity = Warning + +# AnalyzerId= Microsoft.Analyzers.ManagedCodeAnalysis RuleNamespace= Microsoft.Rules.Managed +dotnet_diagnostic.CA1001.severity = Warning +dotnet_diagnostic.CA1009.severity = Warning +dotnet_diagnostic.CA1016.severity = Warning +dotnet_diagnostic.CA1033.severity = Warning +dotnet_diagnostic.CA1049.severity = Warning +dotnet_diagnostic.CA1056.severity = Warning +dotnet_diagnostic.CA1060.severity = Warning +dotnet_diagnostic.CA1061.severity = Warning +dotnet_diagnostic.CA1063.severity = Warning +dotnet_diagnostic.CA1065.severity = Warning +dotnet_diagnostic.CA1301.severity = Warning +dotnet_diagnostic.CA1400.severity = Warning +dotnet_diagnostic.CA1401.severity = Warning +dotnet_diagnostic.CA1403.severity = Warning +dotnet_diagnostic.CA1404.severity = Warning +dotnet_diagnostic.CA1405.severity = Warning +dotnet_diagnostic.CA1410.severity = Warning +dotnet_diagnostic.CA1415.severity = Warning +dotnet_diagnostic.CA1821.severity = Warning +dotnet_diagnostic.CA1825.severity = Warning +dotnet_diagnostic.CA1900.severity = Warning +dotnet_diagnostic.CA1901.severity = Warning +dotnet_diagnostic.CA2000.severity = Warning +dotnet_diagnostic.CA2002.severity = Warning +dotnet_diagnostic.CA2100.severity = Warning +dotnet_diagnostic.CA2101.severity = Warning +dotnet_diagnostic.CA2108.severity = Warning +dotnet_diagnostic.CA2111.severity = Warning +dotnet_diagnostic.CA2112.severity = Warning +dotnet_diagnostic.CA2114.severity = Warning +dotnet_diagnostic.CA2116.severity = Warning +dotnet_diagnostic.CA2117.severity = Warning +dotnet_diagnostic.CA2122.severity = Warning +dotnet_diagnostic.CA2123.severity = Warning +dotnet_diagnostic.CA2124.severity = Warning +dotnet_diagnostic.CA2126.severity = Warning +dotnet_diagnostic.CA2131.severity = Warning +dotnet_diagnostic.CA2132.severity = Warning +dotnet_diagnostic.CA2133.severity = Warning +dotnet_diagnostic.CA2134.severity = Warning +dotnet_diagnostic.CA2137.severity = Warning +dotnet_diagnostic.CA2138.severity = Warning +dotnet_diagnostic.CA2140.severity = Warning +dotnet_diagnostic.CA2141.severity = Warning +dotnet_diagnostic.CA2146.severity = Warning +dotnet_diagnostic.CA2147.severity = Warning +dotnet_diagnostic.CA2149.severity = Warning +dotnet_diagnostic.CA2200.severity = Warning +dotnet_diagnostic.CA2202.severity = Warning +dotnet_diagnostic.CA2207.severity = Warning +dotnet_diagnostic.CA2212.severity = Warning +dotnet_diagnostic.CA2213.severity = Warning +dotnet_diagnostic.CA2214.severity = Warning +dotnet_diagnostic.CA2215.severity = Warning +dotnet_diagnostic.CA2216.severity = Warning +dotnet_diagnostic.CA2220.severity = Warning +dotnet_diagnostic.CA2229.severity = Warning +dotnet_diagnostic.CA2231.severity = Warning +dotnet_diagnostic.CA2232.severity = Warning +dotnet_diagnostic.CA2235.severity = Warning +dotnet_diagnostic.CA2236.severity = Warning +dotnet_diagnostic.CA2237.severity = Warning +dotnet_diagnostic.CA2238.severity = Warning +dotnet_diagnostic.CA2240.severity = Warning +dotnet_diagnostic.CA2241.severity = Warning +dotnet_diagnostic.CA2242.severity = Warning +dotnet_diagnostic.CA2253.severity = Warning +dotnet_diagnostic.CA2254.severity = Warning + +# AnalyzerId= Microsoft.CodeAnalysis.CSharp RuleNamespace= Microsoft.CodeAnalysis.CSharp +dotnet_diagnostic.CS8019.severity = Warning + +# AnalyzerId= Microsoft.CodeAnalysis.CSharp.Analyzers RuleNamespace= Microsoft.CodeAnalysis.CSharp.Analyzers +dotnet_diagnostic.RS1022.severity = Warning + +# AnalyzerId= Microsoft.CodeAnalysis.CSharp.CodeStyle RuleNamespace= Microsoft.CodeAnalysis.CSharp.CodeStyle +dotnet_diagnostic.IDE0055.severity = None +dotnet_diagnostic.IDE0079.severity = Warning +dotnet_diagnostic.IDE1006.severity = Warning + +# AnalyzerId= Microsoft.CodeAnalysis.CSharp.Features RuleNamespace= Microsoft.CodeAnalysis.CSharp.Features +dotnet_diagnostic.IDE0003.severity = None +dotnet_diagnostic.IDE0007.severity = Warning +dotnet_diagnostic.IDE0009.severity = None + +# AnalyzerId= StyleCop.Analyzers RuleNamespace= StyleCop.Analyzers +dotnet_diagnostic.SA1000.severity = None +dotnet_diagnostic.SA1010.severity = None +dotnet_diagnostic.SA1003.severity = None +dotnet_diagnostic.SA1005.severity = None +dotnet_diagnostic.SA1008.severity = None +dotnet_diagnostic.SA1009.severity = None +dotnet_diagnostic.SA1012.severity = None +dotnet_diagnostic.SA1013.severity = None +dotnet_diagnostic.SA1025.severity = None +dotnet_diagnostic.SA1101.severity = None +dotnet_diagnostic.SA1108.severity = None +dotnet_diagnostic.SA1111.severity = None +dotnet_diagnostic.SA1116.severity = None +dotnet_diagnostic.SA1118.severity = None +dotnet_diagnostic.SA1123.severity = None +dotnet_diagnostic.SA1124.severity = None +dotnet_diagnostic.SA1127.severity = None +dotnet_diagnostic.SA1128.severity = None +dotnet_diagnostic.SA1137.severity = None +dotnet_diagnostic.SA1201.severity = None +dotnet_diagnostic.SA1202.severity = None +dotnet_diagnostic.SA1203.severity = None +dotnet_diagnostic.SA1204.severity = None +dotnet_diagnostic.SA1309.severity = None +dotnet_diagnostic.SA1412.severity = Error +dotnet_diagnostic.SA1413.severity = None +dotnet_diagnostic.SA1500.severity = None +dotnet_diagnostic.SA1501.severity = None +dotnet_diagnostic.SA1502.severity = None +dotnet_diagnostic.SA1504.severity = None +dotnet_diagnostic.SA1512.severity = None +dotnet_diagnostic.SA1513.severity = None +dotnet_diagnostic.SA1515.severity = None +dotnet_diagnostic.SA1516.severity = None + +# Documentation Rules (SA1600-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/DocumentationRules.md +dotnet_diagnostic.SA1600.severity = Warning # A C# code element is missing a documentation header. +dotnet_diagnostic.SA1601.severity = Warning # A C# partial element is missing a documentation header. +dotnet_diagnostic.SA1602.severity = Warning # An item within a C# enumeration is missing an Xml documentation header. +dotnet_diagnostic.SA1603.severity = Warning # The Xml within a C# element's document header is badly formed. +dotnet_diagnostic.SA1604.severity = Warning # The Xml header documentation for a C# element is missing a tag. +dotnet_diagnostic.SA1605.severity = Warning # The or tag within the documentation header for a C# code element is missing or empty. +dotnet_diagnostic.SA1606.severity = Warning # The tag within the documentation header for a C# code element is empty. +dotnet_diagnostic.SA1607.severity = Warning # The or tag within the documentation header for a C# code element is empty. +dotnet_diagnostic.SA1608.severity = Warning # The tag within an element's Xml header documentation contains the default text generated by Visual Studio during the creation of the element. +dotnet_diagnostic.SA1609.severity = Warning # The Xml header documentation for a C# property does not contain a tag. +dotnet_diagnostic.SA1610.severity = Warning # The Xml header documentation for a C# property contains an empty tag. +dotnet_diagnostic.SA1611.severity = Warning # A C# method, constructor, delegate or indexer element is missing documentation for one or more of its parameters. +dotnet_diagnostic.SA1612.severity = Warning # The documentation describing the parameters to a C# method, constructor, delegate or indexer element does not match the actual parameters on the element. +dotnet_diagnostic.SA1613.severity = Warning # A tag within a C# element's documentation header is missing a name attribute containing the name of the parameter. +dotnet_diagnostic.SA1614.severity = Warning # A tag within a C# element's documentation header is empty. +dotnet_diagnostic.SA1615.severity = Warning # A C# element is missing documentation for its return value. +dotnet_diagnostic.SA1616.severity = Warning # The tag within a C# element's documentation header is empty. +dotnet_diagnostic.SA1617.severity = Warning # A C# code element does not contain a return value, or returns void, but the documentation header for the element contains a tag. +dotnet_diagnostic.SA1618.severity = Warning # A generic C# element is missing documentation for one or more of its generic type parameters. +dotnet_diagnostic.SA1619.severity = Warning # A generic, partial C# element is missing documentation for one or more of its generic type parameters, and the documentation for the element contains a tag. +dotnet_diagnostic.SA1620.severity = Warning # The tags within the Xml header documentation for a generic C# element do not match the generic type parameters on the element. +dotnet_diagnostic.SA1621.severity = Warning # A tag within the Xml header documentation for a generic C# element is missing a name attribute, or contains an empty name attribute. +dotnet_diagnostic.SA1622.severity = Warning # A tag within the Xml header documentation for a generic C# element is empty. +dotnet_diagnostic.SA1623.severity = Warning # The documentation text within a C# property's tag does not match the accessors within the property. +dotnet_diagnostic.SA1624.severity = Warning # The documentation text within a C# property's tag takes into account all of the accessors within the property, but one of the accessors has limited access. +dotnet_diagnostic.SA1625.severity = Warning # The Xml documentation for a C# element contains two or more identical entries, indicating that the documentation has been copied and pasted. +dotnet_diagnostic.SA1626.severity = Warning # The C# code contains a single-line comment which begins with three forward slashes in a row. +dotnet_diagnostic.SA1627.severity = Warning # The Xml header documentation for a C# code element contains an empty tag. +dotnet_diagnostic.SA1628.severity = Warning # A section of the Xml header documentation for a C# element does not begin with a capital letter. +dotnet_diagnostic.SA1629.severity = None # A section of the Xml header documentation for a C# element does not end with a period (also known as a full stop). +dotnet_diagnostic.SA1630.severity = Warning # A section of the Xml header documentation for a C# element does not contain any whitespace between words. +dotnet_diagnostic.SA1631.severity = Warning # A section of the Xml header documentation for a C# element does not contain enough alphabetic characters. +dotnet_diagnostic.SA1632.severity = None # From StyleCop 4.5 this rule is disabled by default. +dotnet_diagnostic.SA1633.severity = None # A C# code file is missing a standard file header. +dotnet_diagnostic.SA1634.severity = Warning # The file header at the top of a C# code file is missing a copyright tag. +dotnet_diagnostic.SA1635.severity = Warning # The file header at the top of a C# code file is missing copyright text. +dotnet_diagnostic.SA1636.severity = Warning # The file header at the top of a C# code file does not contain the appropriate copyright text. +dotnet_diagnostic.SA1637.severity = Warning # The file header at the top of a C# code file is missing the file name. +dotnet_diagnostic.SA1638.severity = Warning # The file tag within the file header at the top of a C# code file does not contain the name of the file. +dotnet_diagnostic.SA1639.severity = Warning # The file header at the top of a C# code file does not contain a filled-in summary tag. +dotnet_diagnostic.SA1640.severity = Warning # The file header at the top of a C# code file does not contain company name text. +dotnet_diagnostic.SA1641.severity = Warning # The file header at the top of a C# code file does not contain the appropriate company name text. +dotnet_diagnostic.SA1642.severity = Warning # The XML documentation header for a C# constructor does not contain the appropriate summary text. +dotnet_diagnostic.SA1643.severity = Warning # The Xml documentation header for a C# finalizer does not contain the appropriate summary text. +dotnet_diagnostic.SA1644.severity = Warning # A section within the Xml documentation header for a C# element contains blank lines. +dotnet_diagnostic.SA1645.severity = Warning # An included Xml documentation file does not exist. +dotnet_diagnostic.SA1646.severity = Warning # An included Xml documentation link contains an invalid path. +dotnet_diagnostic.SA1647.severity = Warning # An include tag within an Xml documentation header does not contain valid file and path attribute. +dotnet_diagnostic.SA1648.severity = Warning # has been used on an element that doesn't inherit from a base class or implement an interface. +dotnet_diagnostic.SA1649.severity = Warning # The file name of a C# code file does not match the first type declared in the file. +dotnet_diagnostic.SA1650.severity = Warning # The element documentation for the element contains one or more spelling mistakes or unrecognized words. +dotnet_diagnostic.SA1651.severity = Warning # The documentation for the element contains one or more elements. +dotnet_diagnostic.SA1652.severity = None # This rule was moved to SA0001 + +# Sonar +dotnet_diagnostic.S107.severity = Suggestion # Methods should not have too many parameters +dotnet_diagnostic.S1210.severity = Suggestion # "Equals" and the comparison operators should be overridden when implementing "IComparable" +dotnet_diagnostic.S2436.severity = Suggestion # Types and methods should not have too many generic parameters +dotnet_diagnostic.S4457.severity = Warning # Parameter validation in "async"/"await" methods should be wrapped + +# C# Autogenerated code +[{**/obj/**,**/bin/**}] +# AnalyzerId= Microsoft.CodeAnalysis.CSharp RuleNamespace= Microsoft.CodeAnalysis.CSharp +dotnet_diagnostic.CS8019.severity = none + +# C# Tests files in Tests projects +[{**Tests/**Tests.cs,**Tests.**/**Tests.cs}] +# Reset StyleCop documentation comments +dotnet_diagnostic.SA1600.severity = None # A C# code element is missing a documentation header. +dotnet_diagnostic.SA1601.severity = None # A C# partial element is missing a documentation header. +dotnet_diagnostic.SA1602.severity = None # An item within a C# enumeration is missing an Xml documentation header. + +# Sonar +dotnet_diagnostic.S4114.severity = Suggestion # Methods should not have identical implementations + +# Microsoft.VisualStudio.Threading +dotnet_diagnostic.VSTHRD200.severity = None # Use Async suffix for async methods + +# Misc +dotnet_diagnostic.CS1591.severity = None # Missing XML comment for publicly visible type or member 'Type_or_Member' +dotnet_diagnostic.CA1707.severity = None # Identifiers should not contain underscores +dotnet_diagnostic.RCS1192.severity = Warning # RCS1192: Unnecessary usage of verbatim string literal. diff --git a/.gitattributes b/.gitattributes index d6ad5f9..20c6716 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,26 +1,57 @@ +############################################################################### +# Git Attributes +# +# Normalize text files to LF in the repository. Git will handle conversion +# during checkout as needed, while .editorconfig controls the editor behavior. ############################################################################### -# Set default behavior to automatically normalize line endings. + +* text=auto eol=lf + ############################################################################### -* text=auto +# Language-specific diff drivers +############################################################################### + +*.cs diff=csharp ############################################################################### -# Set default behavior for command prompt diff. +# Merge behavior # -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line +# Prevent Git from inserting conflict markers into Visual Studio solution and +# project files. These files require manual conflict resolution. +############################################################################### + +*.sln merge=binary +*.csproj merge=binary + +############################################################################### +# Linux / Cross-platform files ############################################################################### -*.cs diff=csharp + +*.bash text eol=lf +*.editorconfig text eol=lf +*.sh text eol=lf + +*.json text eol=lf +*.jsonc text eol=lf +*.md text eol=lf +*.xml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +Dockerfile text eol=lf ############################################################################### -# Set the merge driver for project and solution files +# Windows-native files +############################################################################### + +*.bat text eol=crlf +*.cmd text eol=crlf + +############################################################################### +# PowerShell # -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -*.sln merge=binary -*.csproj merge=binary \ No newline at end of file +# PowerShell fully supports LF, making it the preferred choice for +# cross-platform repositories and devcontainers. +############################################################################### + +*.ps1 text eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e69de29 diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..bb55ed8 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,12 @@ +name: "NetDuid CodeQL Configuration" + +query-filters: + - exclude: + problem.severity: + - warning + - recommendation + - note + +paths-ignore: + - src/NetDuid.Tests + - src/NetDuid.Benchmarks diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c29e02b..d893178 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,10 +9,6 @@ updates: day: "sunday" allow: - dependency-type: direct - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-patch" commit-message: prefix: "Dependabot package update" open-pull-requests-limit: 5 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98ed4c2..081bc98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,11 +13,19 @@ on: branches: ["main"] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: build: - runs-on: windows-latest + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} env: - HUSKY: 0 # don't run husky.net hooks + HUSKY: 0 # don't run husky.net hooks steps: - name: Checkout code @@ -26,22 +34,87 @@ jobs: - name: Setup .NET SDKs uses: actions/setup-dotnet@v5 with: - # .net 4.8.x is included by default in windows-latest dotnet-version: | 8.0.x 9.0.x 10.0.x + - name: NuGet cache + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget- + - name: Restore .NET tools run: dotnet tool restore - working-directory: . - name: Restore dependencies run: dotnet restore - working-directory: ./src - name: Build - run: dotnet build ./src --no-restore -p:VersionFromCI="0.0.0-cibuild" + run: dotnet build --no-restore --configuration Release -p:VersionFromCI="0.0.0-cibuild" + + - name: Test with coverage (Ubuntu — net8.0) + if: matrix.os == 'ubuntu-latest' + run: | + dotnet tool install --global dotnet-coverage 2>/dev/null || dotnet tool update --global dotnet-coverage + dotnet-coverage collect "dotnet test --no-build --verbosity normal --logger trx --framework net8.0" -f cobertura -o coverage/cobertura-net8.xml - - name: Test - run: dotnet test ./src --no-build --verbosity normal + - name: Test with coverage (Ubuntu — net9.0) + if: matrix.os == 'ubuntu-latest' + run: | + dotnet tool install --global dotnet-coverage 2>/dev/null || dotnet tool update --global dotnet-coverage + dotnet-coverage collect "dotnet test --no-build --verbosity normal --logger trx --framework net9.0" -f cobertura -o coverage/cobertura-net9.xml + + - name: Test with coverage (Ubuntu — net10.0) + if: matrix.os == 'ubuntu-latest' + run: | + dotnet tool install --global dotnet-coverage 2>/dev/null || dotnet tool update --global dotnet-coverage + dotnet-coverage collect "dotnet test --no-build --verbosity normal --logger trx --framework net10.0" -f cobertura -o coverage/cobertura.xml + + - name: Test (Windows — all TFMs including net48) + if: matrix.os == 'windows-latest' + run: dotnet test --no-build --verbosity normal --logger trx + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ runner.os }} + path: '**/TestResults/*.trx' + retention-days: 30 + + - name: Upload coverage results + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ github.sha }} + path: coverage/ + retention-days: 30 + + coverage-report: + needs: build + if: github.event_name == 'pull_request' && needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - uses: actions/download-artifact@v4 + with: + name: coverage-${{ github.sha }} + path: coverage/ + merge-multiple: true + - name: Generate coverage badge + uses: danielpalme/ReportGenerator-GitHub-Action@v5 + with: + reports: coverage/*.xml + targetdir: coveragereport + reporttypes: MarkdownSummary + - name: Post coverage summary + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: Code Coverage + path: coveragereport/Summary.md diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..0b2ca1e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,50 @@ +name: "CodeQL" + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + schedule: + - cron: '31 15 * * 2' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: csharp + build-mode: autobuild + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended,security-and-quality + config-file: ./.github/codeql/codeql-config.yml + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..b453179 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,28 @@ +name: Dependency Review + +on: + pull_request: + branches: ["main"] + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Review dependencies + uses: actions/dependency-review-action@v4 + continue-on-error: true + with: + fail-on-severity: high + comment-summary-in-pr: true + # Suppress "unknown license" warnings — they are false positives: + # 1) GitHub Actions don't carry SPDX metadata the tool can read + # 2) Some preview NuGet packages lack modern in nuspec + # 3) The NetDuid smoke test references a placeholder version not on NuGet.org + allow-unknown-licenses: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d28cb68..ae8e319 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,7 @@ name: Publish .NET Package permissions: - contents: read + contents: write packages: write pull-requests: write @@ -17,7 +17,7 @@ jobs: build: runs-on: ubuntu-latest env: - HUSKY: 0 # don't run husky.net hooks + HUSKY: 0 # don't run husky.net hooks steps: - name: Checkout code @@ -28,30 +28,89 @@ jobs: with: dotnet-version: 10.0.x - - name: Extract version - id: extract_version - run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV + - name: Restore .NET tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release /p:VersionFromCI=${{ github.ref_name }} + + - name: Test + run: dotnet test --no-build --verbosity normal + + publish: + needs: [build] + runs-on: ubuntu-latest + env: + HUSKY: 0 # don't run husky.net hooks + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x - name: Restore .NET tools run: dotnet tool restore - working-directory: . - name: Restore dependencies run: dotnet restore - working-directory: ./src - name: Build - run: dotnet build ./src --no-restore --configuration Release /p:VersionFromCI=${{ env.VERSION }} + run: dotnet build --no-restore --configuration Release /p:VersionFromCI=${{ github.ref_name }} - name: Pack - run: dotnet pack ./src/NetDuid/NetDuid.csproj --configuration Release --no-build --output ./packages /p:VersionFromCI=${{ env.VERSION }} + run: dotnet pack src/NetDuid/NetDuid.csproj --configuration Release --no-build --output packages /p:VersionFromCI=${{ github.ref_name }} + + - name: Generate CycloneDX SBOM + run: | + dotnet tool install --global CycloneDX --version 3.* + dotnet-cyclonedx packages/ -o packages/ --output-format json + echo "SBOM generated:" + ls packages/*.json + + - name: Upload NuGet packages and SBOM + uses: actions/upload-artifact@v4 + with: + name: nuget-packages + path: | + packages/*.nupkg + packages/*.json + packages/*.xml + retention-days: 90 - name: Publish to GitHub Packages env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: dotnet nuget push ./packages/*.nupkg --source "https://nuget.pkg.github.com/sandialabs/index.json" --api-key $GITHUB_TOKEN --skip-duplicate + run: dotnet nuget push ./packages/*.nupkg --source "https://nuget.pkg.github.com/${{ github.repository }}/index.json" --api-key $GITHUB_TOKEN --skip-duplicate - name: Publish to nuget.org env: NUGET_ORG_APIKEY: ${{ secrets.NUGET_ORG_APIKEY }} run: dotnet nuget push ./packages/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key $NUGET_ORG_APIKEY --skip-duplicate + + - name: Generate release body + run: | + VERSION="${{ github.ref_name }}" + TAG="${{ github.ref_name }}" + # Extract section from CHANGELOG.md for this version, or fall back to commit log + if grep -q "## \[$VERSION\]" CHANGELOG.md 2>/dev/null; then + awk "/^## \[$VERSION\]/,/^## \[/{ if (!/^## \[/) print }" CHANGELOG.md | head -n -1 > release_body.md + else + echo "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for release notes." > release_body.md + fi + + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ github.ref_name }}" \ + --repo "${{ github.repository }}" \ + --title "v${{ github.ref_name }}" \ + --notes-file release_body.md \ + ./packages/*.nupkg diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..5c433d4 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,44 @@ +name: OpenSSF Scorecard + +on: + push: + branches: ["main"] + schedule: + - cron: "0 10 * * 1" + +permissions: + contents: read + +jobs: + analysis: + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + actions: read + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Run analysis + uses: ossf/scorecard-action@v2 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: Upload SARIF results + uses: actions/upload-artifact@v4 + with: + name: scorecard-results + path: results.sarif + retention-days: 30 + + - name: Upload to code-scanning dashboard + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: results.sarif diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml new file mode 100644 index 0000000..71ed34f --- /dev/null +++ b/.github/workflows/smoke-test.yml @@ -0,0 +1,79 @@ +name: Smoke Test + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + smoke-test: + name: ${{ matrix.os }} / ${{ matrix.framework }} + runs-on: ${{ matrix.os }} + env: + HUSKY: 0 + + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + framework: net48 + - os: ubuntu-latest + framework: net8.0 + - os: ubuntu-latest + framework: net9.0 + - os: ubuntu-latest + framework: net10.0 + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: NuGet cache + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-smoke-${{ hashFiles('**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget-smoke- + ${{ runner.os }}-nuget- + + - name: Pack NetDuid into local feed + shell: bash + run: | + mkdir -p smoketests/feed + dotnet pack src/NetDuid/NetDuid.csproj \ + --configuration Release \ + --output smoketests/feed \ + -p:VersionFromCI=99.0.0-smoke \ + --verbosity quiet + + - name: Restore SmokeTests consumer + run: dotnet restore smoketests/SmokeTests/SmokeTests.csproj -p:NetDuidVersion=99.0.0-smoke --verbosity quiet + + - name: Build SmokeTests consumer (${{ matrix.framework }}) + run: dotnet build smoketests/SmokeTests/SmokeTests.csproj --no-restore --configuration Release --framework ${{ matrix.framework }} -p:NetDuidVersion=99.0.0-smoke --verbosity quiet + + - name: Run smoke tests (${{ matrix.framework }}) + run: > + dotnet run + --project smoketests/SmokeTests/SmokeTests.csproj + --framework ${{ matrix.framework }} + --configuration Release + --no-build + -p:NetDuidVersion=99.0.0-smoke diff --git a/.gitignore b/.gitignore index 7e2820d..91ab593 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -### ASPNETCore ### +### ASPNETCore ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. @@ -199,6 +199,40 @@ ehthumbs_vista.db # Coderush **/.cr/* -### Read the Docs ### -docs/_build +# Smoke test local NuGet feed (created by run-smoke-tests scripts) +smoketests/feed/ +# Benchmark results (commit manually with git add -f to record a progression snapshot) +src/results/ + +# Planning and working documents (root-level only) +/PRD*.md +/RELEASE-*.md +/future.todo.md +/skills-lock.json +.claude/ +.opencode/ +.agents/ +devcontainer-lock.json +TestResults/ + +# Git worktrees +.worktrees/ + +# Agent instruction files (root-level only, prevent accidental staging) +/AGENTS.md +/CLAUDE.md + +# Generated XML doc (in-tree artifact) +src/NetDuid/NetDuid.xml + +# Temporary files +*.bak +*.swp +*.swo + +# Container files +.devcontainer/ + +# Local scratch files (notes, plans, review handoffs) +.scratch/ diff --git a/.husky/pre-commit b/.husky/pre-commit index fd85d23..818853f 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,22 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -## husky task runner examples ------------------- -## Note : for local installation use 'dotnet' prefix. e.g. 'dotnet husky' - -## run all tasks -#husky run - -### run all tasks with group: 'group-name' -#husky run --group group-name - -## run task with name: 'task-name' -#husky run --name task-name - -## pass hook arguments to task -#husky run --args "$1" "$2" - -## or put your custom commands ------------------- -#echo 'Husky.Net is awesome!' - dotnet husky run diff --git a/.husky/task-runner.json b/.husky/task-runner.json index 9603920..aa4b730 100644 --- a/.husky/task-runner.json +++ b/.husky/task-runner.json @@ -4,22 +4,22 @@ "name": "style", "group": "pre-commit", "command": "dotnet", - "args": ["format", "src", "style", "--no-restore", "--include", "${staged}"], - "include": ["src/**/*.cs"] + "args": ["format", "style", "--no-restore", "--include", "${staged}"], + "include": ["src/**/*.cs", "smoketests/**/*.cs"] }, { "name": "analyzers", "group": "pre-commit", "command": "dotnet", - "args": ["format", "src", "analyzers", "--no-restore", "--include", "${staged}"], - "include": ["src/**/*.cs"] + "args": ["format", "analyzers", "--no-restore", "--include", "${staged}"], + "include": ["src/**/*.cs", "smoketests/**/*.cs"] }, { "name": "csharpier", "group": "pre-commit", "command": "dotnet", - "args": ["csharpier", "format", "--config-path", ".\\src\\.csharpierrc.json", "${staged}"], - "include": ["src/**/*.cs"] + "args": ["csharpier", "format", "--config-path", "./.csharpierrc.json", "${staged}"], + "include": ["src/**/*.cs", "smoketests/**/*.cs"] } ] } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..71a3fc7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +# Pre-commit hook configuration. +# Install with: pre-commit install +# Run on all files: pre-commit run --all-files +# Update hook versions: pre-commit autoupdate +# +# See https://pre-commit.com for more information. + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + args: [--maxkb=512] + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + args: [--ignore-words-list=fo] + types_or: [csharp, python, javascript, ts, markdown, shell, yaml, json] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.44.0 + hooks: + - id: markdownlint + args: [--disable, MD013] + + - repo: local + hooks: + - id: csharpier + name: CSharpier + entry: dotnet csharpier + language: system + types: [csharp] + pass_filenames: false \ No newline at end of file diff --git a/.scratch/code-review-handoff.md b/.scratch/code-review-handoff.md new file mode 100644 index 0000000..d885de0 --- /dev/null +++ b/.scratch/code-review-handoff.md @@ -0,0 +1,253 @@ +# Code Review Handoff: NetDuid Production Library + +**Date:** 2026-07-05 +**Reviewer:** opencode (qwen3.7-plus) +**Scope:** Production library (`src/NetDuid/`), test project (`src/NetDuid.Tests/`), smoke tests (`smoketests/`), build/CI configuration +**Status:** Discovery complete. Grilling in progress. + +--- + +## Summary + +NetDuid is a .NET library for DHCP Unique Identifiers (DUIDs) per RFC 8415 / RFC 6355. Single sealed partial class `Duid` split across 8 files by interface/concern. Targets `netstandard2.0`, `net8.0`, `net9.0`, `net10.0`. Test suite uses xUnit v3 with MTP runner. + +**Overall assessment:** The production code is well-structured and the test suite is thorough. Several configuration issues and one unnecessary dependency stand out as the most actionable items. + +--- + +## Findings: Production Library + +### HIGH Severity + +#### H1. Unnecessary `Microsoft.Bcl.HashCode` dependency for net8.0+ +- **Location:** `src/NetDuid/NetDuid.csproj:84-85` +- **What:** `Microsoft.Bcl.HashCode` is referenced twice: once conditionally for `net48` (line 84) and once unconditionally (line 85). The unconditional reference means net8.0/net9.0/net10.0 all pull in this package unnecessarily — `HashCode` is in-box since .NET Core 2.1. +- **Why it matters:** Unnecessary dependency increases package size, restore time, and potential for version conflicts. The `netstandard2.0` target genuinely needs it. +- **Confidence:** HIGH (verified in csproj) +- **Interaction:** `Duid.IEquatable.cs:73` uses `System.HashCode` which is in-box for net8.0+ and polyfilled by this package for netstandard2.0. + +#### H2. `Equals(Duid)` allocates on every call via `GetBytes()` +- **Location:** `src/NetDuid/Duid.IEquatable.cs:38-39` +- **What:** `Equals(Duid)` calls `other.GetBytes()` which creates a new `Array.AsReadOnly()` wrapper, then uses LINQ `SequenceEqual` on it. Since `Duid` is a partial class, it has direct access to `other._duidBytes`. +- **Why it matters:** Every equality comparison allocates a `ReadOnlyCollection` wrapper and uses LINQ enumeration instead of direct array access. For a type that implements value semantics and will be compared frequently (e.g., as dictionary keys), this is a hot path. +- **Confidence:** HIGH +- **Interaction:** `GetBytes()` at `Duid.cs:136-139` returns `Array.AsReadOnly(_duidBytes)`. `IComparable.cs:42` accesses `other._duidBytes[i]` directly — inconsistent with `IEquatable`. + +#### H3. `Directory.Build.props` TargetFrameworks are dead configuration +- **Location:** `src/Directory.Build.props:3` +- **What:** Sets `TargetFrameworks` to `net10.0;net9.0;net8.0;net48`, but both `NetDuid.csproj` (line 4: `netstandard2.0;net8.0;net9.0;net10.0`) and `NetDuid.Tests.csproj` (line 11: `net48;net8.0;net9.0;net10.0`) override this property. The Directory.Build.props value is never used. +- **Why it matters:** Misleading to developers. Someone might change TFMs in Directory.Build.props expecting it to propagate, but it won't. Also, the Directory.Build.props includes `net48` which the production library does NOT target. +- **Confidence:** HIGH +- **Interaction:** The `net48` TFM in Directory.Build.props suggests the library should target net48, but it targets `netstandard2.0` instead (which is the correct choice for net48 compatibility). + +#### H4. Duplicate and inconsistent metadata in test csproj +- **Location:** `src/NetDuid.Tests/NetDuid.Tests.csproj:4-8` and `22-26` +- **What:** `Company`, `Authors`, and `Copyright` are defined in two separate PropertyGroups. The second set overrides the first. The `Copyright` values differ: line 6 uses `&` while line 24 uses `&amp;` (double-encoded XML entity producing literal `&` in output). +- **Why it matters:** The double-encoded entity means the copyright string in the test assembly metadata will contain a literal `&` instead of `&`. This is a metadata bug. +- **Confidence:** HIGH + +### MEDIUM Severity + +#### M1. `` should be `` in IComparable XML doc +- **Location:** `src/NetDuid/Duid.IComparable.cs:13` +- **What:** Uses `` (singular) instead of the standard `` XML doc tag. +- **Why it matters:** `` is not a standard C# XML doc tag. Tools may not render it correctly. +- **Confidence:** HIGH + +#### M2. `HexCharToUpper` uses obscure ASCII arithmetic +- **Location:** `src/NetDuid/Duid.Factory.cs:171` +- **What:** `input - ' '` (subtracting space character = 32) to convert lowercase to uppercase. While correct for ASCII, it's obscure. +- **Why it matters:** Readability. `char.ToUpper(input)` or `input - 32` with a comment would be clearer. +- **Confidence:** MEDIUM + +#### M3. `IFormattable.ToString` uses division/modulo instead of bit shifts +- **Location:** `src/NetDuid/Duid.IFormattable.cs:148-149` +- **What:** `@byte / 16` and `@byte % 16` instead of `@byte >> 4` and `@byte & 0x0F`. +- **Why it matters:** Minor performance. The JIT may optimize this, but bit shifts are idiomatic for nibble extraction. +- **Confidence:** MEDIUM + +#### M4. `_lazyHashCode` declared in IEquatable partial, initialized in Duid.cs and ISerializable partial +- **Location:** `src/NetDuid/Duid.IEquatable.cs:14`, `Duid.cs:44`, `Duid.ISerializable.cs:27` +- **What:** The field `_lazyHashCode` is declared in one partial file but initialized in two different files (the main constructor and the serialization constructor). +- **Why it matters:** Cross-file dependency is not obvious. A developer modifying one partial file may not realize another file depends on it. If a new constructor is added and forgets to initialize `_lazyHashCode`, it will throw `NullReferenceException` on `GetHashCode()`. +- **Confidence:** HIGH + +#### M5. `Duid` is a reference type with value semantics +- **Location:** `src/NetDuid/Duid.cs:15` +- **What:** `Duid` is a `sealed class` implementing `IEquatable`, `IComparable`, with operator overloads for `==`, `!=`, `<`, `>`, `<=`, `>=`. Null has special handling in every comparison and equality method. +- **Why it matters:** Reference types with value semantics require null checks everywhere. A `readonly record struct` would eliminate null handling, provide value equality automatically, and be more efficient. However, changing to a struct would be a **major breaking change** (boxing behavior, default values, serialization). +- **Confidence:** HIGH (design concern, not a bug) +- **Interaction:** Affects every interface implementation and all operators. + +#### M6. `Type` property is heuristic, not authoritative +- **Location:** `src/NetDuid/Duid.cs:31`, `Duid.cs:75-101` +- **What:** `DuidType` is determined by the first 2 bytes, but the XML doc warns "it is possible for the DUID contents to disagree with the type specification." A DUID with type code `0x0001` but only 2 bytes total would be classified as `LinkLayerPlusTime` despite being structurally invalid for that type. +- **Why it matters:** Consumers may rely on `Type` to make decisions (e.g., parsing the rest of the DUID structure). The heuristic nature is documented but could lead to bugs. +- **Confidence:** MEDIUM + +#### M7. Undelimited hex regex pattern inconsistency with delimited pattern +- **Location:** `src/NetDuid/DuidRegexSource.cs:24-30` +- **What:** The delimited pattern uses `[0-9a-fA-F]` (explicit both cases) without `IgnoreCase`. The undelimited pattern uses `[0-9a-f]` with `RegexOptions.IgnoreCase` (for source gen) or `RegexOptions.Compiled | RegexOptions.IgnoreCase` (for legacy). This is intentional (documented) but creates two different approaches to case handling in the same class. +- **Why it matters:** Maintenance burden. If someone changes one pattern without understanding the other's case handling, they could introduce a bug. +- **Confidence:** MEDIUM + +#### M8. `DelimitedStringToBytes` over-allocates then copies +- **Location:** `src/NetDuid/Duid.Factory.cs:117-158` +- **What:** Calculates `maxBytesLength`, allocates that array, parses into it, then copies to a correctly-sized array via `Buffer.BlockCopy`. The `maxBytesLength` formula `(str.Length + delimiterLength) / (1 + delimiterLength)` can over-allocate. +- **Why it matters:** Extra allocation and copy on every parse of delimited strings. Could use `List` or `Span` with a stackalloc for small inputs. +- **Confidence:** MEDIUM + +### LOW Severity + +#### L1. Typo in `DuidType.Undefined` XML doc +- **Location:** `src/NetDuid/DuidType.cs:9` +- **What:** "not specified in RFC8415 of RFC6355" should be "or" not "of". +- **Confidence:** HIGH (clear typo) + +#### L2. Unreachable code comment in `GetDuidType()` +- **Location:** `src/NetDuid/Duid.cs:81` +- **What:** Comment says "this is in theory unreachable given all current construction enforces a minimum of 3 bytes." The `< 2` check is defensive but unreachable. +- **Why it matters:** Not a bug, but dead code. If the minimum length validation changes, this branch becomes reachable. +- **Confidence:** HIGH + +#### L3. `DuidRegexSource` naming +- **Location:** `src/NetDuid/DuidRegexSource.cs:12` +- **What:** Class name `DuidRegexSource` is unusual. Could be `DuidRegex` or `DuidRegexPatterns`. +- **Confidence:** LOW (subjective) + +--- + +## Findings: Test Project + +### MEDIUM Severity + +#### T1. `Equal_Object_NotDuid_Throws_ArgumentException_Test` is misnamed +- **Location:** `src/NetDuid.Tests/Duid.IEquatableTests.cs` +- **What:** Test name says "Throws_ArgumentException" but asserts `Assert.False(result)`. `Equals(object)` returns `false` for non-Duid types, it does not throw. +- **Confidence:** HIGH + +#### T2. `ISerializable` tests gated entirely to NET48 +- **Location:** `src/NetDuid.Tests/Duid.ISerializableTests.cs` +- **What:** Entire file wrapped in `#if NET48`. BinaryFormatter works on .NET 8+ with `EnableUnsafeBinaryFormatterSerialization` (which is set). Serialization is only tested on one TFM. +- **Confidence:** MEDIUM + +#### T3. No test with non-array `IEnumerable` +- **Location:** Test coverage gap +- **What:** Constructor accepts `IEnumerable` but only `byte[]` is tested. No test with `List`, lazy enumerable, etc. +- **Confidence:** MEDIUM + +#### T4. Operator tests derive expected values from `CompareTo` +- **Location:** `src/NetDuid.Tests/Duid.OperatorsTests.cs` +- **What:** Test data uses `CompareTo` to derive expected comparison results. If `CompareTo` has a bug, operator tests inherit it (circular dependency). +- **Confidence:** MEDIUM + +#### T5. No test for hash code inequality or stability +- **Location:** Test coverage gap +- **What:** Only tests that equal DUIDs produce equal hash codes. No test for different DUIDs producing different hash codes, or same instance returning same hash code on multiple calls. +- **Confidence:** LOW + +--- + +## Findings: Build/CI Configuration + +### HIGH Severity + +#### C1. CI build does not treat warnings as errors +- **Location:** `.github/workflows/build.yml` +- **What:** Build step does not specify `--configuration Release`. `TreatWarningsAsErrors` is only active in Release (per `Directory.Build.props:23`). Analyzer warnings pass silently in CI. +- **Why it matters:** The publish workflow builds in Release and would catch these, but by then a broken commit may already be on `main`. +- **Confidence:** HIGH + +#### C2. Publish workflow does not run tests +- **Location:** `.github/workflows/publish.yml` +- **What:** Builds, packs, and publishes without running any tests. No `needs:` dependency on the build workflow. +- **Why it matters:** A tag pushed on a commit that hasn't passed CI could publish a broken package. +- **Confidence:** HIGH + +### MEDIUM Severity + +#### C3. `CONTRIBUTING.md` references wrong path for `global.json` +- **Location:** `CONTRIBUTING.md` +- **What:** Says "see `src/global.json`" but the file is at the repo root. +- **Confidence:** HIGH + +#### C4. `.runsettings` may be unused in CI +- **Location:** `src/.runsettings` +- **What:** Configures `XPlat Code Coverage` (coverlet), but CI uses `dotnet-coverage` (Microsoft tool). The `.runsettings` file is not referenced by any CI command. +- **Confidence:** MEDIUM + +#### C5. Coverage collected only for net10.0 on Ubuntu +- **Location:** `.github/workflows/build.yml` +- **What:** net8.0 and net9.0 test runs on Ubuntu do not collect coverage. Windows tests (including net48) also do not collect coverage. +- **Confidence:** HIGH + +#### C6. `AGENTS.md` contains stale information about smoke tests +- **Location:** `AGENTS.md` +- **What:** Claims smoketests reference `NetDuid.Comparers`, `NetDuid.Converters`, `NetDuid.Math`, `NetDuid.Utilities` namespaces. The actual smoke test code only uses `NetDuid` namespace with `Duid` and `DuidType`. +- **Confidence:** HIGH + +### LOW Severity + +#### C7. Benchmark project directory exists but is empty +- **Location:** `src/NetDuid.Benchmarks/` +- **What:** Directory exists with build artifacts but no `.csproj` or source files. `run-benchmarks.sh` references non-existent `NetDuid.Benchmarks` project. +- **Confidence:** HIGH + +#### C8. Smoke test data inconsistency (cosmetic) +- **Location:** `smoketests/SmokeTests/Program.cs` +- **What:** `llTimeStringNoDelim` has 15 bytes worth of hex but `llTimeBytes` has 14 bytes. Does not cause test failure because only `DuidType` is checked, not byte equality. +- **Confidence:** MEDIUM + +--- + +## Findings: Cross-cutting / Interaction Concerns + +### X1. Partial class field dependency chain +- **What:** `_lazyHashCode` (declared in `Duid.IEquatable.cs`) is initialized in `Duid.cs` constructor and `Duid.ISerializable.cs` deserialization constructor. `_duidBytes` (declared in `Duid.cs`) is accessed in all partial files. `Type` (declared in `Duid.cs`) is set in both constructors. +- **Why it matters:** Adding a new constructor requires knowing to initialize both `_duidBytes`/`Type` (via `ConstructWithBytesGuard` + `GetDuidType()`) AND `_lazyHashCode`. No compile-time enforcement. +- **Confidence:** HIGH + +### X2. `GetBytes()` vs direct field access inconsistency +- **What:** `IEquatable.Equals` calls `other.GetBytes()` (allocating), while `IComparable.CompareTo` accesses `other._duidBytes` directly. Both are in the same partial class and have access to the private field. +- **Why it matters:** Inconsistent access patterns. The `Equals` path allocates unnecessarily. +- **Confidence:** HIGH + +### X3. Serialization does not preserve `Type` directly +- **What:** `GetObjectData` serializes `_duidBytes` and `SerializableVersion`. The deserialization constructor re-derives `Type` from the bytes. If the serialization format changes, the `Type` derivation must remain consistent. +- **Why it matters:** Not a bug currently, but a fragility in the serialization contract. +- **Confidence:** MEDIUM + +--- + +## Suggested Skills for Fix Implementation + +When implementing fixes for these issues, the following skills may be relevant: + +1. **tdd** / **test-driven-development** — For implementing fixes test-first +2. **writing-mstest-tests** — NOT applicable (this project uses xUnit v3) +3. **code-testing-agent** — For generating additional test coverage +4. **test-anti-patterns** — For auditing the test naming issue (T1) +5. **msbuild-antipatterns** — For fixing csproj issues (H1, H3, H4) +6. **directory-build-organization** — For fixing Directory.Build.props TFM issue (H3) +7. **item-management** — For fixing duplicate PackageReference (H1) +8. **property-patterns** — For fixing duplicate metadata properties (H4) +9. **gen-dotnet-docs-comments** — For fixing XML doc issues (M1, L1) +10. **analyzing-dotnet-performance** — For the Equals allocation issue (H2) +11. **github-actions-writer** — For CI/CD fixes (C1, C2) +12. **split-type-to-partials** — NOT needed (already split correctly) + +--- + +## Fix Priority Order (Most Critical to Least) + +1. **H1** — Remove unconditional `Microsoft.Bcl.HashCode` reference (add condition for netstandard2.0 only) +2. **H2** — Fix `Equals(Duid)` to access `_duidBytes` directly instead of via `GetBytes()` +3. **H4** — Fix double-encoded XML entity in test csproj `Copyright` +4. **C1** — Add `--configuration Release` to CI build workflow +5. **C2** — Add test step or `needs:` dependency to publish workflow +6. **H3** — Remove or clarify dead `TargetFrameworks` in `Directory.Build.props` +7. **M4** — Document or consolidate `_lazyHashCode` initialization pattern +8. **M1** — Fix `` to `` in IComparable XML doc +9. **T1** — Fix misnamed test method +10. **L1** — Fix typo in DuidType XML doc +11. Remaining MEDIUM and LOW items diff --git a/.scratch/future-design-duid-struct-conversion.md b/.scratch/future-design-duid-struct-conversion.md new file mode 100644 index 0000000..3cda88c --- /dev/null +++ b/.scratch/future-design-duid-struct-conversion.md @@ -0,0 +1,239 @@ +# Future Design: Convert `Duid` from `class` to `readonly record struct` + +**Date:** 2026-07-05 +**Status:** Deferred — requires separate design discussion and major version bump +**Origin:** Code review finding M5 in `code-review-handoff.md` + +--- + +## Current Design + +`Duid` is a `sealed partial class` implementing: +- `IEquatable` +- `IComparable`, `IComparable` +- `IFormattable` +- `ISerializable` +- `IParsable` (on NET7_0_OR_GREATER) + +All interface implementations include explicit null checks. The type uses value semantics (equality based on byte contents, not reference identity) but is a reference type. + +### Current Implementation Characteristics + +- **Null handling:** Every comparison, equality, and operator method checks for null +- **Field storage:** `_duidBytes` (byte array), `Type` (property), `_lazyHashCode` (Lazy) +- **Partial class structure:** Split across 8 files by interface/concern +- **Constructors:** Public constructor from `IEnumerable`, private deserialization constructor +- **Operators:** `==`, `!=`, `<`, `<=`, `>`, `>=` all handle null operands + +--- + +## Proposed Conversion: `readonly record struct` + +### Benefits + +1. **Eliminate null checks:** Structs cannot be null (unless `Nullable`). All null-check boilerplate in equality, comparison, and operators disappears. + +2. **Automatic value equality:** `record struct` provides synthesized `Equals`, `GetHashCode`, and `==`/`!=` operators based on field values. Could eliminate custom implementations in `Duid.IEquatable.cs` and `Duid.Operators.cs`. + +3. **Performance:** + - No heap allocation for `Duid` instances (stack-only) + - No null-check overhead in comparisons + - Better cache locality + - `GetHashCode()` could be computed eagerly in constructor instead of lazily + +4. **Reduced code:** + - Remove null checks from `CompareTo`, `Equals`, all 6 operators + - Potentially remove custom `Equals`/`GetHashCode` implementations (use synthesized versions) + - Remove `operator ==` and `operator !=` if record struct synthesis is sufficient + +5. **Immutability:** `readonly struct` enforces immutability at the type level. Current `_duidBytes` is `readonly` but the array contents are mutable (though the constructor copies the input). + +### Breaking Changes + +#### 1. **Null semantics (MAJOR)** + +**Before:** +```csharp +Duid d = null; // Valid +if (d == null) { ... } // Valid +Duid? nullable = null; // Valid (reference type) +``` + +**After:** +```csharp +Duid d = null; // Compile error +Duid? nullable = null; // Valid (Nullable) +if (nullable is null) { ... } // Valid +``` + +**Impact:** Any code that checks `d == null` or `d is null` will break. Any code that passes `null` as a `Duid` parameter will break. This is a **source-level breaking change**. + +**Binary compatibility:** Existing compiled assemblies that pass `null` will throw `NullReferenceException` at runtime when the struct is dereferenced. + +#### 2. **Default value (MAJOR)** + +**Before:** +```csharp +Duid d; // d is null +Duid[] arr = new Duid[10]; // All elements are null +``` + +**After:** +```csharp +Duid d; // d is default(Duid) with _duidBytes = null, Type = Undefined +Duid[] arr = new Duid[10]; // All elements are default(Duid) +``` + +**Impact:** Code that relies on uninitialized `Duid` being `null` will break. Arrays of `Duid` will no longer contain nulls. + +#### 3. **Boxing behavior (MODERATE)** + +**Before:** `Duid` is a reference type. Casting to `object` does not box. + +**After:** `Duid` is a value type. Casting to `object` boxes the struct (allocates on heap). + +**Impact:** Performance regression if `Duid` is frequently cast to `object` (e.g., stored in non-generic collections, used with reflection). However, this is unlikely for a DUID type. + +#### 4. **Serialization (MAJOR)** + +**Current:** Implements `ISerializable` with custom `GetObjectData` and deserialization constructor. + +**After:** `ISerializable` is still supported on structs, but: +- Binary serialization of structs has different semantics +- The deserialization constructor must be adjusted (structs cannot have parameterless constructors, but `ISerializable` deserialization uses a special constructor) +- Need to verify `BinaryFormatter` works correctly with `readonly record struct` + +**Impact:** Existing serialized data may not deserialize correctly. Need to test round-trip compatibility. + +#### 5. **Operator behavior with null (MODERATE)** + +**Before:** +```csharp +Duid a = null; +Duid b = new Duid(bytes); +bool result = a < b; // false (null is less than non-null) +``` + +**After:** +```csharp +Duid? a = null; +Duid b = new Duid(bytes); +bool result = a < b; // Compile error: cannot compare Nullable with Duid +bool result2 = a.Value < b; // Throws InvalidOperationException if a is null +``` + +**Impact:** All comparison operators need to be rethought for `Nullable`. The current "null is less than any non-null value" semantics cannot be expressed with operator overloads on structs. + +#### 6. **Record struct equality synthesis (MODERATE)** + +**Current:** Custom `Equals(Duid)` compares `_duidBytes` using `SequenceEqual`. + +**After:** `record struct` synthesizes `Equals` based on all fields. If we keep `_duidBytes`, `Type`, and `_lazyHashCode` as fields, the synthesized equality will compare all three. + +**Problem:** `_lazyHashCode` is a `Lazy` which is a reference type. Two `Duid` instances with the same bytes but different `_lazyHashCode` instances will not be equal under synthesized equality. + +**Solution:** Mark `_lazyHashCode` with `[field: NonSerialized]` or compute hash code eagerly. Or implement custom `Equals` to exclude `_lazyHashCode`. + +#### 7. **`IParsable` and `ISerializable` on structs (LOW)** + +Both interfaces are supported on structs, but: +- `IParsable.TryParse` must return `bool` and output `T` (works fine) +- `ISerializable` deserialization constructor must be `private` (works fine) + +**Impact:** Minimal, but need to verify both interfaces work correctly with `readonly record struct`. + +#### 8. **Partial class structure (LOW)** + +**Current:** 8 partial class files. + +**After:** Can still use partial struct, but the syntax is `partial struct` not `partial class`. Need to update all file headers. + +**Impact:** Mechanical change, but affects all 8 files. + +--- + +## Migration Strategy + +### Option 1: Major version bump (breaking change) + +- Convert `Duid` to `readonly record struct` +- Update all null checks in tests and smoke tests +- Update serialization tests +- Document breaking changes in release notes +- Publish as v2.0.0 + +### Option 2: Introduce new type alongside (non-breaking) + +- Keep `Duid` as `class` +- Introduce `DuidStruct` as `readonly record struct` +- Deprecate `Duid` class +- Provide conversion methods: `Duid.ToStruct()` and `DuidStruct.ToClass()` +- Migrate consumers over time +- Eventually remove `Duid` class in v3.0.0 + +### Option 3: Hybrid approach (complex) + +- Convert `Duid` to `readonly record struct` +- Provide extension methods for null-like semantics: `Duid?` with custom operators +- This is complex and may not be worth the effort + +--- + +## Open Questions + +1. **Should `Type` be a field or computed property?** + - Current: Property set in constructor + - Struct: Could be computed on-demand from `_duidBytes[0..1]` + - Trade-off: Memory vs. computation + +2. **Should hash code be eager or lazy?** + - Current: Lazy (computed on first `GetHashCode()` call) + - Struct: Could compute in constructor (no allocation overhead for `Lazy`) + - Trade-off: Constructor cost vs. repeated computation + +3. **Should we keep `ISerializable`?** + - BinaryFormatter is deprecated and discouraged + - Struct serialization has different semantics + - Consider removing `ISerializable` in v2.0.0 + +4. **Should we keep all operator overloads?** + - `<`, `<=`, `>`, `>=` with null semantics are awkward for structs + - Consider removing comparison operators and requiring explicit `CompareTo()` + - Or provide operators only for non-nullable `Duid` + +5. **Should `_duidBytes` be a fixed-size buffer?** + - RFC 8415 says DUIDs are 3-130 bytes + - Could use `fixed byte _duidBytes[130]` with a length field + - Trade-off: Stack allocation (fast) vs. wasted space (most DUIDs are much smaller than 130 bytes) + +6. **Should we use `ReadOnlyMemory` instead of `byte[]`?** + - Would allow zero-copy slicing of larger buffers + - Trade-off: More complex, may not be needed for typical DUID use cases + +--- + +## Implementation Checklist (for future session) + +- [ ] Decide on migration strategy (Option 1, 2, or 3) +- [ ] Resolve open questions above +- [ ] Convert `Duid.cs` to `readonly record struct` +- [ ] Update all 7 partial files (`Duid.*.cs`) +- [ ] Update test project (remove null checks, update serialization tests) +- [ ] Update smoke tests +- [ ] Verify `ISerializable` round-trip works +- [ ] Verify `IParsable` works +- [ ] Benchmark performance (allocation, equality, comparison) +- [ ] Update XML documentation +- [ ] Write migration guide for consumers +- [ ] Update `AGENTS.md` with new architecture +- [ ] Bump major version + +--- + +## References + +- Original finding: `.scratch/code-review-handoff.md` (finding M5) +- Current implementation: `src/NetDuid/Duid.cs` and partial files +- Test coverage: `src/NetDuid.Tests/DuidTests.cs` and partial files +- RFC 8415: DHCP Unique Identifier specification +- RFC 6355: UUID-based DUID specification diff --git a/.scratch/remediation-plan.md b/.scratch/remediation-plan.md new file mode 100644 index 0000000..eb2ca52 --- /dev/null +++ b/.scratch/remediation-plan.md @@ -0,0 +1,261 @@ +# NetDuid Remediation Plan + +**Date:** 2026-07-05 +**Source:** `.scratch/code-review-handoff.md` +**Status:** Ready for execution + +--- + +## Executive Summary + +This plan addresses 24 findings from the code review, organized into 5 batches that can be parallelized using git worktrees. Batches 1-4 have no file overlap and can execute simultaneously. Batch 5 depends on Batch 2 (both touch `Duid.Factory.cs`). + +**Deferred items** (separate documents or out of scope): +- M5: Struct conversion → `.scratch/future-design-duid-struct-conversion.md` +- M6, M7, L2: Documentation-only, already documented +- T2: ISerializable NET48 gating is correct behavior +- C7: Empty benchmark directory kept for future use + +--- + +## Batching Strategy + +### Batch 1: Configuration & CI Fixes +**Worktree:** `fix/config-and-ci` +**Approach:** Mechanical edits, no TDD +**Files:** `NetDuid.csproj`, `Directory.Build.props`, `NetDuid.Tests.csproj`, `build.yml`, `publish.yml`, `CONTRIBUTING.md`, `AGENTS.md`, `.runsettings`, `smoketests/SmokeTests/Program.cs` + +| ID | Issue | Fix | Verification | +|---|---|---|---| +| H1 | Unconditional `Microsoft.Bcl.HashCode` | Remove line 85, change line 84 condition to `netstandard2.0` | `dotnet restore` succeeds, `dotnet build` succeeds for all TFMs | +| H3 | Dead `TargetFrameworks` in Directory.Build.props | Remove line 3 (or comment explaining it's overridden) | `dotnet build` succeeds | +| H4 | Double-encoded XML entity in test csproj | Fix `&amp;` → `&` on line 24 | `dotnet build` succeeds, verify assembly metadata | +| C1 | CI build doesn't treat warnings as errors | Add `--configuration Release` to `build.yml` build step | CI passes | +| C2 | Publish workflow doesn't run tests | Add `needs: [build]` to `publish.yml` | CI passes | +| C3 | Wrong `global.json` path in CONTRIBUTING.md | Change `src/global.json` → `global.json` | Manual verification | +| C4 | `.runsettings` possibly unused | Add comment explaining it's for local dev, or remove if unused | Manual verification | +| C5 | Coverage only for net10.0 | Expand `build.yml` to collect coverage for net8.0 and net9.0 on Ubuntu | CI produces coverage for all Ubuntu TFMs | +| C6 | Stale smoke test info in AGENTS.md | Remove claim about non-existent namespaces | Manual verification | +| C8 | Smoke test hex string mismatch | Fix `llTimeStringNoDelim` to match `llTimeBytes` (14 bytes, not 15) | Smoke tests pass | + +**Execution order:** All items are independent, can be done in any order within the batch. + +--- + +### Batch 2: Trivial Production Fixes +**Worktree:** `fix/trivial-production` +**Approach:** Readability improvements, comment out old code and add improved versions alongside +**Files:** `Duid.IComparable.cs`, `DuidType.cs`, `Duid.Factory.cs`, `Duid.IFormattable.cs`, `DuidRegexSource.cs` + +| ID | Issue | Fix | Verification | +|---|---|---|---| +| M1 | `` tag | Change to `` on line 13 of `Duid.IComparable.cs` | `dotnet build` succeeds, XML doc validates | +| L1 | Typo in DuidType XML doc | Change "of RFC6355" → "or RFC6355" on line 9 of `DuidType.cs` | `dotnet build` succeeds | +| M2 | Obscure `HexCharToUpper` | Comment out lines 167-175, add improved version with clear comment: `// Improved: use char.ToUpperInvariant for clarity` | Existing Parse/TryParse tests pass | +| M3 | Division/modulo in IFormattable | Comment out lines 148-149, add bit shift version: `@byte >> 4` and `@byte & 0x0F` with comment: `// Improved: bit shifts are idiomatic for nibble extraction` | Existing IFormattable tests pass | +| L3 | `DuidRegexSource` naming | Rename class to `DuidRegexPatterns` (clearer intent), update all references in `Duid.Factory.cs` | `dotnet build` succeeds, Parse/TryParse tests pass | + +**Execution order:** M2 and M3 both touch `Duid.Factory.cs`, so do them together. L3 requires updating references in `Duid.Factory.cs`, so do it after M2/M3. + +**Note:** For M2 and M3, the old code is commented out (not deleted) so both versions are visible for comparison. After verification, the commented code can be removed in a follow-up commit. + +--- + +### Batch 3: Equals Allocation & Lazy HashCode +**Worktree:** `fix/equals-and-hashcode` +**Approach:** TDD — existing tests verify correctness, add allocation test +**Files:** `Duid.IEquatable.cs`, `Duid.cs`, `Duid.ISerializable.cs` + +| ID | Issue | Fix | Verification | +|---|---|---|---| +| H2 | `Equals(Duid)` allocates via `GetBytes()` | Change line 38-39 in `Duid.IEquatable.cs` to access `other._duidBytes` directly instead of `other.GetBytes()` | Existing `IEquatable` tests pass | +| X2 | Inconsistent field access pattern | Same fix as H2 (makes `Equals` consistent with `CompareTo`) | Existing `IComparable` tests pass | +| M4 | `_lazyHashCode` cross-file dependency | Extract initialization into private helper method `InitializeLazyHashCode()` called by both constructors (main and deserialization) | Existing `GetHashCode` tests pass | + +**TDD approach:** +1. Run existing tests to establish baseline (all should pass) +2. Optionally add a benchmark or allocation test to verify the improvement (e.g., using `BenchmarkDotNet` or manual allocation counting) +3. Refactor `Equals(Duid)` to use `other._duidBytes` directly +4. Extract `_lazyHashCode` initialization into helper method +5. Run tests again to verify no regression + +**Execution order:** H2/X2 first (simple refactor), then M4 (helper method extraction). + +**Breaking changes:** None. This is a behavior-preserving optimization. + +--- + +### Batch 4: Test Improvements +**Worktree:** `fix/test-improvements` +**Approach:** TDD — add new tests, fix existing test issues +**Files:** `Duid.IEquatableTests.cs`, `DuidTests.cs`, `Duid.OperatorsTests.cs` + +| ID | Issue | Fix | Verification | +|---|---|---|---| +| T1 | Misnamed test | Rename `Equal_Object_NotDuid_Throws_ArgumentException_Test` → `Equal_Object_NotDuid_Returns_False_Test` | Test still passes | +| T3 | No test with non-array `IEnumerable` | Add test in `DuidTests.cs` that constructs `Duid` from `List` and verifies correctness | New test passes | +| T4 | Operator tests derive expected from `CompareTo` | Refactor test data generation in `Duid.OperatorsTests.cs` to use independently computed expected values (e.g., manual byte comparison) | Existing operator tests still pass | +| T5 | No hash code stability/inequality test | Add tests in `Duid.IEquatableTests.cs`: (a) same instance returns same hash code on multiple calls, (b) different DUIDs produce different hash codes (probabilistic, test a few known-different cases) | New tests pass | + +**TDD approach:** +1. Write failing tests first (T3, T5 — new tests) +2. Verify they fail (or pass if the implementation is already correct) +3. For T4, refactor test data generation to be independent of `CompareTo` +4. For T1, rename the test method +5. Run all tests to verify + +**Execution order:** T1 (rename) first, then T3/T5 (new tests), then T4 (refactor test data). + +**Breaking changes:** None. Test-only changes. + +--- + +### Batch 5: Parse Performance Optimization +**Worktree:** `fix/parse-performance` +**Depends on:** Batch 2 (both touch `Duid.Factory.cs`) +**Approach:** TDD + benchmarks +**Files:** `Duid.Factory.cs` + +| ID | Issue | Fix | Verification | +|---|---|---|---| +| M8 | `DelimitedStringToBytes` over-allocates | Refactor to use `List` or `Span` with stackalloc for small inputs, eliminating the over-allocation and `Buffer.BlockCopy` | Existing Parse/TryParse tests pass, benchmark shows improvement | + +**TDD approach:** +1. Run existing Parse/TryParse tests to establish baseline +2. Optionally add a benchmark using `BenchmarkDotNet` (once the benchmark project is scaffolded) to measure allocation and throughput +3. Refactor `DelimitedStringToBytes` to use a more efficient approach: + - Option A: Use `List` (simpler, still allocates but less over-allocation) + - Option B: Use `Span` with `stackalloc` for small inputs (≤128 bytes), fall back to array for larger +4. Run tests again to verify no regression +5. Compare benchmark results + +**Execution order:** Must wait for Batch 2 to merge (both touch `Duid.Factory.cs`). + +**Breaking changes:** None. This is a behavior-preserving optimization. + +--- + +## Git Worktree Parallelization Strategy + +### Phase 1: Parallel Execution (Batches 1-4) + +Create 4 worktrees, one for each batch: + +```bash +# Batch 1: Configuration & CI +git worktree add ../netduid-config-fixes fix/config-and-ci +# Batch 2: Trivial production +git worktree add ../netduid-trivial-fixes fix/trivial-production +# Batch 3: Equals & hashcode +git worktree add ../netduid-equals-fixes fix/equals-and-hashcode +# Batch 4: Test improvements +git worktree add ../netduid-test-fixes fix/test-improvements +``` + +**File overlap analysis:** +- Batch 1: Config files, CI files, docs, smoke tests +- Batch 2: `Duid.IComparable.cs`, `DuidType.cs`, `Duid.Factory.cs`, `Duid.IFormattable.cs`, `DuidRegexSource.cs` +- Batch 3: `Duid.IEquatable.cs`, `Duid.cs`, `Duid.ISerializable.cs` +- Batch 4: Test files only + +**No overlap** — all 4 batches can execute simultaneously. + +### Phase 2: Sequential Execution (Batch 5) + +After Batch 2 merges: + +```bash +# Batch 5: Parse performance (depends on Batch 2) +git worktree add ../netduid-perf-fixes fix/parse-performance +``` + +**File overlap:** Batch 5 touches `Duid.Factory.cs`, which Batch 2 also touches. Must wait for Batch 2 to merge first. + +### Merge Strategy + +After all batches complete: + +1. **Option A: Sequential merge** (safer) + - Merge Batch 1 → main + - Merge Batch 2 → main + - Merge Batch 3 → main + - Merge Batch 4 → main + - Merge Batch 5 → main (after Batch 2) + - Resolve any merge conflicts as they arise + +2. **Option B: Octopus merge** (faster, riskier) + - Merge all 5 branches into main in one operation + - Risk: conflicts may be harder to resolve + - Only viable if all batches are well-tested and independent + +**Recommendation:** Use Option A (sequential merge) for safety. Each merge should be followed by a full test run. + +--- + +## Verification Strategy + +### Per-Batch Verification + +| Batch | Build | Test | Format | Coverage | +|---|---|---|---|---| +| 1 | `dotnet build --configuration Release` | `dotnet test --configuration Release` | N/A | N/A | +| 2 | `dotnet build` | `dotnet test` | `dotnet format style && dotnet format analyzers && dotnet csharpier format .` | N/A | +| 3 | `dotnet build` | `dotnet test` | `dotnet format style && dotnet format analyzers && dotnet csharpier format .` | N/A | +| 4 | `dotnet build` | `dotnet test` | `dotnet format style && dotnet format analyzers && dotnet csharpier format .` | N/A | +| 5 | `dotnet build` | `dotnet test` | `dotnet format style && dotnet format analyzers && dotnet csharpier format .` | Optional benchmark | + +### Post-Merge Verification + +After all batches merge: + +```bash +# Full build and test +dotnet build --configuration Release +dotnet test --configuration Release + +# Format check +dotnet tool restore +dotnet format style --verify-no-changes +dotnet format analyzers --verify-no-changes +dotnet csharpier format --check . + +# Smoke tests +./smoketests/run-smoke-tests.sh +``` + +--- + +## Risk Assessment + +| Batch | Risk | Mitigation | +|---|---|---| +| 1 | Low — config changes only | Full build + test after each change | +| 2 | Low — readability improvements, old code commented | Existing tests verify behavior preservation | +| 3 | Medium — changes to core equality/comparison logic | TDD approach, existing tests verify correctness | +| 4 | Low — test-only changes | New tests verify themselves | +| 5 | Medium — performance optimization in parsing | TDD approach, existing tests verify behavior preservation | + +--- + +## Suggested Skills for Implementation + +| Batch | Skills to invoke | +|---|---| +| 1 | `msbuild-antipatterns`, `directory-build-organization`, `property-patterns`, `github-actions-writer` | +| 2 | `gen-dotnet-docs-comments` | +| 3 | `test-driven-development`, `analyzing-dotnet-performance` | +| 4 | `test-driven-development`, `code-testing-agent` | +| 5 | `test-driven-development`, `analyzing-dotnet-performance`, `microbenchmarking` | + +--- + +## Summary + +- **5 batches** of work, organized by file overlap and dependencies +- **4 batches can run in parallel** (Batches 1-4) +- **1 batch is sequential** (Batch 5, depends on Batch 2) +- **TDD approach** for Batches 3-5 (production code changes) +- **Mechanical edits** for Batches 1-2 (no TDD needed) +- **Deferred items** documented in separate files + +**Total estimated effort:** 2-3 days for a single developer, or 1 day with 4 parallel worktrees. diff --git a/API_REFERENCE.md b/API_REFERENCE.md new file mode 100644 index 0000000..c01e90a --- /dev/null +++ b/API_REFERENCE.md @@ -0,0 +1,352 @@ +# API Reference + +## `NetDuid.Duid` (sealed class) + +The primary type of the library. Represents a DHCP Unique Identifier (DUID) as an immutable byte array. + +Implements: `IEquatable`, `IComparable`, `IComparable`, `IFormattable`, `ISerializable`, and (on .NET 7+) `IParsable`. On .NET 8+ it additionally implements `ISpanFormattable` and `IUtf8SpanFormattable`. + +--- + +### Construction + +```csharp +public Duid(IEnumerable bytes) +``` + +Creates a DUID from any enumerable of bytes. Accepts `byte[]`, `List`, `Memory`, or any other `IEnumerable`. + +```csharp +// from byte array +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02, 0x03 }); + +// from List +var duid = new Duid(new List { 0x00, 0x01, 0x02, 0x03 }); +``` + +**Validation rules:** +| Condition | Exception | +|-----------|-----------| +| `null` | `ArgumentNullException` | +| Empty (0 bytes) | `ArgumentException` | +| 1–2 bytes | `ArgumentException` ("less than 3 octets") | +| 3–130 bytes | Success | +| 131+ bytes | `ArgumentException` ("more than 130 octets") | + +#### `Duid(ReadOnlySpan)` (.NET 8+) + +```csharp +public Duid(ReadOnlySpan bytes) +``` + +Creates a DUID from a read-only span of bytes. This constructor is useful when working with stack-allocated data, `ArrayPool`, or other span-based APIs. The bytes are copied defensively into an internal array. + +```csharp +// from stack-allocated span +Span stackBytes = stackalloc byte[] { 0x00, 0x01, 0x02, 0x03 }; +var duid = new Duid((ReadOnlySpan)stackBytes); +``` + +Same validation rules as the `IEnumerable` constructor. Throws `ArgumentException` on invalid input. + +--- + +### Properties + +#### `Type` + +```csharp +public DuidType Type { get; } +``` + +A heuristic type label derived from the first two bytes (big-endian type code). See `DuidType` below. + +> **Important:** The RFC advises treating DUIDs as opaque byte arrays — this property is a best guess based on the type code, not a definitive classification. The byte content may disagree with the type label. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0xAB, 0xCD }); +Console.WriteLine(duid.Type); // LinkLayerPlusTime +``` + +#### `Length` + +```csharp +public int Length { get; } +``` + +The number of octets in the DUID (always between 3 and 130 inclusive). This is a zero-allocation alternative to `GetBytes().Count`. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02, 0x03 }); +Console.WriteLine(duid.Length); // 4 +``` + +#### `Span` (.NET 8+) + +```csharp +public ReadOnlySpan Span { get; } +``` + +Returns the DUID bytes as a read-only span. Provides zero-allocation, stack-friendly access to the underlying byte data. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02, 0x03 }); +ReadOnlySpan span = duid.Span; +Console.WriteLine(span.Length); // 4 +Console.WriteLine(span[0]); // 0 +Console.WriteLine(span.SequenceEqual(new byte[] { 0x00, 0x01, 0x02, 0x03 })); // True +``` + +#### `Memory` (.NET 8+) + +```csharp +public ReadOnlyMemory Memory { get; } +``` + +Returns the DUID bytes as a read-only memory region. Useful for interop scenarios where `ReadOnlySpan` cannot be used (e.g., async operations, `IAsyncEnumerable`). + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02, 0x03 }); +ReadOnlyMemory memory = duid.Memory; +Console.WriteLine(memory.Length); // 4 +``` + +--- + +### Methods + +#### `GetBytes()` + +```csharp +public IReadOnlyCollection GetBytes() +``` + +Returns a read-only view of the underlying bytes. The runtime type is `ReadOnlyCollection`, preventing callers from casting and mutating the DUID's internal state. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02, 0x03 }); +var bytes = duid.GetBytes(); +``` + +#### `ToString()` / `ToString(string)` / `ToString(string, IFormatProvider)` + +Converts the DUID to a colon-delimited uppercase hex string by default. The single-argument `ToString(string)` overload is a convenience that delegates to the two-argument form with a `null` format provider. Supports these format strings: + +| Format | Description | Example | +|--------|-------------|---------| +| `null`, `""`, `":"`, `"U:"` | Uppercase, colon delimited | `00:01:A2:B3` | +| `"U-"` or `"-"` | Uppercase, dash delimited | `00-01-A2-B3` | +| `"U"` | Uppercase, no delimiter | `0001A2B3` | +| `"L:"` | Lowercase, colon delimited | `00:01:a2:b3` | +| `"L-"` | Lowercase, dash delimited | `00-01-a2-b3` | +| `"L"` | Lowercase, no delimiter | `0001a2b3` | + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0xA2, 0xB3 }); + +Console.WriteLine(duid.ToString()); // 00:01:A2:B3 +Console.WriteLine(duid.ToString("L-", null)); // 00-01-a2-b3 +Console.WriteLine($"{duid:L}"); // 0001a2b3 (composite formatting) +``` + +Throws `FormatException` for unrecognized format strings (e.g. `"X"`, `"Ux"`, `"L "`). + +#### `Parse(string)` / `TryParse(string, out Duid)` + +```csharp +public static Duid Parse(string duidString) +public static bool TryParse(string duidString, out Duid duid) +``` + +Parses a hex string into a DUID. Accepts these formats: +- Colon-delimited octets: `01:02:A3:B4` +- Dash-delimited octets: `01-02-A3-B4` +- Space-delimited octets: `01 02 A3 B4` +- Undelimited octets: `0102A3B4` +- Leading-zero-omitted pairs: `1:2:A3:B4` (delimiter required) + +Input is trimmed before parsing; casing is ignored. Mixed delimiters (e.g. `01:02-A3:B4`) are rejected. + +```csharp +var a = Duid.Parse("00:01:A2:b3"); +var b = Duid.Parse("00-01-A2-b3"); +var c = Duid.Parse("0001A2b3"); +var d = Duid.Parse(" 00:01:A2:B3 "); // whitespace trimmed + +if (Duid.TryParse("GG:HH:II", out var result)) +{ + // not reached — invalid hex chars +} +``` + +`Parse` throws `ArgumentException` on failure. `TryParse` returns `false` and sets the out parameter to `null`. + +#### `IParsable` — `Parse(string, IFormatProvider)` / `TryParse(string, IFormatProvider, out Duid)` + +```csharp +public static Duid Parse(string s, IFormatProvider provider) +public static bool TryParse(string s, IFormatProvider provider, out Duid result) +``` + +Required by the `IParsable` interface (.NET 7+). The `provider` parameter is unused — these methods delegate directly to the non-provider overloads. + +```csharp +// generic parsing via IParsable +var duid = IParsable.Parse("00:01:A2:B3", null); +``` + +#### `Equals(Duid)` / `Equals(object)` + +```csharp +public bool Equals(Duid other) +public override bool Equals(object obj) +``` + +Byte-wise equality — two DUIDs are equal when they have the same length and identical byte values at every position. + +```csharp +var a = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +var b = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +var c = new Duid(new byte[] { 0x00, 0x01, 0xFF }); + +Console.WriteLine(a.Equals(b)); // True +Console.WriteLine(a.Equals(c)); // False +Console.WriteLine(a.Equals("not a duid")); // False (no exception) +``` + +#### `GetHashCode()` + +```csharp +public override int GetHashCode() +``` + +Lazily computed on first access and cached for the lifetime of the instance. Uses `HashCode.Add(byte)` for each byte, ensuring deterministic results across all target frameworks. Stable across serialization/deserialization boundaries. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +var hash = duid.GetHashCode(); // computed once, cached +var same = duid.GetHashCode(); // returns cached value +``` + +#### `CompareTo(Duid)` / `CompareTo(object)` + +```csharp +public int CompareTo(Duid other) +public int CompareTo(object obj) +``` + +Sorts by length first, then by unsigned byte value. A non-null DUID is always greater than `null`. + +```csharp +var shortDuid = new Duid(new byte[] { 0x00, 0x01, 0x02 }); // 3 bytes +var longDuid = new Duid(new byte[] { 0x00, 0x01, 0x02, 0x03 }); // 4 bytes + +Console.WriteLine(shortDuid.CompareTo(longDuid)); // -1 (shorter sorts first) +Console.WriteLine(longDuid.CompareTo(null)); // 1 (non-null > null) +``` + +`CompareTo(object)` throws `ArgumentException` if the argument is not a `Duid`. + +--- + +### Operators + +```csharp +public static bool operator ==(Duid lhs, Duid rhs) +public static bool operator !=(Duid lhs, Duid rhs) +public static bool operator <(Duid lhs, Duid rhs) +public static bool operator >(Duid lhs, Duid rhs) +public static bool operator <=(Duid lhs, Duid rhs) +public static bool operator >=(Duid lhs, Duid rhs) +``` + +`null` is treated as less than any non-`null` value for ordering operators (`<`, `>`, `<=`, `>=`). Both `null` operands are equal for `==`. + +```csharp +Duid a = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +Duid b = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +Duid c = null; + +Console.WriteLine(a == b); // True +Console.WriteLine(a == c); // False +Console.WriteLine(c == c); // True (both null) +Console.WriteLine(a > c); // True (non-null > null) +``` + +--- + +### Serialization + +Implements `ISerializable`. Uses a versioned payload (`SerializableVersion = 0`) for forward compatibility. Deserialization properly initializes the lazy hash code cache. + +```csharp +// BinaryFormatter example (net48 only) +var duid = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +var formatter = new BinaryFormatter(); +using var stream = new MemoryStream(); +formatter.Serialize(stream, duid); +stream.Position = 0; +var deserialized = (Duid)formatter.Deserialize(stream); +``` + +> **Note:** `BinaryFormatter` is removed in .NET 9+. Serialization testing targets `net48` only. + +--- + +### Span Formatting (.NET 8+) + +#### `ISpanFormattable` + +```csharp +bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) +``` + +Formats the DUID into a character span buffer. Returns `false` if the destination is too small. Uses the same format strings as `ToString(string, IFormatProvider)`. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0xA2, 0xB3 }); +Span buffer = stackalloc char[11]; +bool success = ((ISpanFormattable)duid).TryFormat(buffer, out int written, "U:", null); +// success = true, written = 11, buffer = "00:01:A2:B3" +``` + +#### `IUtf8SpanFormattable` + +```csharp +bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider provider) +``` + +Formats the DUID directly into a UTF-8 byte buffer. Returns `false` if the destination is too small. Avoids intermediate string allocations when the output is consumed as UTF-8 bytes. + +```csharp +var duid = new Duid(new byte[] { 0x00, 0x01, 0xA2, 0xB3 }); +Span utf8Buffer = stackalloc byte[11]; +bool success = ((IUtf8SpanFormattable)duid).TryFormat(utf8Buffer, out int written, "U:", null); +// success = true, written = 11, utf8Buffer contains UTF-8 bytes of "00:01:A2:B3" +``` + +--- + +## `NetDuid.DuidType` (enum) + +Maps the 2-byte big-endian type code to a human-readable label. This is a heuristic — the DUID content may not match the type it claims to be. + +```csharp +Console.WriteLine(duid.Type); // DuidType.LinkLayerPlusTime + +switch (duid.Type) +{ + case DuidType.Uuid: + break; + case DuidType.Undefined: + break; +} +``` + +| Value | Type Code | RFC | Description | +|-------|-----------|-----|-------------| +| `Undefined` | `0x0000`, `0x0005`–`0xFFFF` | — | Unknown or unregistered type | +| `LinkLayerPlusTime` | `0x0001` | RFC 8415 | Link-layer address plus time | +| `VendorAssigned` | `0x0002` | RFC 8415 | Vendor-assigned unique ID based on Enterprise Number | +| `LinkLayer` | `0x0003` | RFC 8415 | Link-layer address | +| `Uuid` | `0x0004` | RFC 6355 | Universally Unique Identifier (UUID) | diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c37c69e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [3.0.0] - TBD + +### Added + +- `Length` property on `Duid` for retrieving the octet count without allocating a collection. +- `Span` property (`ReadOnlySpan`) on `Duid` for zero-allocation access to underlying bytes (.NET 8+). +- `Memory` property (`ReadOnlyMemory`) on `Duid` for interop-friendly access to underlying bytes (.NET 8+). +- `Duid(ReadOnlySpan)` constructor for stack-friendly DUID creation from span data (.NET 8+). +- `ToString(string)` convenience overload that delegates to `ToString(string, IFormatProvider)`. +- `ISpanFormattable` implementation for formatting DUIDs into `Span` buffers (.NET 8+). +- `IUtf8SpanFormattable` implementation for formatting DUIDs directly into UTF-8 `Span` buffers (.NET 8+). +- `API_REFERENCE.md` - comprehensive public API documentation covering construction, validation, formatting, equality, comparison, operators, serialization, and parsing. +- Whitespace-tolerant parsing - `Parse` and `TryParse` now trim leading and trailing whitespace from input strings. +- `[NotNullWhen]` and `[MaybeNullWhen]` nullability annotations on `TryParse` overloads for .NET 8+ targets, enabling compiler null analysis after parse results. +- `Microsoft.Bcl.Memory` dependency for the `net48` target, providing modern `Span` and `Memory` APIs on .NET Framework. +- Smoke test suite (`smoketests/`) that validates the packed NuGet package works correctly as a consumer reference across all target frameworks. +- BenchmarkDotNet project (`src/NetDuid.Benchmarks/`) for performance regression testing covering comparison, equality, factory, formatting, and miscellaneous operations. + +### Changed + +- **Central Package Management** - all NuGet package versions are now centralized in `Directory.Packages.props` instead of individual `.csproj` files. +- Solution file migrated from `.sln` to `.slnx` format and moved to the repository root. +- Configuration files consolidated to the repository root: `.editorconfig`, `stylecop.json`, `.csharpierrc.json`, and `global.json` now live at the repo root instead of under `src/`. +- Regex patterns used in parsing now include a 1-second timeout to protect against ReDoS on malformed input. +- `Equals(Duid)` optimized to directly access the underlying byte array instead of allocating a `ReadOnlyCollection` on every equality check. +- Format string parsing extracted into a dedicated method for cleaner internals (no behavioral change). +- `DuidRegexSource` renamed to `DuidRegexPatterns` (internal type, not part of public API). + +### Fixed + +- Undelimited hex parsing now accepts uppercase characters (`A-F`). The `UndelimitedOctetPattern` regex was missing `A-F`, causing `TryParse` and `Parse` to reject valid uppercase undelimited DUID strings. +- `CompareTo(Duid)` and `CompareTo(object)` now return `1` when comparing a non-null DUID to `null`, correcting a violation of the `IComparable` contract. Previously these methods returned `-1`, which incorrectly treated non-null values as less than null. + +### Removed + +- `GeneratePackageOnBuild` MSBuild property - packaging is now an explicit CI step, improving build times during local development. +- `.husky/pre-commit` hook - replaced by the dual Husky.Net and Python pre-commit system. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cd14769 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing to NetDuid + +## Prerequisites + +- [.NET SDK](https://dotnet.microsoft.com/download) (version pinned in `global.json`) +- (optional) The dev container in `.devcontainer/` if you'd rather not install the SDK locally +- (optional) [Python](https://www.python.org/) and `pre-commit` for the Python-based pre-commit hooks + +## Build & test + +Run everything from the repository root: + +```bash +dotnet restore +dotnet build +dotnet test --verbosity normal +``` + +To test a single target framework: + +```bash +dotnet test --framework net8.0 +``` + +> **Note:** `net48` tests only work on Windows. CI runs per-target-framework on Ubuntu and all target frameworks on Windows. + +## Project layout + +- `src/` — the `NetDuid` library and its xUnit test project +- `src/NetDuid.Benchmarks/` — BenchmarkDotNet performance benchmarks +- `smoketests/` — a standalone consumer project that validates the packed NuGet package works correctly across all target frameworks +- `.devcontainer/` — dev container configuration +- `.scratch/` — internal working documents (not shipped) + +The solution file is `NetDuid.slnx` at the repository root. + +## Pull requests + +1. Fork and branch from `main`. +2. Keep changes focused — one logical change per PR. +3. Add or update tests for behavior changes. +4. Run formatting before pushing (or let the pre-commit hooks do it). +5. Fill out the PR template; link any related issue. + +## Code style + +Formatting and analyzer rules are enforced via `.editorconfig` and +`Directory.Build.props`. CI will fail the build on analyzer warnings treated +as errors — run `dotnet build` locally to catch these before pushing. + +To run formatting manually: + +```bash +dotnet tool restore +dotnet format style; dotnet format analyzers; dotnet csharpier format . +``` + +These commands may be called independently, but order may matter. + +## Pre-commit hooks + +Two systems run independently on commit: + +1. **[Husky.Net](https://alirezanet.github.io/Husky.Net/)** — lints and stages `.cs` files via `dotnet format` and [CSharpier](https://csharpier.com/). Disable with `HUSKY=0`. +2. **[pre-commit](https://pre-commit.com/)** (Python-based) — runs `codespell`, `markdownlint`, trailing-whitespace and end-of-file checks, YAML validation, and CSharpier. Install with `pre-commit install`. + +## Central package management + +NuGet package versions are centralized in `Directory.Packages.props`. When adding or updating a dependency, edit that file — not the individual `.csproj` files. After changing dependencies, run `dotnet restore` to regenerate the committed `packages.lock.json` files. + +## Smoke tests + +`smoketests/` exercises the **packed NuGet package** (not a project reference). Requires building and packing first. Run via `smoketests/run-smoke-tests.sh` (Linux/macOS) or `smoketests/run-smoke-tests.ps1` (Windows). + +## Benchmarks + +`src/NetDuid.Benchmarks/` is a BenchmarkDotNet project targeting `net10.0`. Run via `src/run-benchmarks.sh` (Linux/macOS) or `src/run-benchmarks.ps1` (Windows). Results land in `src/results//`. diff --git a/NetDuid.slnx b/NetDuid.slnx new file mode 100644 index 0000000..e99793c --- /dev/null +++ b/NetDuid.slnx @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 5ae554c..002a13a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# NetDuid +# NetDuid -![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/sandialabs/NetDuid/build.yml?branch=main) -[![NuGet Version](https://img.shields.io/nuget/v/NetDuid)](https://www.nuget.org/packages/NetDuid) -[![GitHub Release](https://img.shields.io/github/v/release/sandialabs/NetDuid)](https://github.com/sandialabs/NetDuid/releases) -[![GitHub Tag](https://img.shields.io/github/v/tag/sandialabs/NetDuid)](https://github.com/sandialabs/NetDuid/tags) -![Targets](https://img.shields.io/badge/.NET%20Standard%202.0%20|%20.NET%208.0%20|%20.NET%209.0|%20.NET%2010.0-blue) -[![Apache 2.0 License](https://img.shields.io/github/license/sandialabs/NetDuid?logo=apache)](https://github.com/sandialabs/NetDuid/blob/main/LICENSE) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/sandialabs/NetDuid/build.yml?branch=main&logo=github&label=build) +[![NuGet Version](https://img.shields.io/nuget/v/NetDuid?logo=nuget)](https://www.nuget.org/packages/NetDuid) +[![NuGet Downloads](https://img.shields.io/nuget/dt/NetDuid?logo=nuget)](https://www.nuget.org/packages/NetDuid) +[![GitHub Release](https://img.shields.io/github/v/release/sandialabs/NetDuid?logo=github)](https://github.com/sandialabs/NetDuid/releases) +![Targets](https://img.shields.io/badge/.NET-Standard%202.0%20%7C%208.0%20%7C%209.0%20%7C%2010.0-blue) +[![License](https://img.shields.io/github/license/sandialabs/NetDuid?logo=apache)](https://github.com/sandialabs/NetDuid/blob/main/LICENSE) ## About the Project @@ -22,12 +22,6 @@ The main goals of this project are: - **Performance**: Optimize the library for performance, ensuring that it can handle large volumes of DUIDs efficiently. - **Cross-Platform and Target Support**: Target multiple .NET versions to ensure compatibility across different platforms and environments. -### Features - -- **Parsing and Construction**: Easily parse DUIDs from strings or construct them from byte arrays. -- **Comparison and Equality**: Implement comparison and equality operations for DUIDs. -- **Formatting**: Convert DUIDs to formatted string representations for display or logging. - ### Use Cases This library is intended for use in various scenarios, including but not limited to: @@ -36,11 +30,17 @@ This library is intended for use in various scenarios, including but not limited - **Logging and Monitoring**: Tracking and logging DUIDs in network traffic for monitoring and analysis. - **Testing and Simulation**: Simulating DHCPv6 clients and servers in test environments. +## Changelog + +For a detailed list of changes, see [CHANGELOG.md](CHANGELOG.md). + ## Getting Started +For a complete API reference, see [API_REFERENCE.md](API_REFERENCE.md). + You are most likely to be interacting with the `NetDuid.Duid` type. -The `Duid` type implements `IEquatable`, `IComparable`, `IFormattable`, `ISerializable`, and for .NET 8 and greater `IParsable`. +The `Duid` type implements `IEquatable`, `IComparable`, `IFormattable`, `ISerializable`, for .NET 7+ `IParsable`, and for .NET 8+ `ISpanFormattable` and `IUtf8SpanFormattable`. The library "knows" RFC 8415 ("Link-layer address plus time", "Vendor-assigned unique ID based on Enterprise Number", and "Link-layer address") and RFC 6355 ("Universally Unique Identifier (UUID)") DUIDs, but can treat any valid `byte` array (a minimum of 3 bytes, and maximum of 130 bytes per the RFCs) as a DUID. An unhandled DUID type will be treated as an "Undefined" type, but otherwise functionality is identical. @@ -55,6 +55,13 @@ var duidBytes = new byte[] { 0x00, 0x01, 0x02, 0x03 }; var duid = new Duid(duidBytes); ``` +On .NET 8+ you can also construct from a `ReadOnlySpan`, which is useful for stack-allocated data or pool-sourced buffers: + +```csharp +Span stackBytes = stackalloc byte[] { 0x00, 0x01, 0x02, 0x03 }; +var duid = new Duid((ReadOnlySpan)stackBytes); +``` + #### Parsing a string to a DUID You can also parse a DUID from a `string` using the `TryParse` or `Parse` methods. @@ -101,7 +108,9 @@ Simply calling `ToString()` will return the default format of upper cased colon #### Bytes -Calling `GetBytes()` will return a read only collection of the underlying bytes of a DUID. +Calling `GetBytes()` will return a read only collection of the underlying bytes of a DUID. The `Length` property provides the octet count without any allocation. + +On .NET 8+, `Span` and `Memory` properties provide zero-allocation access to the underlying bytes as `ReadOnlySpan` and `ReadOnlyMemory` respectively. #### DUID Types @@ -123,6 +132,35 @@ The `Duid` class implements `IEquatable`, `IComparable`, `IComparabl The `CompareTo`, and its operators, is not done in mathematical order or bytes, but rather first by byte length then by unsigned value. When using the comparison operators a `null` value is considered less than any non-`null` value. +## New Features in v3.0.0 + +### Span-Based APIs (.NET 8+) + +- `Duid(ReadOnlySpan)` constructor — create DUIDs from stack-allocated or pool-sourced byte data. +- `Span` / `Memory` properties — zero-allocation access to underlying bytes. +- `ISpanFormattable` / `IUtf8SpanFormattable` — format DUIDs directly into character or UTF-8 byte buffers without intermediate string allocations. +- `ToString(string)` convenience overload — call `duid.ToString("L")` without providing a format provider. +- `Length` property — get the octet count without calling `GetBytes().Count`. + +### Whitespace-Tolerant Parsing + +`Parse` and `TryParse` now trim leading and trailing whitespace from input strings, so `" 00:01:A2:B3 "` parses successfully. + +### Performance Improvements + +- **Regex source generation**: On .NET 7+ targets, parsing uses `[GeneratedRegex]` for compile-time regex generation, reducing startup overhead. +- **Lazy hash code**: The hash code is computed once and cached. Deserialized instances also initialize the cache, fixing a `NullReferenceException` in earlier versions. + +## Breaking Changes in v3.0.0 + +### `GetBytes()` Returns an Immutable View + +The runtime type of the returned `IReadOnlyCollection` changed from `byte[]` to `ReadOnlyCollection`. Previously, callers could cast the return value and mutate the DUID's internal state. The new implementation wraps the array via `Array.AsReadOnly()`, enforcing true immutability. + +### `CompareTo` Null Contract Corrected + +`CompareTo(Duid?)` and `CompareTo(object?)` previously returned `-1` when comparing any non-null DUID to `null`, violating the `IComparable` standard contract (non-null > null should return `1`). Apologies — this was improperly implemented and went unnoticed because null DUIDs are uncommon in practice. v3.0.0 corrects both overloads to return `1`. + ## Developer Notes ### Built With @@ -132,6 +170,7 @@ This project was built with the aid of: - [CSharpier](https://csharpier.com/) - [dotnet-outdated](https://github.com/dotnet-outdated/dotnet-outdated) - [Husky.Net](https://alirezanet.github.io/Husky.Net/) +- [NSubstitute](https://nsubstitute.github.io/) - [Roslynator](https://josefpihrt.github.io/docs/roslynator/) - [SonarAnalyzer](https://www.sonarsource.com/products/sonarlint/features/visual-studio/) - [StyleCop.Analyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) @@ -147,19 +186,18 @@ This project uses [Semantic Versioning](https://semver.org/) The project targets [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0), [.NET 8](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8), [.NET 9](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview), and [.NET 10](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview). The test project similarly targets .NET 8, .NET 9, .NET 10, but targets [.NET Framework 4.8](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48) for the .NET Standard 2.0 tests. -### Commit Hook - -The project itself has a configured pre-commit git hook, via [Husky.Net](https://alirezanet.github.io/Husky.Net/) that automatically lints and formats code via [dotnet format](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-format) and [csharpier](https://csharpier.com/). +### Commit Hooks -#### Disable husky in CI/CD pipelines +The project has two pre-commit systems configured: -Per the [Husky.Net instructions](https://alirezanet.github.io/Husky.Net/guide/automate.html#disable-husky-in-ci-cd-pipelines) +1. **[Husky.Net](https://alirezanet.github.io/Husky.Net/)** — lints and stages `.cs` files via `dotnet format` and [CSharpier](https://csharpier.com/). +2. **[pre-commit](https://pre-commit.com/)** (Python-based) — runs `codespell`, `markdownlint`, trailing-whitespace and end-of-file checks, YAML validation, and CSharpier on all supported file types. -> You can set the `HUSKY` environment variable to `0` in order to disable husky in CI/CD pipelines. +Both run automatically on `git commit`. To disable Husky in CI/CD pipelines, set the `HUSKY` environment variable to `0`. #### Manual Linting and Formatting -On occasion a manual run is desired it may be done so via the `src` directory and with the command +To run formatting manually from the repository root: ```shell dotnet format style; dotnet format analyzers; dotnet csharpier format . @@ -169,7 +207,11 @@ These commands may be called independently, but order may matter. #### Testing -After making changes tests should be run that include all targets +After making changes, run tests across all target frameworks: + +```shell +dotnet test src --verbosity normal +``` ## Acknowledgments @@ -185,12 +227,12 @@ Including, but not limited to: ## Copyright -> Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software +> Copyright 2026 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software ## License > Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at > -> http://www.apache.org/licenses/LICENSE-2.0 +> > > Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/SECURITY.md b/SECURITY.md index 3b15405..5aff268 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,9 @@ # Reporting Security Issues -The NetDuid team takes security issues in NetDuid and our other projects seriously. We appreciate your efforts to responsibly disclose security-related findings. +The NetDuid team takes security issues in NetDuid and our other projects seriously. We appreciate your efforts to responsibly disclose security related findings. -**Please do not file an issue via the issue tracker**; we'd prefer to avoid publishing security issue details until after we have a fix in place. This will aid in avoiding vulnerability exploitation in the wild. + **Please do not file an issue via the issue tracker**, we'd prefer to avoid publishing security issue details until after we have a fix in place. This will aid in avoiding vulnerability exploitation in the wild. -We request that if you discover a security significant issue in our library, you report it via email to us at [opensource@sandia.gov](mailto:opensource@sandia.gov). Please include the words "[NETDUID SECURITY]" in the subject line. Upon notification of a security issue, we may request additional information. +We request that if you discover a security significant issue in our library that you report it via email to us at [opensource@sandia.gov](mailto:opensource@sandia.gov). Please include the words "[NetDuid SECURITY]" in the subject line. Upon notification of a security issue we may request additional information. -The NetDuid team will do our best to alleviate the issue and inform the public as appropriate. \ No newline at end of file +The NetDuid team will do our best alleviate the issue and inform the public as appropriate. diff --git a/src/global.json b/global.json similarity index 100% rename from src/global.json rename to global.json diff --git a/smoketests/README.md b/smoketests/README.md new file mode 100644 index 0000000..dee35a0 --- /dev/null +++ b/smoketests/README.md @@ -0,0 +1,159 @@ +# NetDuid Smoke Tests + +Smoke tests that prove the **packed NuGet package** works for every supported target framework. + +## Why these exist + +The main `src/NetDuid.Tests/` suite references the library via a *project reference*, which +bypasses NuGet asset-selection entirely. A project reference can never surface: + +- Incorrect `lib//` folder layout in the `.nupkg` +- Wrong TFM asset being chosen by a consumer (e.g. `net9.0` falling back to `netstandard2.0` + when a dedicated `net9.0` asset should exist) +- BCL types referenced by a C# language feature that are missing on an older runtime +- Missing or mismatched transitive dependencies declared in the `.nuspec` + +Smoke tests fix this by **packing the library first**, then referencing it as a NuGet package +from a separate consumer project — exactly as an end-user would. + +## Structure + +``` +smoketests/ +├── nuget.config # adds smoketests/feed/ as a local NuGet source +├── run-smoke-tests.sh # Linux / macOS (net8.0, net9.0, net10.0) +├── run-smoke-tests.ps1 # Windows (net48, net8.0, net9.0, net10.0) +└── SmokeTests/ + ├── SmokeTests.csproj # multi-target console app; PackageReference to NetDuid + └── Program.cs # exercises the full public API surface +``` + +`smoketests/feed/` is git-ignored; the run scripts create it on demand. + +## Running locally + +### Linux / macOS + +```bash +# From the repo root: +./smoketests/run-smoke-tests.sh + +# With an explicit version: +./smoketests/run-smoke-tests.sh 4.0.0-preview +``` + +Covers **net8.0**, **net9.0**, **net10.0**. + +### Windows (PowerShell) + +```powershell +# From the repo root: +.\smoketests\run-smoke-tests.ps1 + +# With an explicit version: +.\smoketests\run-smoke-tests.ps1 -Version 4.0.0-preview +``` + +Covers **net48** (→ `netstandard2.0` asset), **net8.0**, **net9.0**, **net10.0**. + +> **net48 requires Windows.** The `netstandard2.0` asset is what any .NET Framework +> or legacy .NET Standard consumer receives; net48 is the proxy TFM that validates it. + +## What is tested + +`Program.cs` exercises the entire public API surface against a real runtime: + +| Area | Checks | +|---|---| +| `Duid` (all types) | Create from bytes, type detection for all 5 `DuidType` values | +| Parse / TryParse | Colon, dash, space, undelimited, leading-zero-omitted, whitespace trim, case-insensitive, mixed-delimiter rejection, null/empty rejection | +| `IFormattable` / `ToString` | All 6 format strings (`null`, `""`, `"U:"`, `"U-"`, `"U"`, `"L:"`, `"L-"`, `"L"`), invalid format throws `FormatException` | +| `IParsable` (NET7+) | `Parse(string, IFormatProvider)`, `TryParse(string, IFormatProvider, out Duid)` success and failure | +| Equality | Same bytes, different bytes, null, `==`, `!=`, `GetHashCode` | +| Comparison | `CompareTo` (shorter, longer, equal), `<`, `>`, `<=`, `>=` | +| Null operator semantics | `null == null`, `null != non-null`, `non-null > null`, `null < non-null`, `>=`, `<=` with null | +| `GetBytes` | Content, read-only snapshot semantics | +| Edge cases | 3-byte minimum, 130-byte maximum, constructor rejects null/empty/2-byte/131-byte | + +## How the package reference works + +The consumer project (`SmokeTests.csproj`) does **not** use a project reference. +Instead it declares: + +```xml + +``` + +This indirection is necessary because there is no published NetDuid package on nuget.org. +The workflow is always the same: + +1. **Pack** `src/NetDuid/NetDuid.csproj` with a synthetic version (e.g. `99.0.0-smoke`) + into `smoketests/feed/`. +2. **Restore** passing `-p:NetDuidVersion=99.0.0-smoke` — the condition matches, NuGet + resolves the package from the local feed. +3. **Build and run** the consumer project. + +### The placeholder version + +When no `NetDuidVersion` is passed, the csproj defaults to `0.0.0-placeholder`. +The `Condition` on the `PackageReference` excludes it entirely in this state, +preventing restore failures when the local feed does not exist. + +### org-level dependency submission + +The repository has an org-wide `submit-nuget` action that scans every `.csproj` and +runs `dotnet restore` to submit dependency graphs. Because `0.0.0-placeholder` causes +the `PackageReference` to be omitted, the restore succeeds without needing the local +feed — no manual exclusions required. + +## CI + +Smoke tests run in the independent **`smoke-test.yml`** workflow (`.github/workflows/smoke-test.yml`), +triggered on every PR and push to `main`: + +- **ubuntu-latest** → net8.0, net9.0, net10.0 +- **windows-latest** → net48 (netstandard2.0 asset validation) + +The workflow packs the library, restores the consumer against the local feed, builds +per-TFM, and runs the console app. Any non-zero exit code from the consumer fails the job. + +## Developer notes + +### Opening in an IDE + +Opening `smoketests/SmokeTests/SmokeTests.csproj` directly in an IDE will show build +errors — the `PackageReference` is conditioned away and the `Duid` type is not available. +This is expected. Always use the run scripts or the CI workflow, which first pack the +library into the local feed. + +If you need IDE support for editing, open the whole repo solution (`src/`) instead. + +### Adding a new test + +All smoke tests are top-level `Check()` calls in `Program.cs`. Add a new block: + +```csharp +Check( + "Description of what is tested", + () => + { + // Arrange + var duid = new Duid(new byte[] { 0x00, 0x04, 0x01, 0x02 }); + // Assert + Require(duid.Type == DuidType.Uuid, "expected UUID type"); + } +); +``` + +The project uses no test framework — it is a self-checking console app. `Check()` +catches exceptions, prints `[PASS]` / `[FAIL]`, and exits with code 1 on any failure. + +### Troubleshooting + +| Symptom | Likely cause | +|---|---| +| `NU1301: The local source '.../feed' doesn't exist` | Restore without first packing. Run the script or CI workflow. | +| `NU1101: Unable to find package NetDuid` | The feed is populated with a different version than `NetDuidVersion` expects. Check the version passed to `pack` and `restore` match. | +| `CS0246: The type or namespace 'Duid' could not be found` | The `PackageReference` was conditioned away (placeholder version). Pass `-p:NetDuidVersion=...` with a real version. | +| `Failed to restore` on unrelated checkout | Org-level `submit-nuget` ran restore without a local feed. Verify the placeholder condition is intact. | diff --git a/smoketests/SmokeTests/Program.cs b/smoketests/SmokeTests/Program.cs new file mode 100644 index 0000000..bc69947 --- /dev/null +++ b/smoketests/SmokeTests/Program.cs @@ -0,0 +1,695 @@ +// NetDuid NuGet package smoke tests. +// +// This program is a consumer of the *packed NuGet package* (not a project reference). +// Its only job is to prove that each target-framework asset (netstandard2.0 via net48, +// net8.0, net9.0, net10.0) loads correctly and that the public API behaves at runtime. +// +// Run via smoketests/run-smoke-tests.{sh,ps1} — those scripts pack the library first. + +using System; +using System.Collections.Generic; +using System.Linq; +using NetDuid; + +var tfm = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; +Console.WriteLine($"=== NetDuid Smoke Tests ({tfm}) ==="); +Console.WriteLine(); + +var passed = 0; +var failed = 0; + +void Check(string name, Action test) +{ + try + { + test(); + Console.WriteLine($" [PASS] {name}"); + passed++; + } + catch (Exception ex) + { + Console.WriteLine($" [FAIL] {name}: {ex.Message}"); + failed++; + } +} + +void Require(bool condition, string message) +{ + if (!condition) + throw new InvalidOperationException(message); +} + +void RequireEqual(T expected, T actual, string field) +{ + if (!Equals(expected, actual)) + throw new InvalidOperationException($"{field}: expected '{expected}', got '{actual}'"); +} + +// --------------------------------------------------------------------------- +// Duid — all type variants +// --------------------------------------------------------------------------- +Console.WriteLine("Duid (all types)"); + +var llTimeBytes = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 }; +var vendorBytes = new byte[] { 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03 }; +var linkLayerBytes = new byte[] { 0x00, 0x03, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; +var uuidBytes = new byte[] +{ + 0x00, + 0x04, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, +}; + +Check( + "LinkLayerPlusTime type detection", + () => + { + var duid = new Duid(llTimeBytes); + RequireEqual(DuidType.LinkLayerPlusTime, duid.Type, "Type"); + } +); + +Check( + "VendorAssigned type detection", + () => + { + var duid = new Duid(vendorBytes); + RequireEqual(DuidType.VendorAssigned, duid.Type, "Type"); + } +); + +Check( + "LinkLayer type detection", + () => + { + var duid = new Duid(linkLayerBytes); + RequireEqual(DuidType.LinkLayer, duid.Type, "Type"); + } +); + +Check( + "Uuid type detection", + () => + { + var duid = new Duid(uuidBytes); + RequireEqual(DuidType.Uuid, duid.Type, "Type"); + } +); + +Check( + "Undefined type for unknown type code", + () => + { + var duid = new Duid(new byte[] { 0xFF, 0xFE, 0x01, 0x02 }); + RequireEqual(DuidType.Undefined, duid.Type, "Type"); + } +); + +// --------------------------------------------------------------------------- +// Parse / TryParse +// --------------------------------------------------------------------------- +Console.WriteLine("\nParse / TryParse"); + +var colonString = "00:01:00:00:00:00:00:01:00:11:22:33:44:55"; +var dashString = "00-01-00-00-00-00-00-01-00-11-22-33-44-55"; +var spaceString = "00 01 00 00 00 00 00 01 00 11 22 33 44 55"; +var noDelimString = "0001000000000001001122334455"; + +Check( + "Parse colon-delimited", + () => + { + var duid = Duid.Parse(colonString); + RequireEqual(DuidType.LinkLayerPlusTime, duid.Type, "Type"); + RequireEqual(14, duid.GetBytes().Count, "byte count"); + } +); + +Check( + "Parse dash-delimited", + () => + { + var duid = Duid.Parse(dashString); + RequireEqual(DuidType.LinkLayerPlusTime, duid.Type, "Type"); + } +); + +Check( + "Parse space-delimited", + () => + { + var duid = Duid.Parse(spaceString); + RequireEqual(DuidType.LinkLayerPlusTime, duid.Type, "Type"); + } +); + +Check( + "Parse undelimited", + () => + { + var duid = Duid.Parse(noDelimString); + RequireEqual(DuidType.LinkLayerPlusTime, duid.Type, "Type"); + } +); + +Check( + "Parse leading-zero-omitted pairs", + () => + { + var duid = Duid.Parse("1:2:A3:B4"); + RequireEqual(4, duid.GetBytes().Count, "byte count"); + } +); + +Check( + "Parse trims whitespace", + () => + { + var duid = Duid.Parse(" 00:01:02:03 "); + RequireEqual(4, duid.GetBytes().Count, "byte count"); + } +); + +Check( + "Parse is case-insensitive", + () => + { + var lower = Duid.Parse("ab:cd:ef:01:02:03"); + var upper = Duid.Parse("AB:CD:EF:01:02:03"); + Require(lower.Equals(upper), "case-insensitive parse yields equal DUIDs"); + } +); + +Check( + "TryParse success", + () => + { + Require(Duid.TryParse("00:01:00:01:02:03", out var duid), "TryParse returned true"); + Require(duid != null, "duid not null"); + } +); + +Check( + "TryParse failure on garbage", + () => + { + Require(!Duid.TryParse("not-a-duid", out _), "TryParse returned false"); + } +); + +Check( + "Mixed delimiters rejected", + () => + { + Require(!Duid.TryParse("01:02-A3:B4", out _), "mixed delimiters rejected"); + } +); + +Check( + "Parse rejects null", + () => + { + try + { + Duid.Parse(null!); + throw new InvalidOperationException("should have thrown"); + } + catch (ArgumentException) + { + // expected + } + } +); + +Check( + "Parse rejects empty string", + () => + { + try + { + Duid.Parse(string.Empty); + throw new InvalidOperationException("should have thrown"); + } + catch (ArgumentException) + { + // expected + } + } +); + +// --------------------------------------------------------------------------- +// IFormattable / ToString +// --------------------------------------------------------------------------- +Console.WriteLine("\nIFormattable / ToString"); + +var hexDuid = Duid.Parse("ab:cd:ef:01:02:03"); + +Check( + "ToString() parameterless", + () => + { + var s = hexDuid.ToString(); + RequireEqual("AB:CD:EF:01:02:03", s, "parameterless ToString"); + } +); + +Check( + "ToString default format (uppercase colon)", + () => + { + var s = hexDuid.ToString(null, null); + RequireEqual("AB:CD:EF:01:02:03", s, "default format"); + } +); + +Check( + "ToString uppercase dash", + () => + { + var s = hexDuid.ToString("U-", null); + RequireEqual("AB-CD-EF-01-02-03", s, "U- format"); + } +); + +Check( + "ToString uppercase no delimiter", + () => + { + var s = hexDuid.ToString("U", null); + RequireEqual("ABCDEF010203", s, "U format"); + } +); + +Check( + "ToString lowercase colon", + () => + { + var s = hexDuid.ToString("L:", null); + RequireEqual("ab:cd:ef:01:02:03", s, "L: format"); + } +); + +Check( + "ToString lowercase dash", + () => + { + var s = hexDuid.ToString("L-", null); + RequireEqual("ab-cd-ef-01-02-03", s, "L- format"); + } +); + +Check( + "ToString lowercase no delimiter", + () => + { + var s = hexDuid.ToString("L", null); + RequireEqual("abcdef010203", s, "L format"); + } +); + +Check( + "ToString empty format defaults to uppercase colon", + () => + { + var s = hexDuid.ToString("", null); + RequireEqual("AB:CD:EF:01:02:03", s, "empty format"); + } +); + +Check( + "ToString invalid format throws FormatException", + () => + { + try + { + hexDuid.ToString("X", null); + throw new InvalidOperationException("should have thrown"); + } + catch (FormatException) + { + // expected + } + } +); + +// --------------------------------------------------------------------------- +// IParsable (NET7+ only) +// --------------------------------------------------------------------------- +#if NET7_0_OR_GREATER +Console.WriteLine("\nIParsable"); + +Check( + "Parse with IFormatProvider", + () => + { + var duid = Duid.Parse("ab:cd:ef:01:02:03", (IFormatProvider)null); + RequireEqual(6, duid.GetBytes().Count, "byte count"); + } +); + +Check( + "TryParse with IFormatProvider", + () => + { + Require(Duid.TryParse("ab:cd:ef:01:02:03", (IFormatProvider)null, out var duid), "TryParse succeeded"); + Require(duid != null, "duid not null"); + } +); + +Check( + "TryParse with IFormatProvider failure", + () => + { + Require(!Duid.TryParse("garbage", (IFormatProvider)null, out _), "garbage returns false"); + } +); +#endif + +// --------------------------------------------------------------------------- +// Equality +// --------------------------------------------------------------------------- +Console.WriteLine("\nEquality"); + +var a = new Duid(llTimeBytes); +var b = new Duid(llTimeBytes); +var c = new Duid(uuidBytes); + +Check( + "Equals same bytes", + () => + { + Require(a.Equals(b), "a.Equals(b)"); + Require(b.Equals(a), "b.Equals(a)"); + } +); + +Check( + "Equals different bytes", + () => + { + Require(!a.Equals(c), "a.Equals(c) is false"); + } +); + +Check( + "Equals null", + () => + { + Require(!a.Equals(null), "a.Equals(null) is false"); + } +); + +Check( + "operator ==", + () => + { + Require(a == b, "a == b"); + Require(!(a == c), "a != c"); + } +); + +Check( + "operator !=", + () => + { + Require(a != c, "a != c"); + Require(!(a != b), "not (a != b)"); + } +); + +Check( + "GetHashCode equal for equal DUIDs", + () => + { + RequireEqual(a.GetHashCode(), b.GetHashCode(), "hash codes"); + } +); + +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +Console.WriteLine("\nComparison"); + +var small = new Duid(new byte[] { 0x00, 0x01, 0x00, 0x01 }); +var medium = new Duid(new byte[] { 0x00, 0x01, 0x00, 0x01, 0x02 }); +var large = new Duid(new byte[] { 0x00, 0x01, 0x00, 0x01, 0x02, 0x03 }); + +Check( + "CompareTo shorter < longer", + () => + { + Require(small.CompareTo(medium) < 0, "small < medium"); + Require(medium.CompareTo(small) > 0, "medium > small"); + } +); + +Check( + "CompareTo equal lengths", + () => + { + var x = new Duid(new byte[] { 0x00, 0x01, 0x00, 0x01, 0x00 }); + var y = new Duid(new byte[] { 0x00, 0x01, 0x00, 0x01, 0x01 }); + Require(x.CompareTo(y) < 0, "x < y"); + Require(y.CompareTo(x) > 0, "y > x"); + Require(x.CompareTo(x) == 0, "x == x"); + } +); + +Check( + "operator < and >", + () => + { + Require(small < medium, "small < medium"); + Require(medium > small, "medium > small"); + Require(!(small > medium), "not (small > medium)"); + } +); + +Check( + "operator <= and >=", + () => + { + var small2 = new Duid(new byte[] { 0x00, 0x01, 0x00, 0x01 }); + Require(small <= medium, "small <= medium"); + Require(small2 <= small, "small <= small"); + Require(medium >= small, "medium >= small"); + Require(small2 >= small, "small >= small"); + } +); + +// --------------------------------------------------------------------------- +// Null operator semantics +// --------------------------------------------------------------------------- +Console.WriteLine("\nNull operator semantics"); + +Duid nonNull = new Duid(new byte[] { 0x00, 0x01, 0x02 }); +Duid nullDuid = null; + +Check( + "null == null is true", + () => + { + Require(nullDuid == null, "null == null"); + } +); + +Check( + "null != non-null is true", + () => + { + Require(nullDuid != nonNull, "null != nonNull"); + } +); + +Check( + "non-null > null is true", + () => + { + Require(nonNull > nullDuid, "nonNull > null"); + } +); + +Check( + "null < non-null is true", + () => + { + Require(nullDuid < nonNull, "null < nonNull"); + } +); + +Check( + "null >= null is true", + () => + { + Require(nullDuid >= null, "null >= null"); + } +); + +Check( + "null <= null is true", + () => + { + Require(nullDuid <= null, "null <= null"); + } +); + +Check( + "non-null >= null is true", + () => + { + Require(nonNull >= nullDuid, "nonNull >= null"); + } +); + +Check( + "null <= non-null is true", + () => + { + Require(nullDuid <= nonNull, "null <= nonNull"); + } +); + +// --------------------------------------------------------------------------- +// GetBytes +// --------------------------------------------------------------------------- +Console.WriteLine("\nGetBytes"); + +Check( + "GetBytes returns correct bytes", + () => + { + var duid = new Duid(llTimeBytes); + var bytes = duid.GetBytes(); + RequireEqual(llTimeBytes.Length, bytes.Count, "count"); + Require(bytes.SequenceEqual(llTimeBytes), "content matches"); + } +); + +Check( + "GetBytes is read-only snapshot", + () => + { + var original = new byte[] { 0x00, 0x01, 0x02 }; + var duid = new Duid(original); + var bytes = duid.GetBytes(); + var first = bytes.First(); + original[0] = 0xFF; + Require(bytes.First() == first, "GetBytes returns a snapshot, not a live view"); + } +); + +// --------------------------------------------------------------------------- +// Edge Cases +// --------------------------------------------------------------------------- +Console.WriteLine("\nEdge Cases"); + +Check( + "Minimum valid DUID (3 bytes)", + () => + { + var duid = new Duid(new byte[] { 0x00, 0x01, 0x00 }); + RequireEqual(DuidType.LinkLayerPlusTime, duid.Type, "Type"); + RequireEqual(3, duid.GetBytes().Count, "count"); + } +); + +Check( + "Maximum valid DUID (130 bytes)", + () => + { + var bytes = new byte[130]; + bytes[0] = 0x00; + bytes[1] = 0x04; + var duid = new Duid(bytes); + RequireEqual(DuidType.Uuid, duid.Type, "Type"); + RequireEqual(130, duid.GetBytes().Count, "count"); + } +); + +Check( + "Constructor rejects null bytes", + () => + { + try + { + _ = new Duid(null!); + throw new InvalidOperationException("should have thrown"); + } + catch (ArgumentNullException) + { + // expected + } + } +); + +Check( + "Constructor rejects empty bytes", + () => + { + try + { + _ = new Duid(Array.Empty()); + throw new InvalidOperationException("should have thrown"); + } + catch (ArgumentException) + { + // expected + } + } +); + +Check( + "Constructor rejects less than 3 bytes", + () => + { + try + { + _ = new Duid(new byte[] { 0x00, 0x01 }); + throw new InvalidOperationException("should have thrown"); + } + catch (ArgumentException) + { + // expected + } + } +); + +Check( + "Constructor rejects more than 130 bytes", + () => + { + try + { + _ = new Duid(new byte[131]); + throw new InvalidOperationException("should have thrown"); + } + catch (ArgumentException) + { + // expected + } + } +); + +// --------------------------------------------------------------------------- +// Summary +// --------------------------------------------------------------------------- +Console.WriteLine(); +Console.WriteLine($"Results: {passed} passed, {failed} failed."); + +if (failed > 0) + Environment.Exit(1); diff --git a/smoketests/SmokeTests/SmokeTests.csproj b/smoketests/SmokeTests/SmokeTests.csproj new file mode 100644 index 0000000..ebf2731 --- /dev/null +++ b/smoketests/SmokeTests/SmokeTests.csproj @@ -0,0 +1,33 @@ + + + Exe + + net48;net8.0;net9.0;net10.0 + latest + disable + + 0.0.0-placeholder + + + + + + diff --git a/smoketests/nuget.config b/smoketests/nuget.config new file mode 100644 index 0000000..58e89a3 --- /dev/null +++ b/smoketests/nuget.config @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/smoketests/run-smoke-tests.ps1 b/smoketests/run-smoke-tests.ps1 new file mode 100644 index 0000000..0e9d050 --- /dev/null +++ b/smoketests/run-smoke-tests.ps1 @@ -0,0 +1,117 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Run NetDuid NuGet package smoke tests on Windows (all frameworks including net48). + +.DESCRIPTION + 1. Packs the NetDuid library into a local NuGet feed (smoketests\feed\). + 2. Restores and builds the SmokeTests consumer project against that feed. + 3. Runs the consumer for each supported framework and reports results. + + net48 validates the netstandard2.0 asset on .NET Framework — the TFM that cannot + be covered by a Linux runner. The script also covers net8.0, net9.0, and net10.0. + +.PARAMETER Version + SemVer string for the packed library. Defaults to "99.0.0-smoke". + +.EXAMPLE + .\smoketests\run-smoke-tests.ps1 + + .\smoketests\run-smoke-tests.ps1 -Version 4.1.0-preview +.NOTES + Exit code 0 = all frameworks passed; 1 = one or more frameworks failed. +#> + +param( + [string]$Version = "99.0.0-smoke" +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = $PSScriptRoot +$RepoRoot = (Get-Item $ScriptDir).Parent.FullName +$FeedDir = Join-Path $ScriptDir "feed" +$ConsumerProj = Join-Path $ScriptDir "SmokeTests\SmokeTests.csproj" +$LibProj = Join-Path $RepoRoot "src\NetDuid\NetDuid.csproj" +$Frameworks = @("net48", "net8.0", "net9.0", "net10.0") + +$OverallExit = 0 + +function Write-Header([string]$Text) { + Write-Host "`n$Text" -ForegroundColor Cyan +} +function Write-Ok([string]$Text) { Write-Host " OK $Text" -ForegroundColor Green } +function Write-Err([string]$Text) { Write-Host " ERR $Text" -ForegroundColor Red } + +# ── 1. Pack ─────────────────────────────────────────────────────────────────── + +Write-Header "Step 1 — Pack NetDuid $Version" + +if (Test-Path $FeedDir) { Remove-Item $FeedDir -Recurse -Force } +New-Item -ItemType Directory -Path $FeedDir | Out-Null + +dotnet pack $LibProj ` + --configuration Release ` + --output $FeedDir ` + "-p:VersionFromCI=$Version" ` + --verbosity quiet + +if ($LASTEXITCODE -ne 0) { Write-Err "Pack failed"; exit 1 } +Write-Ok "Packed to $FeedDir" + +# ── 2. Restore ──────────────────────────────────────────────────────────────── + +Write-Header "Step 2 — Restore SmokeTests consumer" + +dotnet restore $ConsumerProj ` + "-p:NetDuidVersion=$Version" ` + --verbosity quiet + +if ($LASTEXITCODE -ne 0) { Write-Err "Restore failed"; exit 1 } +Write-Ok "Restored" + +# ── 3. Build ────────────────────────────────────────────────────────────────── + +Write-Header "Step 3 — Build SmokeTests consumer (all frameworks)" + +dotnet build $ConsumerProj ` + --no-restore ` + --configuration Release ` + "-p:NetDuidVersion=$Version" ` + --verbosity quiet + +if ($LASTEXITCODE -ne 0) { Write-Err "Build failed"; exit 1 } +Write-Ok "Built" + +# ── 4. Run per framework ───────────────────────────────────────────────────── + +Write-Header "Step 4 — Run smoke tests" + +foreach ($fw in $Frameworks) { + Write-Host "`n Framework: $fw" + + dotnet run ` + --project $ConsumerProj ` + --framework $fw ` + --configuration Release ` + --no-build ` + "-p:NetDuidVersion=$Version" + + if ($LASTEXITCODE -eq 0) { + Write-Ok "$fw passed" + } else { + Write-Err "$fw FAILED" + $OverallExit = 1 + } +} + +# ── Summary ─────────────────────────────────────────────────────────────────── + +Write-Host "" +if ($OverallExit -eq 0) { + Write-Host "All smoke tests passed." -ForegroundColor Green +} else { + Write-Host "One or more frameworks FAILED." -ForegroundColor Red +} + +exit $OverallExit diff --git a/smoketests/run-smoke-tests.sh b/smoketests/run-smoke-tests.sh new file mode 100644 index 0000000..9bd2138 --- /dev/null +++ b/smoketests/run-smoke-tests.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# Run NetDuid NuGet package smoke tests on Linux/macOS. +# +# What this script does: +# 1. Packs the NetDuid library into a local NuGet feed (smoketests/feed/). +# 2. Restores and builds the SmokeTests consumer project against that feed. +# 3. Runs the consumer for each supported framework and reports results. +# +# net48 is excluded because .NET Framework does not run natively on Linux/macOS. +# Use run-smoke-tests.ps1 on Windows to cover net48 (netstandard2.0 asset). +# +# Usage: +# ./smoketests/run-smoke-tests.sh [VERSION] +# +# VERSION SemVer string for the packed library. Defaults to 99.0.0-smoke. +# +# Exit codes: +# 0 all frameworks passed +# 1 one or more frameworks failed + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +FEED_DIR="${SCRIPT_DIR}/feed" +CONSUMER_PROJ="${SCRIPT_DIR}/SmokeTests/SmokeTests.csproj" +LIB_PROJ="${REPO_ROOT}/src/NetDuid/NetDuid.csproj" +FRAMEWORKS=("net8.0" "net9.0" "net10.0") +VERSION="${1:-99.0.0-smoke}" + +# ── helpers ────────────────────────────────────────────────────────────────── + +header() { printf '\n\033[1;36m%s\033[0m\n' "$*"; } +ok() { printf '\033[0;32m ✓ %s\033[0m\n' "$*"; } +err() { printf '\033[0;31m ✗ %s\033[0m\n' "$*" >&2; } + +overall_exit=0 + +# ── 1. Pack ─────────────────────────────────────────────────────────────────── + +header "Step 1 — Pack NetDuid ${VERSION}" + +rm -rf "${FEED_DIR}" +mkdir -p "${FEED_DIR}" + +dotnet pack "${LIB_PROJ}" \ + --configuration Release \ + --output "${FEED_DIR}" \ + -p:VersionFromCI="${VERSION}" \ + --verbosity quiet + +ok "Packed to ${FEED_DIR}" + +# ── 2. Restore ──────────────────────────────────────────────────────────────── + +header "Step 2 — Restore SmokeTests consumer" + +dotnet restore "${CONSUMER_PROJ}" \ + -p:NetDuidVersion="${VERSION}" \ + --verbosity quiet + +ok "Restored" + +# ── 3. Build ────────────────────────────────────────────────────────────────── + +header "Step 3 — Build SmokeTests consumer (all frameworks)" + +dotnet build "${CONSUMER_PROJ}" \ + --no-restore \ + --configuration Release \ + -p:NetDuidVersion="${VERSION}" \ + --verbosity quiet + +ok "Built" + +# ── 4. Run per framework ───────────────────────────────────────────────────── + +header "Step 4 — Run smoke tests" + +for fw in "${FRAMEWORKS[@]}"; do + printf '\n Framework: %s\n' "${fw}" + if dotnet run \ + --project "${CONSUMER_PROJ}" \ + --framework "${fw}" \ + --configuration Release \ + --no-build \ + -p:NetDuidVersion="${VERSION}"; then + ok "${fw} passed" + else + err "${fw} FAILED" + overall_exit=1 + fi +done + +# ── Summary ─────────────────────────────────────────────────────────────────── + +echo "" +if [ "${overall_exit}" -eq 0 ]; then + printf '\033[1;32mAll smoke tests passed.\033[0m\n' +else + printf '\033[1;31mOne or more frameworks FAILED.\033[0m\n' >&2 +fi + +exit "${overall_exit}" diff --git a/src/.editorconfig b/src/.editorconfig deleted file mode 100644 index 39cd34b..0000000 --- a/src/.editorconfig +++ /dev/null @@ -1,600 +0,0 @@ -# .NET project .editorconfig as of 2023-08-24 - -root = false - -# Visual Studio Solutions -[*.sln] -indent_style = tab - -# Visual Studio Project Files -[*.csproj] -indent_size = 2 - -# C# files -[*.cs] - -#### Core EditorConfig Options #### - -# Indentation and spacing -indent_size = 4 -tab_width = 4 - -# New line preferences -end_of_line = crlf -insert_final_newline = true - -#### .NET Coding Conventions #### - -# Organize usings -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = true -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:suggestion -dotnet_style_prefer_compound_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion -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:warning - -# Parameter preferences -dotnet_code_quality_unused_parameters = all:suggestion - -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = None - -#### C# Coding Conventions #### -# var preferences -csharp_style_var_elsewhere = true:suggestion -csharp_style_var_for_built_in_types = true:warning -csharp_style_var_when_type_is_apparent = true:warning - - -# 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:suggestion -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_not_pattern = true:suggestion -csharp_style_prefer_pattern_matching = 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:warning -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:warning -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 = true -csharp_new_line_before_else = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all -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 = no_change -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 = true -csharp_preserve_single_line_statements = true -csharp_style_namespace_declarations = block_scoped:silent -csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_top_level_statements = true:silent -csharp_style_prefer_null_check_over_type_check = true:suggestion -csharp_style_prefer_local_over_anonymous_function = true:suggestion -csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion -csharp_style_prefer_tuple_swap = true:suggestion -csharp_style_prefer_utf8_string_literals = true:suggestion -csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent -csharp_style_prefer_extended_property_pattern = true:suggestion - -#### Naming styles #### -# Naming rules -dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces -dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion -dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces -dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase - -dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion -dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters -dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase - -dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods -dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties -dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.events_should_be_pascalcase.symbols = events -dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion -dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables -dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase - -dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion -dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants -dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase - -dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion -dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters -dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase - -dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields -dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion -dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields -dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase - -dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion -dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields -dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = _camelcase - -dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields -dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields -dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields -dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields -dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums -dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions -dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase - -# Symbol specifications - -dotnet_naming_symbols.interfaces.applicable_kinds = interface -dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interfaces.required_modifiers = - -dotnet_naming_symbols.enums.applicable_kinds = enum -dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.enums.required_modifiers = - -dotnet_naming_symbols.events.applicable_kinds = event -dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.events.required_modifiers = - -dotnet_naming_symbols.methods.applicable_kinds = method -dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.methods.required_modifiers = - -dotnet_naming_symbols.properties.applicable_kinds = property -dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.properties.required_modifiers = - -dotnet_naming_symbols.public_fields.applicable_kinds = field -dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_fields.required_modifiers = - -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_fields.required_modifiers = - -dotnet_naming_symbols.private_static_fields.applicable_kinds = field -dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_static_fields.required_modifiers = static - -dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum -dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types_and_namespaces.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 = - -dotnet_naming_symbols.type_parameters.applicable_kinds = namespace -dotnet_naming_symbols.type_parameters.applicable_accessibilities = * -dotnet_naming_symbols.type_parameters.required_modifiers = - -dotnet_naming_symbols.private_constant_fields.applicable_kinds = field -dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_constant_fields.required_modifiers = const - -dotnet_naming_symbols.local_variables.applicable_kinds = local -dotnet_naming_symbols.local_variables.applicable_accessibilities = local -dotnet_naming_symbols.local_variables.required_modifiers = - -dotnet_naming_symbols.local_constants.applicable_kinds = local -dotnet_naming_symbols.local_constants.applicable_accessibilities = local -dotnet_naming_symbols.local_constants.required_modifiers = const - -dotnet_naming_symbols.parameters.applicable_kinds = parameter -dotnet_naming_symbols.parameters.applicable_accessibilities = * -dotnet_naming_symbols.parameters.required_modifiers = - -dotnet_naming_symbols.public_constant_fields.applicable_kinds = field -dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_constant_fields.required_modifiers = const - -dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static - -dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static - -dotnet_naming_symbols.local_functions.applicable_kinds = local_function -dotnet_naming_symbols.local_functions.applicable_accessibilities = * -dotnet_naming_symbols.local_functions.required_modifiers = - -# Naming styles - -dotnet_naming_style.pascalcase.required_prefix = -dotnet_naming_style.pascalcase.required_suffix = -dotnet_naming_style.pascalcase.word_separator = -dotnet_naming_style.pascalcase.capitalization = pascal_case - -dotnet_naming_style.ipascalcase.required_prefix = I -dotnet_naming_style.ipascalcase.required_suffix = -dotnet_naming_style.ipascalcase.word_separator = -dotnet_naming_style.ipascalcase.capitalization = pascal_case - -dotnet_naming_style.tpascalcase.required_prefix = T -dotnet_naming_style.tpascalcase.required_suffix = -dotnet_naming_style.tpascalcase.word_separator = -dotnet_naming_style.tpascalcase.capitalization = pascal_case - -dotnet_naming_style._camelcase.required_prefix = _ -dotnet_naming_style._camelcase.required_suffix = -dotnet_naming_style._camelcase.word_separator = -dotnet_naming_style._camelcase.capitalization = camel_case - -dotnet_naming_style.camelcase.required_prefix = -dotnet_naming_style.camelcase.required_suffix = -dotnet_naming_style.camelcase.word_separator = -dotnet_naming_style.camelcase.capitalization = camel_case - -dotnet_naming_style.s_camelcase.required_prefix = s_ -dotnet_naming_style.s_camelcase.required_suffix = -dotnet_naming_style.s_camelcase.word_separator = -dotnet_naming_style.s_camelcase.capitalization = camel_case -dotnet_style_namespace_match_folder = true:suggestion -dotnet_style_allow_multiple_blank_lines_experimental = true:suggestion -dotnet_style_allow_statement_immediately_after_block_experimental = true:silent - -# AnalyzerId= AsyncUsageAnalyzers RuleNamespace= AsyncUsageAnalyzers -dotnet_diagnostic.UseConfigureAwait.severity = Warning - -# AnalyzerId= DocumentationAnalyzers RuleNamespace= DocumentationAnalyzers -dotnet_diagnostic.DOC100.severity = Warning -dotnet_diagnostic.DOC101.severity = Warning -dotnet_diagnostic.DOC102.severity = Warning -dotnet_diagnostic.DOC103.severity = Warning -dotnet_diagnostic.DOC104.severity = Warning -dotnet_diagnostic.DOC105.severity = Warning -dotnet_diagnostic.DOC106.severity = Warning -dotnet_diagnostic.DOC107.severity = Warning -dotnet_diagnostic.DOC108.severity = Warning -dotnet_diagnostic.DOC200.severity = Warning -dotnet_diagnostic.DOC201.severity = Warning -dotnet_diagnostic.DOC202.severity = Warning -dotnet_diagnostic.DOC203.severity = Warning -dotnet_diagnostic.DOC204.severity = Warning - -# AnalyzerId= Microsoft.Analyzers.ManagedCodeAnalysis RuleNamespace= Microsoft.Rules.Managed -dotnet_diagnostic.CA1001.severity = Warning -dotnet_diagnostic.CA1009.severity = Warning -dotnet_diagnostic.CA1016.severity = Warning -dotnet_diagnostic.CA1033.severity = Warning -dotnet_diagnostic.CA1049.severity = Warning -dotnet_diagnostic.CA1056.severity = Warning -dotnet_diagnostic.CA1060.severity = Warning -dotnet_diagnostic.CA1061.severity = Warning -dotnet_diagnostic.CA1063.severity = Warning -dotnet_diagnostic.CA1065.severity = Warning -dotnet_diagnostic.CA1301.severity = Warning -dotnet_diagnostic.CA1400.severity = Warning -dotnet_diagnostic.CA1401.severity = Warning -dotnet_diagnostic.CA1403.severity = Warning -dotnet_diagnostic.CA1404.severity = Warning -dotnet_diagnostic.CA1405.severity = Warning -dotnet_diagnostic.CA1410.severity = Warning -dotnet_diagnostic.CA1415.severity = Warning -dotnet_diagnostic.CA1821.severity = Warning -dotnet_diagnostic.CA1825.severity = Warning -dotnet_diagnostic.CA1900.severity = Warning -dotnet_diagnostic.CA1901.severity = Warning -dotnet_diagnostic.CA2000.severity = Warning -dotnet_diagnostic.CA2002.severity = Warning -dotnet_diagnostic.CA2100.severity = Warning -dotnet_diagnostic.CA2101.severity = Warning -dotnet_diagnostic.CA2108.severity = Warning -dotnet_diagnostic.CA2111.severity = Warning -dotnet_diagnostic.CA2112.severity = Warning -dotnet_diagnostic.CA2114.severity = Warning -dotnet_diagnostic.CA2116.severity = Warning -dotnet_diagnostic.CA2117.severity = Warning -dotnet_diagnostic.CA2122.severity = Warning -dotnet_diagnostic.CA2123.severity = Warning -dotnet_diagnostic.CA2124.severity = Warning -dotnet_diagnostic.CA2126.severity = Warning -dotnet_diagnostic.CA2131.severity = Warning -dotnet_diagnostic.CA2132.severity = Warning -dotnet_diagnostic.CA2133.severity = Warning -dotnet_diagnostic.CA2134.severity = Warning -dotnet_diagnostic.CA2137.severity = Warning -dotnet_diagnostic.CA2138.severity = Warning -dotnet_diagnostic.CA2140.severity = Warning -dotnet_diagnostic.CA2141.severity = Warning -dotnet_diagnostic.CA2146.severity = Warning -dotnet_diagnostic.CA2147.severity = Warning -dotnet_diagnostic.CA2149.severity = Warning -dotnet_diagnostic.CA2200.severity = Warning -dotnet_diagnostic.CA2202.severity = Warning -dotnet_diagnostic.CA2207.severity = Warning -dotnet_diagnostic.CA2212.severity = Warning -dotnet_diagnostic.CA2213.severity = Warning -dotnet_diagnostic.CA2214.severity = Warning -dotnet_diagnostic.CA2215.severity = Warning -dotnet_diagnostic.CA2216.severity = Warning -dotnet_diagnostic.CA2220.severity = Warning -dotnet_diagnostic.CA2229.severity = Warning -dotnet_diagnostic.CA2231.severity = Warning -dotnet_diagnostic.CA2232.severity = Warning -dotnet_diagnostic.CA2235.severity = Warning -dotnet_diagnostic.CA2236.severity = Warning -dotnet_diagnostic.CA2237.severity = Warning -dotnet_diagnostic.CA2238.severity = Warning -dotnet_diagnostic.CA2240.severity = Warning -dotnet_diagnostic.CA2241.severity = Warning -dotnet_diagnostic.CA2242.severity = Warning -dotnet_diagnostic.CA2253.severity = Warning -dotnet_diagnostic.CA2254.severity = Warning - -# AnalyzerId= Microsoft.CodeAnalysis.CSharp RuleNamespace= Microsoft.CodeAnalysis.CSharp -dotnet_diagnostic.CS8019.severity = Warning - -# AnalyzerId= Microsoft.CodeAnalysis.CSharp.Analyzers RuleNamespace= Microsoft.CodeAnalysis.CSharp.Analyzers -dotnet_diagnostic.RS1022.severity = Warning - -# AnalyzerId= Microsoft.CodeAnalysis.CSharp.CodeStyle RuleNamespace= Microsoft.CodeAnalysis.CSharp.CodeStyle -dotnet_diagnostic.IDE0055.severity = None -dotnet_diagnostic.IDE0079.severity = Warning -dotnet_diagnostic.IDE1006.severity = Warning - -# AnalyzerId= Microsoft.CodeAnalysis.CSharp.Features RuleNamespace= Microsoft.CodeAnalysis.CSharp.Features -dotnet_diagnostic.IDE0003.severity = None -dotnet_diagnostic.IDE0007.severity = Warning -dotnet_diagnostic.IDE0009.severity = None - -# AnalyzerId= StyleCop.Analyzers RuleNamespace= StyleCop.Analyzers -dotnet_diagnostic.SA1000.severity = None -dotnet_diagnostic.SA1003.severity = None -dotnet_diagnostic.SA1005.severity = None -dotnet_diagnostic.SA1008.severity = None -dotnet_diagnostic.SA1009.severity = None -dotnet_diagnostic.SA1012.severity = None -dotnet_diagnostic.SA1013.severity = None -dotnet_diagnostic.SA1025.severity = None -dotnet_diagnostic.SA1101.severity = None -dotnet_diagnostic.SA1108.severity = None -dotnet_diagnostic.SA1111.severity = None -dotnet_diagnostic.SA1116.severity = None -dotnet_diagnostic.SA1118.severity = None -dotnet_diagnostic.SA1123.severity = None -dotnet_diagnostic.SA1124.severity = None -dotnet_diagnostic.SA1127.severity = None -dotnet_diagnostic.SA1128.severity = None -dotnet_diagnostic.SA1137.severity = None -dotnet_diagnostic.SA1201.severity = None -dotnet_diagnostic.SA1202.severity = None -dotnet_diagnostic.SA1203.severity = None -dotnet_diagnostic.SA1204.severity = None -dotnet_diagnostic.SA1309.severity = None -dotnet_diagnostic.SA1412.severity = Error -dotnet_diagnostic.SA1413.severity = None -dotnet_diagnostic.SA1500.severity = None -dotnet_diagnostic.SA1501.severity = None -dotnet_diagnostic.SA1502.severity = None -dotnet_diagnostic.SA1504.severity = None -dotnet_diagnostic.SA1512.severity = None -dotnet_diagnostic.SA1513.severity = None -dotnet_diagnostic.SA1515.severity = None -dotnet_diagnostic.SA1516.severity = None - -# Documentation Rules (SA1600-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/DocumentationRules.md -dotnet_diagnostic.SA1600.severity = Warning # A C# code element is missing a documentation header. -dotnet_diagnostic.SA1601.severity = Warning # A C# partial element is missing a documentation header. -dotnet_diagnostic.SA1602.severity = Warning # An item within a C# enumeration is missing an Xml documentation header. -dotnet_diagnostic.SA1603.severity = Warning # The Xml within a C# element's document header is badly formed. -dotnet_diagnostic.SA1604.severity = Warning # The Xml header documentation for a C# element is missing a tag. -dotnet_diagnostic.SA1605.severity = Warning # The or tag within the documentation header for a C# code element is missing or empty. -dotnet_diagnostic.SA1606.severity = Warning # The tag within the documentation header for a C# code element is empty. -dotnet_diagnostic.SA1607.severity = Warning # The or tag within the documentation header for a C# code element is empty. -dotnet_diagnostic.SA1608.severity = Warning # The tag within an element's Xml header documentation contains the default text generated by Visual Studio during the creation of the element. -dotnet_diagnostic.SA1609.severity = Warning # The Xml header documentation for a C# property does not contain a tag. -dotnet_diagnostic.SA1610.severity = Warning # The Xml header documentation for a C# property contains an empty tag. -dotnet_diagnostic.SA1611.severity = Warning # A C# method, constructor, delegate or indexer element is missing documentation for one or more of its parameters. -dotnet_diagnostic.SA1612.severity = Warning # The documentation describing the parameters to a C# method, constructor, delegate or indexer element does not match the actual parameters on the element. -dotnet_diagnostic.SA1613.severity = Warning # A tag within a C# element's documentation header is missing a name attribute containing the name of the parameter. -dotnet_diagnostic.SA1614.severity = Warning # A tag within a C# element's documentation header is empty. -dotnet_diagnostic.SA1615.severity = Warning # A C# element is missing documentation for its return value. -dotnet_diagnostic.SA1616.severity = Warning # The tag within a C# element's documentation header is empty. -dotnet_diagnostic.SA1617.severity = Warning # A C# code element does not contain a return value, or returns void, but the documentation header for the element contains a tag. -dotnet_diagnostic.SA1618.severity = Warning # A generic C# element is missing documentation for one or more of its generic type parameters. -dotnet_diagnostic.SA1619.severity = Warning # A generic, partial C# element is missing documentation for one or more of its generic type parameters, and the documentation for the element contains a tag. -dotnet_diagnostic.SA1620.severity = Warning # The tags within the Xml header documentation for a generic C# element do not match the generic type parameters on the element. -dotnet_diagnostic.SA1621.severity = Warning # A tag within the Xml header documentation for a generic C# element is missing a name attribute, or contains an empty name attribute. -dotnet_diagnostic.SA1622.severity = Warning # A tag within the Xml header documentation for a generic C# element is empty. -dotnet_diagnostic.SA1623.severity = Warning # The documentation text within a C# property's tag does not match the accessors within the property. -dotnet_diagnostic.SA1624.severity = Warning # The documentation text within a C# property's tag takes into account all of the accessors within the property, but one of the accessors has limited access. -dotnet_diagnostic.SA1625.severity = Warning # The Xml documentation for a C# element contains two or more identical entries, indicating that the documentation has been copied and pasted. -dotnet_diagnostic.SA1626.severity = Warning # The C# code contains a single-line comment which begins with three forward slashes in a row. -dotnet_diagnostic.SA1627.severity = Warning # The Xml header documentation for a C# code element contains an empty tag. -dotnet_diagnostic.SA1628.severity = Warning # A section of the Xml header documentation for a C# element does not begin with a capital letter. -dotnet_diagnostic.SA1629.severity = None # A section of the Xml header documentation for a C# element does not end with a period (also known as a full stop). -dotnet_diagnostic.SA1630.severity = Warning # A section of the Xml header documentation for a C# element does not contain any whitespace between words. -dotnet_diagnostic.SA1631.severity = Warning # A section of the Xml header documentation for a C# element does not contain enough alphabetic characters. -dotnet_diagnostic.SA1632.severity = None # From StyleCop 4.5 this rule is disabled by default. -dotnet_diagnostic.SA1633.severity = None # A C# code file is missing a standard file header. -dotnet_diagnostic.SA1634.severity = Warning # The file header at the top of a C# code file is missing a copyright tag. -dotnet_diagnostic.SA1635.severity = Warning # The file header at the top of a C# code file is missing copyright text. -dotnet_diagnostic.SA1636.severity = Warning # The file header at the top of a C# code file does not contain the appropriate copyright text. -dotnet_diagnostic.SA1637.severity = Warning # The file header at the top of a C# code file is missing the file name. -dotnet_diagnostic.SA1638.severity = Warning # The file tag within the file header at the top of a C# code file does not contain the name of the file. -dotnet_diagnostic.SA1639.severity = Warning # The file header at the top of a C# code file does not contain a filled-in summary tag. -dotnet_diagnostic.SA1640.severity = Warning # The file header at the top of a C# code file does not contain company name text. -dotnet_diagnostic.SA1641.severity = Warning # The file header at the top of a C# code file does not contain the appropriate company name text. -dotnet_diagnostic.SA1642.severity = Warning # The XML documentation header for a C# constructor does not contain the appropriate summary text. -dotnet_diagnostic.SA1643.severity = Warning # The Xml documentation header for a C# finalizer does not contain the appropriate summary text. -dotnet_diagnostic.SA1644.severity = Warning # A section within the Xml documentation header for a C# element contains blank lines. -dotnet_diagnostic.SA1645.severity = Warning # An included Xml documentation file does not exist. -dotnet_diagnostic.SA1646.severity = Warning # An included Xml documentation link contains an invalid path. -dotnet_diagnostic.SA1647.severity = Warning # An include tag within an Xml documentation header does not contain valid file and path attribute. -dotnet_diagnostic.SA1648.severity = Warning # has been used on an element that doesn't inherit from a base class or implement an interface. -dotnet_diagnostic.SA1649.severity = Warning # The file name of a C# code file does not match the first type declared in the file. -dotnet_diagnostic.SA1650.severity = Warning # The element documentation for the element contains one or more spelling mistakes or unrecognized words. -dotnet_diagnostic.SA1651.severity = Warning # The documentation for the element contains one or more elements. -dotnet_diagnostic.SA1652.severity = None # This rule was moved to SA0001 - -# Sonar -dotnet_diagnostic.S107.severity = Suggestion # Methods should not have too many parameters -dotnet_diagnostic.S1210.severity = Suggestion # "Equals" and the comparison operators should be overridden when implementing "IComparable" -dotnet_diagnostic.S2436.severity = Suggestion # Types and methods should not have too many generic parameters -dotnet_diagnostic.S4457.severity = Warning # Parameter validation in "async"/"await" methods should be wrapped - -# C# Autogenerated code -[{**/obj/**,**/bin/**}] -# AnalyzerId= Microsoft.CodeAnalysis.CSharp RuleNamespace= Microsoft.CodeAnalysis.CSharp -dotnet_diagnostic.CS8019.severity = none - -# C# Tests files in Tests projects -[{**Tests/**Tests.cs,**Tests.**/**Tests.cs}] -# Reset StyleCop documentation comments -dotnet_diagnostic.SA1600.severity = None # A C# code element is missing a documentation header. -dotnet_diagnostic.SA1601.severity = None # A C# partial element is missing a documentation header. -dotnet_diagnostic.SA1602.severity = None # An item within a C# enumeration is missing an Xml documentation header. - -# Sonar -dotnet_diagnostic.S4114.severity = Suggestion # Methods should not have identical implementations - -# Microsoft.VisualStudio.Threading -dotnet_diagnostic.VSTHRD200.severity = None # Use Async suffix for async methods - -# Misc -dotnet_diagnostic.CS1591.severity = None # Missing XML comment for publicly visible type or member 'Type_or_Member' -dotnet_diagnostic.SA1600.severity = None # Elements should be documented -dotnet_diagnostic.CA1707.severity = None # Identifiers should not contain underscores -dotnet_diagnostic.RCS1192.severity = Warning # RCS1192: Unnecessary usage of verbatim string literal. diff --git a/src/.runsettings b/src/.runsettings new file mode 100644 index 0000000..07e43c4 --- /dev/null +++ b/src/.runsettings @@ -0,0 +1,29 @@ + + + + + + 0 + + + + + + + + + + .*\.Tests\.dll + .*\.Benchmarks\.dll + xunit\.execution\.* + xunit\.v3\.* + NSubstitute\.* + + + true + + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..943fbf7 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,29 @@ + + + 14.0 + enable + true + true + + + + Sandia National Laboratories + Apache-2.0 + https://github.com/sandialabs/NetDuid.git + true + true + + + + true + latest + true + + true + + + + + true + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..ceab246 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,15 @@ + + + + + false + true + + + + false + Exe + + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 0000000..94cba09 --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,24 @@ + + + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NetDuid.Benchmarks/DuidComparisonBenchmarks.cs b/src/NetDuid.Benchmarks/DuidComparisonBenchmarks.cs new file mode 100644 index 0000000..55149a8 --- /dev/null +++ b/src/NetDuid.Benchmarks/DuidComparisonBenchmarks.cs @@ -0,0 +1,120 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; + +namespace NetDuid.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net48)] +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net90)] +[SimpleJob(RuntimeMoniker.Net10_0)] +[MemoryDiagnoser] +public class DuidComparisonBenchmarks +{ + private static readonly byte[] LinkLayerPlusTimeBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + ]; + + private static readonly byte[] UuidBytes = + [ + 0x00, + 0x04, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + ]; + + private static readonly byte[] SameLengthDifferentBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0xff, + ]; + + private Duid _smaller = null!; + private Duid _larger = null!; + private Duid _sameLengthLarger = null!; + private object _boxedLarger = null!; + + [GlobalSetup] + public void Setup() + { + _smaller = new Duid(LinkLayerPlusTimeBytes); + _larger = new Duid(UuidBytes); + _sameLengthLarger = new Duid(SameLengthDifferentBytes); + _boxedLarger = new Duid(UuidBytes); + } + + [Benchmark(Baseline = true)] + public int CompareTo() + { + return _smaller.CompareTo(_larger); + } + + [Benchmark] + public int CompareToSameLength() + { + return _smaller.CompareTo(_sameLengthLarger); + } + + [Benchmark] + public bool OperatorLessThan() + { + return _smaller < _larger; + } + + [Benchmark] + public bool OperatorLessThanOrEqual() + { + return _smaller <= _larger; + } + + [Benchmark] + public bool OperatorGreaterThan() + { + return _larger > _smaller; + } + + [Benchmark] + public bool OperatorGreaterThanOrEqual() + { + return _larger >= _smaller; + } +} diff --git a/src/NetDuid.Benchmarks/DuidEqualityBenchmarks.cs b/src/NetDuid.Benchmarks/DuidEqualityBenchmarks.cs new file mode 100644 index 0000000..fcf84e4 --- /dev/null +++ b/src/NetDuid.Benchmarks/DuidEqualityBenchmarks.cs @@ -0,0 +1,142 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; + +namespace NetDuid.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net48)] +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net90)] +[SimpleJob(RuntimeMoniker.Net10_0)] +[MemoryDiagnoser] +public class DuidEqualityBenchmarks +{ + private static readonly byte[] LinkLayerPlusTimeBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + ]; + + private static readonly byte[] UuidBytes = + [ + 0x00, + 0x04, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + ]; + + private static readonly byte[] SameLengthDifferentBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0xff, + ]; + + private Duid _duid = null!; + private Duid _equalDuid = null!; + private Duid _differentLengthDuid = null!; + private Duid _sameLengthDifferentDuid = null!; + private object _boxedDuid = null!; + private object _nonDuidObject = null!; + + [GlobalSetup] + public void Setup() + { + _duid = new Duid(LinkLayerPlusTimeBytes); + _equalDuid = new Duid(LinkLayerPlusTimeBytes); + _differentLengthDuid = new Duid(UuidBytes); + _sameLengthDifferentDuid = new Duid(SameLengthDifferentBytes); + _boxedDuid = new Duid(LinkLayerPlusTimeBytes); + _nonDuidObject = "not a duid"; + } + + [Benchmark(Baseline = true)] + public int GetHashCodeBench() + { + return _duid.GetHashCode(); + } + + [Benchmark] + public int GetHashCodeDifferent() + { + return _sameLengthDifferentDuid.GetHashCode(); + } + + [Benchmark] + public bool EqualsDuid() + { + return _duid.Equals(_equalDuid); + } + + [Benchmark] + public bool EqualsSameLengthDifferentBytes() + { + return _duid.Equals(_sameLengthDifferentDuid); + } + + [Benchmark] + public bool EqualsDifferentLength() + { + return _duid.Equals(_differentLengthDuid); + } + + [Benchmark] + public bool EqualsObject() + { + return _duid.Equals(_boxedDuid); + } + + [Benchmark] + public bool EqualsObjectNonDuid() + { + return _duid.Equals(_nonDuidObject); + } + + [Benchmark] + public bool OperatorEquals() + { + return _duid == _equalDuid; + } + + [Benchmark] + public bool OperatorNotEquals() + { + return _duid != _differentLengthDuid; + } +} diff --git a/src/NetDuid.Benchmarks/DuidFactoryBenchmarks.cs b/src/NetDuid.Benchmarks/DuidFactoryBenchmarks.cs new file mode 100644 index 0000000..4a56de9 --- /dev/null +++ b/src/NetDuid.Benchmarks/DuidFactoryBenchmarks.cs @@ -0,0 +1,290 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; + +namespace NetDuid.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net48)] +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net90)] +[SimpleJob(RuntimeMoniker.Net10_0)] +[MemoryDiagnoser] +public class DuidFactoryBenchmarks +{ + private static readonly byte[] ThreeByteDuid = [0x00, 0x03, 0x01]; + + private static readonly byte[] LinkLayerPlusTimeBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + ]; + + private static readonly byte[] MaxDuid = + [ + 0x00, + 0x01, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3a, + 0x3b, + 0x3c, + 0x3d, + 0x3e, + 0x3f, + 0x40, + 0x41, + 0x42, + 0x43, + 0x44, + 0x45, + 0x46, + 0x47, + 0x48, + 0x49, + 0x4a, + 0x4b, + 0x4c, + 0x4d, + 0x4e, + 0x4f, + 0x50, + 0x51, + 0x52, + 0x53, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0x59, + 0x5a, + 0x5b, + 0x5c, + 0x5d, + 0x5e, + 0x5f, + 0x60, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x7e, + 0x7f, + 0x80, + 0x81, + ]; + + private const string ThreeByteUndelimited = "000301"; + private const string ThreeByteColonDelimited = "00:03:01"; + private const string ThreeByteDashDelimited = "00-03-01"; + private const string ThreeByteSpaceDelimited = "00 03 01"; + + private const string MidUndelimited = "000100000000000100112233445566"; + private const string MidColonDelimited = "00:01:00:00:00:00:00:01:00:11:22:33:44:55"; + private const string MidDashDelimited = "00-01-00-00-00-00-00-01-00-11-22-33-44-55"; + private const string MidSpaceDelimited = "00 01 00 00 00 00 00 01 00 11 22 33 44 55"; + + private const string MaxUndelimited = + "00010102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" + + "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" + + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" + + "8081"; + + private const string MaxColonDelimited = + "00:01:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f:" + + "20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:2f:30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:3e:3f:" + + "40:41:42:43:44:45:46:47:48:49:4a:4b:4c:4d:4e:4f:50:51:52:53:54:55:56:57:58:59:5a:5b:5c:5d:5e:5f:" + + "60:61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:78:79:7a:7b:7c:7d:7e:7f:" + + "80:81"; + + private const string MaxDashDelimited = + "00-01-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f-10-11-12-13-14-15-16-17-18-19-1a-1b-1c-1d-1e-1f-" + + "20-21-22-23-24-25-26-27-28-29-2a-2b-2c-2d-2e-2f-30-31-32-33-34-35-36-37-38-39-3a-3b-3c-3d-3e-3f-" + + "40-41-42-43-44-45-46-47-48-49-4a-4b-4c-4d-4e-4f-50-51-52-53-54-55-56-57-58-59-5a-5b-5c-5d-5e-5f-" + + "60-61-62-63-64-65-66-67-68-69-6a-6b-6c-6d-6e-6f-70-71-72-73-74-75-76-77-78-79-7a-7b-7c-7d-7e-7f-" + + "80-81"; + + private const string MaxSpaceDelimited = + "00 01 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f " + + "20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f " + + "40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f " + + "60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f " + + "80 81"; + + [ParamsSource(nameof(DuidBytesSource))] + public byte[] DuidBytes { get; set; } = null!; + + public static IEnumerable DuidBytesSource() + { + yield return ThreeByteDuid; + yield return LinkLayerPlusTimeBytes; + yield return MaxDuid; + } + + [Benchmark(Baseline = true)] + public Duid CreateFromBytes() + { + return new Duid(DuidBytes); + } + + [Benchmark] + public Duid CreateFromList() + { + return new Duid(new List(DuidBytes)); + } + + [Benchmark] + public Duid ParseUndelimited() + { + if (DuidBytes == ThreeByteDuid) + { + return Duid.Parse(ThreeByteUndelimited); + } + + return DuidBytes == LinkLayerPlusTimeBytes ? Duid.Parse(MidUndelimited) : Duid.Parse(MaxUndelimited); + } + + [Benchmark] + public Duid ParseColonDelimited() + { + if (DuidBytes == ThreeByteDuid) + { + return Duid.Parse(ThreeByteColonDelimited); + } + + return DuidBytes == LinkLayerPlusTimeBytes ? Duid.Parse(MidColonDelimited) : Duid.Parse(MaxColonDelimited); + } + + [Benchmark] + public Duid ParseDashDelimited() + { + if (DuidBytes == ThreeByteDuid) + { + return Duid.Parse(ThreeByteDashDelimited); + } + + return DuidBytes == LinkLayerPlusTimeBytes ? Duid.Parse(MidDashDelimited) : Duid.Parse(MaxDashDelimited); + } + + [Benchmark] + public Duid ParseSpaceDelimited() + { + if (DuidBytes == ThreeByteDuid) + { + return Duid.Parse(ThreeByteSpaceDelimited); + } + + return DuidBytes == LinkLayerPlusTimeBytes ? Duid.Parse(MidSpaceDelimited) : Duid.Parse(MaxSpaceDelimited); + } + + [Benchmark] + public bool TryParse() + { + if (DuidBytes == ThreeByteDuid) + { + return Duid.TryParse(ThreeByteColonDelimited, out _); + } + + return DuidBytes == LinkLayerPlusTimeBytes + ? Duid.TryParse(MidColonDelimited, out _) + : Duid.TryParse(MaxColonDelimited, out _); + } + + [Benchmark] + public bool TryParseFailure() + { + return Duid.TryParse("not-a-duid", out _); + } +} diff --git a/src/NetDuid.Benchmarks/DuidFormattableBenchmarks.cs b/src/NetDuid.Benchmarks/DuidFormattableBenchmarks.cs new file mode 100644 index 0000000..abffa12 --- /dev/null +++ b/src/NetDuid.Benchmarks/DuidFormattableBenchmarks.cs @@ -0,0 +1,217 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; + +namespace NetDuid.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net48)] +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net90)] +[SimpleJob(RuntimeMoniker.Net10_0)] +[MemoryDiagnoser] +public class DuidFormattableBenchmarks +{ + private static readonly byte[] ThreeByteDuid = [0x00, 0x03, 0x01]; + + private static readonly byte[] LinkLayerPlusTimeBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + ]; + + private static readonly byte[] UuidBytes = + [ + 0x00, + 0x04, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + ]; + + private static readonly byte[] MaxDuid = + [ + 0x00, + 0x01, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3a, + 0x3b, + 0x3c, + 0x3d, + 0x3e, + 0x3f, + 0x40, + 0x41, + 0x42, + 0x43, + 0x44, + 0x45, + 0x46, + 0x47, + 0x48, + 0x49, + 0x4a, + 0x4b, + 0x4c, + 0x4d, + 0x4e, + 0x4f, + 0x50, + 0x51, + 0x52, + 0x53, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0x59, + 0x5a, + 0x5b, + 0x5c, + 0x5d, + 0x5e, + 0x5f, + 0x60, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x7e, + 0x7f, + 0x80, + 0x81, + ]; + + [ParamsSource(nameof(DuidBytesSource))] + public byte[] DuidBytes { get; set; } = null!; + + [Params(null, "U", "U:", "U-", "L", "L:", "L-", ":", "-")] + public string Format { get; set; } = null!; + + public static IEnumerable DuidBytesSource() + { + yield return ThreeByteDuid; + yield return LinkLayerPlusTimeBytes; + yield return UuidBytes; + yield return MaxDuid; + } + + private Duid _duid = null!; + + [GlobalSetup] + public void Setup() + { + _duid = new Duid(DuidBytes); + } + + [Benchmark(Baseline = true)] + public string FormatDuid() + { + return _duid.ToString(Format, null); + } +} diff --git a/src/NetDuid.Benchmarks/DuidMiscBenchmarks.cs b/src/NetDuid.Benchmarks/DuidMiscBenchmarks.cs new file mode 100644 index 0000000..aad06b6 --- /dev/null +++ b/src/NetDuid.Benchmarks/DuidMiscBenchmarks.cs @@ -0,0 +1,256 @@ +using System.Runtime.Serialization; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; + +#pragma warning disable SYSLIB0050 + +namespace NetDuid.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net48)] +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net90)] +[SimpleJob(RuntimeMoniker.Net10_0)] +[MemoryDiagnoser] +public class DuidMiscBenchmarks +{ + private static readonly byte[] UndefinedDuid = [0x00, 0x05, 0x01]; + + private static readonly byte[] LinkLayerPlusTimeBytes = + [ + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + ]; + + private static readonly byte[] VendorAssignedDuid = [0x00, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; + + private static readonly byte[] LinkLayerDuid = [0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; + + private static readonly byte[] UuidDuid = + [ + 0x00, + 0x04, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + ]; + + private static readonly byte[] ThreeByteDuid = [0x00, 0x03, 0x01]; + + private static readonly byte[] MaxDuid = + [ + 0x00, + 0x01, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3a, + 0x3b, + 0x3c, + 0x3d, + 0x3e, + 0x3f, + 0x40, + 0x41, + 0x42, + 0x43, + 0x44, + 0x45, + 0x46, + 0x47, + 0x48, + 0x49, + 0x4a, + 0x4b, + 0x4c, + 0x4d, + 0x4e, + 0x4f, + 0x50, + 0x51, + 0x52, + 0x53, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0x59, + 0x5a, + 0x5b, + 0x5c, + 0x5d, + 0x5e, + 0x5f, + 0x60, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x7e, + 0x7f, + 0x80, + 0x81, + ]; + + [ParamsSource(nameof(DuidTypeBytesSource))] + public byte[] DuidTypeBytes { get; set; } = null!; + + public static IEnumerable DuidTypeBytesSource() + { + yield return UndefinedDuid; + yield return LinkLayerPlusTimeBytes; + yield return VendorAssignedDuid; + yield return LinkLayerDuid; + yield return UuidDuid; + } + + private Duid _duid = null!; + private Duid _threeByteDuid = null!; + private Duid _midDuid = null!; + private Duid _maxDuid = null!; + private SerializationInfo _serializationInfo = null!; + + [GlobalSetup] + public void Setup() + { + _duid = new Duid(DuidTypeBytes); + _threeByteDuid = new Duid(ThreeByteDuid); + _midDuid = new Duid(LinkLayerPlusTimeBytes); + _maxDuid = new Duid(MaxDuid); + _serializationInfo = new SerializationInfo(typeof(Duid), new FormatterConverter()); + } + + [Benchmark(Baseline = true)] + public DuidType DuidType() + { + return _duid.Type; + } + + [Benchmark] + public IReadOnlyCollection GetBytesThreeByte() + { + return _threeByteDuid.GetBytes(); + } + + [Benchmark] + public IReadOnlyCollection GetBytesMid() + { + return _midDuid.GetBytes(); + } + + [Benchmark] + public IReadOnlyCollection GetBytesMax() + { + return _maxDuid.GetBytes(); + } + + [Benchmark] + public void SerializeGetObjectData() + { + _duid.GetObjectData(_serializationInfo, new StreamingContext()); + } +} diff --git a/src/NetDuid.Benchmarks/NetDuid.Benchmarks.csproj b/src/NetDuid.Benchmarks/NetDuid.Benchmarks.csproj new file mode 100644 index 0000000..f46a56a --- /dev/null +++ b/src/NetDuid.Benchmarks/NetDuid.Benchmarks.csproj @@ -0,0 +1,11 @@ + + + net48;net8.0;net9.0;net10.0 + true + + + + + + + diff --git a/src/NetDuid.Benchmarks/Program.cs b/src/NetDuid.Benchmarks/Program.cs new file mode 100644 index 0000000..d680346 --- /dev/null +++ b/src/NetDuid.Benchmarks/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Running; + +namespace NetDuid.Benchmarks; + +internal static class Program +{ + private static void Main(string[] args) + { + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + } +} diff --git a/src/NetDuid.Benchmarks/packages.lock.json b/src/NetDuid.Benchmarks/packages.lock.json new file mode 100644 index 0000000..b23486e --- /dev/null +++ b/src/NetDuid.Benchmarks/packages.lock.json @@ -0,0 +1,907 @@ +{ + "version": 2, + "dependencies": { + ".NETFramework,Version=v4.8": { + "BenchmarkDotNet": { + "type": "Direct", + "requested": "[0.15.8, )", + "resolved": "0.15.8", + "contentHash": "paCfrWxSeHqn3rUZc0spYXVFnHCF0nzRhG0nOLnyTjZYs8spsimBaaNmb3vwqvALKIplbYq/TF393vYiYSnh/Q==", + "dependencies": { + "BenchmarkDotNet.Annotations": "0.15.8", + "CommandLineParser": "2.9.1", + "Gee.External.Capstone": "2.3.0", + "Iced": "1.21.0", + "Microsoft.CodeAnalysis.CSharp": "4.14.0", + "Microsoft.Diagnostics.Runtime": "3.1.512801", + "Microsoft.Diagnostics.Tracing.TraceEvent": "3.1.21", + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Microsoft.Win32.Registry": "5.0.0", + "Perfolizer": "[0.6.1]", + "System.Management": "9.0.5", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.Emit": "4.7.0", + "System.Reflection.Emit.Lightweight": "4.7.0", + "System.Threading.Tasks.Extensions": "4.6.3" + } + }, + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3" + } + }, + "BenchmarkDotNet.Annotations": { + "type": "Transitive", + "resolved": "0.15.8", + "contentHash": "hfucY0ycAsB0SsoaZcaAp9oq5wlWBJcylvEJb9pmvdYUx6PD6S4mDiYnZWjdjAlLhIpe/xtGCwzORfzAzPqvzA==" + }, + "CommandLineParser": { + "type": "Transitive", + "resolved": "2.9.1", + "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==" + }, + "Gee.External.Capstone": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" + }, + "Iced": { + "type": "Transitive", + "resolved": "1.21.0", + "contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "PC3tuwZYnC+idaPuoC/AZpEdwrtX7qFpmnrfQkgobGIWiYmGi5MCRtl5mx6QrfMGQpK78X2lfIEoZDLg/qnuHg==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "System.Buffers": "4.5.1", + "System.Collections.Immutable": "9.0.0", + "System.Memory": "4.5.5", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.Metadata": "9.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encoding.CodePages": "7.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "568a6wcTivauIhbeWcCwfWwIn7UV7MeHEBvFB2uzGIpM2OhJ4eM/FZ8KS0yhPoNxnSpjGzz7x7CIjTxhslojQA==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[4.14.0]", + "System.Buffers": "4.5.1", + "System.Collections.Immutable": "9.0.0", + "System.Memory": "4.5.5", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.Metadata": "9.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encoding.CodePages": "7.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.510501", + "contentHash": "juoqJYMDs+lRrrZyOkXXMImJHneCF23cuvO4waFRd2Ds7j+ZuGIPbJm0Y/zz34BdeaGiiwGWraMUlln05W1PCQ==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "System.Buffers": "4.5.1" + } + }, + "Microsoft.Diagnostics.Runtime": { + "type": "Transitive", + "resolved": "3.1.512801", + "contentHash": "0lMUDr2oxNZa28D6NH5BuSQEe5T9tZziIkvkD44YkkCGQXPJqvFjLq5ZQq1hYLl3RjQJrY+hR0jFgap+EWPDTw==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.410101", + "System.Collections.Immutable": "6.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.Diagnostics.Tracing.TraceEvent": { + "type": "Transitive", + "resolved": "3.1.21", + "contentHash": "/OrJFKaojSR6TkUKtwh8/qA9XWNtxLrXMqvEb89dBSKCWjaGVTbKMYodIUgF5deCEtmd6GXuRerciXGl5bhZ7Q==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.510501", + "Microsoft.Win32.Registry": "5.0.0", + "System.Collections.Immutable": "8.0.0", + "System.Reflection.Metadata": "8.0.0", + "System.Reflection.TypeExtensions": "4.7.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Json": "8.0.5" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==", + "dependencies": { + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0", + "System.Diagnostics.DiagnosticSource": "6.0.0", + "System.ValueTuple": "4.5.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.NETFramework.ReferenceAssemblies.net48": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Perfolizer": { + "type": "Transitive", + "resolved": "0.6.1", + "contentHash": "CR1QmWg4XYBd1Pb7WseP+sDmV8nGPwvmowKynExTqr3OuckIGVMhvmN4LC5PGzfXqDlR295+hz/T7syA1CxEqA==", + "dependencies": { + "Pragmastat": "3.2.4", + "System.Memory": "4.5.5" + } + }, + "Pragmastat": { + "type": "Transitive", + "resolved": "3.2.4", + "contentHash": "I5qFifWw/gaTQT52MhzjZpkm/JPlfjSeO/DTZJjO7+hTKI+0aGRgOgZ3NN6D96dDuuqbIAZSeA5RimtHjqrA2A==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Management": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "dependencies": { + "System.CodeDom": "9.0.5" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==" + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", + "dependencies": { + "System.Collections.Immutable": "9.0.0", + "System.Memory": "4.5.5" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "hWPhJxc453RCa8Z29O91EmfGeZIHX1ZH2A8L6lYQVSaKzku2DfArSfMEb1/MYYzPQRJZeu0c9dmYeJKxW5Fgng==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "8.0.0", + "System.Buffers": "4.5.1", + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "8.0.0", + "System.Threading.Tasks.Extensions": "4.5.4", + "System.ValueTuple": "4.5.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.1.2" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "netduid": { + "type": "Project", + "dependencies": { + "Microsoft.Bcl.HashCode": "[6.0.0, )" + } + }, + "Microsoft.Bcl.HashCode": { + "type": "CentralTransitive", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + } + }, + "net10.0": { + "BenchmarkDotNet": { + "type": "Direct", + "requested": "[0.15.8, )", + "resolved": "0.15.8", + "contentHash": "paCfrWxSeHqn3rUZc0spYXVFnHCF0nzRhG0nOLnyTjZYs8spsimBaaNmb3vwqvALKIplbYq/TF393vYiYSnh/Q==", + "dependencies": { + "BenchmarkDotNet.Annotations": "0.15.8", + "CommandLineParser": "2.9.1", + "Gee.External.Capstone": "2.3.0", + "Iced": "1.21.0", + "Microsoft.CodeAnalysis.CSharp": "4.14.0", + "Microsoft.Diagnostics.Runtime": "3.1.512801", + "Microsoft.Diagnostics.Tracing.TraceEvent": "3.1.21", + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Perfolizer": "[0.6.1]", + "System.Management": "9.0.5" + } + }, + "BenchmarkDotNet.Annotations": { + "type": "Transitive", + "resolved": "0.15.8", + "contentHash": "hfucY0ycAsB0SsoaZcaAp9oq5wlWBJcylvEJb9pmvdYUx6PD6S4mDiYnZWjdjAlLhIpe/xtGCwzORfzAzPqvzA==" + }, + "CommandLineParser": { + "type": "Transitive", + "resolved": "2.9.1", + "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==" + }, + "Gee.External.Capstone": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" + }, + "Iced": { + "type": "Transitive", + "resolved": "1.21.0", + "contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "PC3tuwZYnC+idaPuoC/AZpEdwrtX7qFpmnrfQkgobGIWiYmGi5MCRtl5mx6QrfMGQpK78X2lfIEoZDLg/qnuHg==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "568a6wcTivauIhbeWcCwfWwIn7UV7MeHEBvFB2uzGIpM2OhJ4eM/FZ8KS0yhPoNxnSpjGzz7x7CIjTxhslojQA==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[4.14.0]" + } + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.510501", + "contentHash": "juoqJYMDs+lRrrZyOkXXMImJHneCF23cuvO4waFRd2Ds7j+ZuGIPbJm0Y/zz34BdeaGiiwGWraMUlln05W1PCQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "6.0.0" + } + }, + "Microsoft.Diagnostics.Runtime": { + "type": "Transitive", + "resolved": "3.1.512801", + "contentHash": "0lMUDr2oxNZa28D6NH5BuSQEe5T9tZziIkvkD44YkkCGQXPJqvFjLq5ZQq1hYLl3RjQJrY+hR0jFgap+EWPDTw==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.410101" + } + }, + "Microsoft.Diagnostics.Tracing.TraceEvent": { + "type": "Transitive", + "resolved": "3.1.21", + "contentHash": "/OrJFKaojSR6TkUKtwh8/qA9XWNtxLrXMqvEb89dBSKCWjaGVTbKMYodIUgF5deCEtmd6GXuRerciXGl5bhZ7Q==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.510501", + "System.Reflection.TypeExtensions": "4.7.0" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==" + }, + "Perfolizer": { + "type": "Transitive", + "resolved": "0.6.1", + "contentHash": "CR1QmWg4XYBd1Pb7WseP+sDmV8nGPwvmowKynExTqr3OuckIGVMhvmN4LC5PGzfXqDlR295+hz/T7syA1CxEqA==", + "dependencies": { + "Pragmastat": "3.2.4" + } + }, + "Pragmastat": { + "type": "Transitive", + "resolved": "3.2.4", + "contentHash": "I5qFifWw/gaTQT52MhzjZpkm/JPlfjSeO/DTZJjO7+hTKI+0aGRgOgZ3NN6D96dDuuqbIAZSeA5RimtHjqrA2A==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "dependencies": { + "System.CodeDom": "9.0.5" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA==" + }, + "netduid": { + "type": "Project" + } + }, + "net8.0": { + "BenchmarkDotNet": { + "type": "Direct", + "requested": "[0.15.8, )", + "resolved": "0.15.8", + "contentHash": "paCfrWxSeHqn3rUZc0spYXVFnHCF0nzRhG0nOLnyTjZYs8spsimBaaNmb3vwqvALKIplbYq/TF393vYiYSnh/Q==", + "dependencies": { + "BenchmarkDotNet.Annotations": "0.15.8", + "CommandLineParser": "2.9.1", + "Gee.External.Capstone": "2.3.0", + "Iced": "1.21.0", + "Microsoft.CodeAnalysis.CSharp": "4.14.0", + "Microsoft.Diagnostics.Runtime": "3.1.512801", + "Microsoft.Diagnostics.Tracing.TraceEvent": "3.1.21", + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Perfolizer": "[0.6.1]", + "System.Management": "9.0.5" + } + }, + "BenchmarkDotNet.Annotations": { + "type": "Transitive", + "resolved": "0.15.8", + "contentHash": "hfucY0ycAsB0SsoaZcaAp9oq5wlWBJcylvEJb9pmvdYUx6PD6S4mDiYnZWjdjAlLhIpe/xtGCwzORfzAzPqvzA==" + }, + "CommandLineParser": { + "type": "Transitive", + "resolved": "2.9.1", + "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==" + }, + "Gee.External.Capstone": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" + }, + "Iced": { + "type": "Transitive", + "resolved": "1.21.0", + "contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "PC3tuwZYnC+idaPuoC/AZpEdwrtX7qFpmnrfQkgobGIWiYmGi5MCRtl5mx6QrfMGQpK78X2lfIEoZDLg/qnuHg==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "System.Collections.Immutable": "9.0.0", + "System.Reflection.Metadata": "9.0.0" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "568a6wcTivauIhbeWcCwfWwIn7UV7MeHEBvFB2uzGIpM2OhJ4eM/FZ8KS0yhPoNxnSpjGzz7x7CIjTxhslojQA==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[4.14.0]", + "System.Collections.Immutable": "9.0.0", + "System.Reflection.Metadata": "9.0.0" + } + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.510501", + "contentHash": "juoqJYMDs+lRrrZyOkXXMImJHneCF23cuvO4waFRd2Ds7j+ZuGIPbJm0Y/zz34BdeaGiiwGWraMUlln05W1PCQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "6.0.0" + } + }, + "Microsoft.Diagnostics.Runtime": { + "type": "Transitive", + "resolved": "3.1.512801", + "contentHash": "0lMUDr2oxNZa28D6NH5BuSQEe5T9tZziIkvkD44YkkCGQXPJqvFjLq5ZQq1hYLl3RjQJrY+hR0jFgap+EWPDTw==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.410101" + } + }, + "Microsoft.Diagnostics.Tracing.TraceEvent": { + "type": "Transitive", + "resolved": "3.1.21", + "contentHash": "/OrJFKaojSR6TkUKtwh8/qA9XWNtxLrXMqvEb89dBSKCWjaGVTbKMYodIUgF5deCEtmd6GXuRerciXGl5bhZ7Q==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.510501" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==" + }, + "Perfolizer": { + "type": "Transitive", + "resolved": "0.6.1", + "contentHash": "CR1QmWg4XYBd1Pb7WseP+sDmV8nGPwvmowKynExTqr3OuckIGVMhvmN4LC5PGzfXqDlR295+hz/T7syA1CxEqA==", + "dependencies": { + "Pragmastat": "3.2.4" + } + }, + "Pragmastat": { + "type": "Transitive", + "resolved": "3.2.4", + "contentHash": "I5qFifWw/gaTQT52MhzjZpkm/JPlfjSeO/DTZJjO7+hTKI+0aGRgOgZ3NN6D96dDuuqbIAZSeA5RimtHjqrA2A==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "dependencies": { + "System.CodeDom": "9.0.5" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", + "dependencies": { + "System.Collections.Immutable": "9.0.0" + } + }, + "netduid": { + "type": "Project" + } + }, + "net9.0": { + "BenchmarkDotNet": { + "type": "Direct", + "requested": "[0.15.8, )", + "resolved": "0.15.8", + "contentHash": "paCfrWxSeHqn3rUZc0spYXVFnHCF0nzRhG0nOLnyTjZYs8spsimBaaNmb3vwqvALKIplbYq/TF393vYiYSnh/Q==", + "dependencies": { + "BenchmarkDotNet.Annotations": "0.15.8", + "CommandLineParser": "2.9.1", + "Gee.External.Capstone": "2.3.0", + "Iced": "1.21.0", + "Microsoft.CodeAnalysis.CSharp": "4.14.0", + "Microsoft.Diagnostics.Runtime": "3.1.512801", + "Microsoft.Diagnostics.Tracing.TraceEvent": "3.1.21", + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Perfolizer": "[0.6.1]", + "System.Management": "9.0.5" + } + }, + "BenchmarkDotNet.Annotations": { + "type": "Transitive", + "resolved": "0.15.8", + "contentHash": "hfucY0ycAsB0SsoaZcaAp9oq5wlWBJcylvEJb9pmvdYUx6PD6S4mDiYnZWjdjAlLhIpe/xtGCwzORfzAzPqvzA==" + }, + "CommandLineParser": { + "type": "Transitive", + "resolved": "2.9.1", + "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==" + }, + "Gee.External.Capstone": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" + }, + "Iced": { + "type": "Transitive", + "resolved": "1.21.0", + "contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "PC3tuwZYnC+idaPuoC/AZpEdwrtX7qFpmnrfQkgobGIWiYmGi5MCRtl5mx6QrfMGQpK78X2lfIEoZDLg/qnuHg==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "568a6wcTivauIhbeWcCwfWwIn7UV7MeHEBvFB2uzGIpM2OhJ4eM/FZ8KS0yhPoNxnSpjGzz7x7CIjTxhslojQA==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[4.14.0]" + } + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.510501", + "contentHash": "juoqJYMDs+lRrrZyOkXXMImJHneCF23cuvO4waFRd2Ds7j+ZuGIPbJm0Y/zz34BdeaGiiwGWraMUlln05W1PCQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "6.0.0" + } + }, + "Microsoft.Diagnostics.Runtime": { + "type": "Transitive", + "resolved": "3.1.512801", + "contentHash": "0lMUDr2oxNZa28D6NH5BuSQEe5T9tZziIkvkD44YkkCGQXPJqvFjLq5ZQq1hYLl3RjQJrY+hR0jFgap+EWPDTw==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.410101" + } + }, + "Microsoft.Diagnostics.Tracing.TraceEvent": { + "type": "Transitive", + "resolved": "3.1.21", + "contentHash": "/OrJFKaojSR6TkUKtwh8/qA9XWNtxLrXMqvEb89dBSKCWjaGVTbKMYodIUgF5deCEtmd6GXuRerciXGl5bhZ7Q==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.510501" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==" + }, + "Perfolizer": { + "type": "Transitive", + "resolved": "0.6.1", + "contentHash": "CR1QmWg4XYBd1Pb7WseP+sDmV8nGPwvmowKynExTqr3OuckIGVMhvmN4LC5PGzfXqDlR295+hz/T7syA1CxEqA==", + "dependencies": { + "Pragmastat": "3.2.4" + } + }, + "Pragmastat": { + "type": "Transitive", + "resolved": "3.2.4", + "contentHash": "I5qFifWw/gaTQT52MhzjZpkm/JPlfjSeO/DTZJjO7+hTKI+0aGRgOgZ3NN6D96dDuuqbIAZSeA5RimtHjqrA2A==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "dependencies": { + "System.CodeDom": "9.0.5" + } + }, + "netduid": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/src/NetDuid.Tests/Duid.FactoryTests.cs b/src/NetDuid.Tests/Duid.FactoryTests.cs index ac6a4d1..e1893b3 100644 --- a/src/NetDuid.Tests/Duid.FactoryTests.cs +++ b/src/NetDuid.Tests/Duid.FactoryTests.cs @@ -1,5 +1,4 @@ -using System; -using Xunit; +using Xunit; namespace NetDuid.Tests { @@ -86,7 +85,10 @@ public static TheoryData Parse_String_Invalid_Test_TestCases() // mixed delimiters — delimited regex backreference \k requires a consistent delimiter AddTestCase("AB:CD-EF"); AddTestCase("AB-CD:EF"); + AddTestCase("AB:CD EF"); AddTestCase("AB CD:EF"); + AddTestCase("AB-CD EF"); + AddTestCase("AB CD-EF"); // structural problems AddTestCase(":AB:CD:EF"); diff --git a/src/NetDuid.Tests/Duid.IComparableTests.cs b/src/NetDuid.Tests/Duid.IComparableTests.cs index 70c81e0..3b0ebdb 100644 --- a/src/NetDuid.Tests/Duid.IComparableTests.cs +++ b/src/NetDuid.Tests/Duid.IComparableTests.cs @@ -1,5 +1,4 @@ -using System; -using Xunit; +using Xunit; namespace NetDuid.Tests { @@ -11,24 +10,24 @@ public static TheoryData Compare_Duid_Test_TestCases() { var theoryData = new TheoryData(); - var referenceDuid = new Duid(new byte[] { 0x00, 0x00, 0xff }); + var referenceDuid = new Duid([0x00, 0x00, 0xff]); AddTestCase(0, referenceDuid, referenceDuid); // by reference AddTestCase(1, referenceDuid, null); // null case — non-null is greater than null per IComparable contract // equality - AddCommutativeTestCases(0, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00 }); - AddCommutativeTestCases(0, new byte[] { 0xFF, 0x00, 0x80 }, new byte[] { 0xFF, 0x00, 0x80 }); + AddCommutativeTestCases(0, [0x00, 0x00, 0x00], [0x00, 0x00, 0x00]); + AddCommutativeTestCases(0, [0xFF, 0x00, 0x80], [0xFF, 0x00, 0x80]); // by length AddCommutativeTestCases(-1, new byte[3], new byte[4]); AddCommutativeTestCases(-1, new byte[3], new byte[130]); // by length, then big endian value - AddCommutativeTestCases(-1, new byte[] { 0xFF, 0x00, 0x00 }, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }); - AddCommutativeTestCases(-1, new byte[] { 0xFF, 0xFF, 0xFF }, new byte[] { 0xFF, 0x00, 0x00, 0x00 }); - AddCommutativeTestCases(-1, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x01 }); - AddCommutativeTestCases(-1, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x80, 0x00, 0x00 }); + AddCommutativeTestCases(-1, [0xFF, 0x00, 0x00], [0xFF, 0xFF, 0xFF, 0xFF]); + AddCommutativeTestCases(-1, [0xFF, 0xFF, 0xFF], [0xFF, 0x00, 0x00, 0x00]); + AddCommutativeTestCases(-1, [0x00, 0x00, 0x00], [0x00, 0x00, 0x01]); + AddCommutativeTestCases(-1, [0x00, 0x00, 0x00], [0x80, 0x00, 0x00]); return theoryData; @@ -103,7 +102,7 @@ public void Compare_Object_Test(int expectedSign, Duid thisDuid, object other) public void Compare_Object_NotDuid_Throws_ArgumentException_Test(object other) { // Arrange - var duid = new Duid(new byte[] { 0x00, 0x00, 0xff }); + var duid = new Duid([0x00, 0x00, 0xff]); // Act // Assert diff --git a/src/NetDuid.Tests/Duid.IEquatableTests.cs b/src/NetDuid.Tests/Duid.IEquatableTests.cs index efd879d..d4d1311 100644 --- a/src/NetDuid.Tests/Duid.IEquatableTests.cs +++ b/src/NetDuid.Tests/Duid.IEquatableTests.cs @@ -11,21 +11,21 @@ public static TheoryData Equal_Duid_Test_TestCases() var theoryData = new TheoryData(); // reference equality - var referenceDuid = new Duid(new byte[] { 0x00, 0x00, 0xff }); + var referenceDuid = new Duid([0x00, 0x00, 0xff]); AddTestCase(true, referenceDuid, referenceDuid); // by reference AddTestCase(false, referenceDuid, null); // null case // equality - AddCommutativeTestCases(true, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00 }); - AddCommutativeTestCases(true, new byte[] { 0xFF, 0x00, 0x80 }, new byte[] { 0xFF, 0x00, 0x80 }); + AddCommutativeTestCases(true, [0x00, 0x00, 0x00], [0x00, 0x00, 0x00]); + AddCommutativeTestCases(true, [0xFF, 0x00, 0x80], [0xFF, 0x00, 0x80]); // inequality AddCommutativeTestCases(false, new byte[3], new byte[4]); AddCommutativeTestCases(false, new byte[3], new byte[130]); - AddCommutativeTestCases(false, new byte[] { 0xFF, 0x00, 0x00 }, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }); - AddCommutativeTestCases(false, new byte[] { 0xFF, 0xFF, 0xFF }, new byte[] { 0xFF, 0x00, 0x00, 0x00 }); - AddCommutativeTestCases(false, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x01 }); - AddCommutativeTestCases(false, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x80, 0x00, 0x00 }); + AddCommutativeTestCases(false, [0xFF, 0x00, 0x00], [0xFF, 0xFF, 0xFF, 0xFF]); + AddCommutativeTestCases(false, [0xFF, 0xFF, 0xFF], [0xFF, 0x00, 0x00, 0x00]); + AddCommutativeTestCases(false, [0x00, 0x00, 0x00], [0x00, 0x00, 0x01]); + AddCommutativeTestCases(false, [0x00, 0x00, 0x00], [0x80, 0x00, 0x00]); return theoryData; @@ -74,10 +74,10 @@ public void Equal_Object_Test(bool expectedEqual, Duid thisDuid, Duid otherDuid) [InlineData("string")] [InlineData(42)] [InlineData(null)] - public void Equal_Object_NotDuid_Throws_ArgumentException_Test(object other) + public void Equal_Object_NotDuid_Returns_False_Test(object other) { // Arrange - var duid = new Duid(new byte[] { 0x00, 0x00, 0xff }); + var duid = new Duid([0x00, 0x00, 0xff]); // Act var result = duid.Equals(other); @@ -113,6 +113,40 @@ public void HashCode_EquivalentDuid_HashEqual_Test(byte[] duidBytes) // Assert Assert.Equal(duidA.GetHashCode(), duidB.GetHashCode()); } + + [Fact] + public void HashCode_SameInstance_ReturnsStableHash_Test() + { + // Arrange + var duid = new Duid([0x00, 0x01, 0x02, 0x03]); + + // Act + var hash1 = duid.GetHashCode(); + var hash2 = duid.GetHashCode(); + var hash3 = duid.GetHashCode(); + + // Assert + Assert.Equal(hash1, hash2); + Assert.Equal(hash2, hash3); + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x01, 0x02 }, new byte[] { 0x00, 0x01, 0x03 })] + [InlineData(new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0xFF, 0xFF, 0xFF })] + [InlineData(new byte[] { 0x00, 0x01, 0x02 }, new byte[] { 0x00, 0x01, 0x02, 0x03 })] + public void HashCode_DifferentDuids_DifferentHashes_Test(byte[] bytesA, byte[] bytesB) + { + // Arrange + var duidA = new Duid(bytesA); + var duidB = new Duid(bytesB); + + // Act + var hashA = duidA.GetHashCode(); + var hashB = duidB.GetHashCode(); + + // Assert + Assert.NotEqual(hashA, hashB); + } #endregion } } diff --git a/src/NetDuid.Tests/Duid.IFormattableTests.cs b/src/NetDuid.Tests/Duid.IFormattableTests.cs index c4959ff..cf26cc2 100644 --- a/src/NetDuid.Tests/Duid.IFormattableTests.cs +++ b/src/NetDuid.Tests/Duid.IFormattableTests.cs @@ -1,5 +1,4 @@ -using System; -using Xunit; +using Xunit; namespace NetDuid.Tests { @@ -88,33 +87,29 @@ public void ToString_String_IFormatProvider_Test(string expectedString, string f public static TheoryData ToString_InvalidFormat_ThrowsFormatException_Test_TestCases() { - var theoryData = new TheoryData(); - - // format string too long (> 2 characters) - theoryData.Add("ABC"); - theoryData.Add("U:X"); - theoryData.Add("UU:"); - - // single character — not in the valid set (U, u, L, l, :, -) - theoryData.Add("X"); - theoryData.Add("A"); - theoryData.Add("1"); - theoryData.Add("?"); - - // two characters — first character not U, u, L, or l - theoryData.Add("X:"); - theoryData.Add("1-"); - theoryData.Add("AU"); - - // two characters — second character not ':' or '-' - // note: space is a valid parse delimiter but not a valid format delimiter - theoryData.Add("UX"); - theoryData.Add("LA"); - theoryData.Add("U1"); - theoryData.Add("L "); - theoryData.Add("U."); - - return theoryData; + return + [ + // format string too long (> 2 characters) + "ABC", + "U:X", + "UU:", + // single character — not in the valid set (U, u, L, l, :, -) + "X", + "A", + "1", + "?", + // two characters — first character not U, u, L, or l + "X:", + "1-", + "AU", + // two characters — second character not ':' or '-' + // note: space is a valid parse delimiter but not a valid format delimiter + "UX", + "LA", + "U1", + "L ", + "U.", + ]; } [Theory] @@ -122,7 +117,7 @@ public static TheoryData ToString_InvalidFormat_ThrowsFormatException_Te public void ToString_InvalidFormat_ThrowsFormatException_Test(string invalidFormat) { // Arrange - var duid = new Duid(new byte[] { 0xAB, 0xCD, 0xEF }); + var duid = new Duid([0xAB, 0xCD, 0xEF]); // Act // Assert diff --git a/src/NetDuid.Tests/Duid.IParsableTests.cs b/src/NetDuid.Tests/Duid.IParsableTests.cs index c9744c3..f65e0c9 100644 --- a/src/NetDuid.Tests/Duid.IParsableTests.cs +++ b/src/NetDuid.Tests/Duid.IParsableTests.cs @@ -84,6 +84,10 @@ public static TheoryData Try_Parse_String_IFormatProvider_Invalid_Test_T // mixed delimiters — delimited regex backreference \k requires a consistent delimiter AddTestCase("AB:CD-EF"); AddTestCase("AB-CD:EF"); + AddTestCase("AB:CD EF"); + AddTestCase("AB CD:EF"); + AddTestCase("AB-CD EF"); + AddTestCase("AB CD-EF"); // structural problems AddTestCase(":AB:CD:EF"); diff --git a/src/NetDuid.Tests/Duid.ISerializableTests.cs b/src/NetDuid.Tests/Duid.ISerializableTests.cs index 6c4a4f7..14cf908 100644 --- a/src/NetDuid.Tests/Duid.ISerializableTests.cs +++ b/src/NetDuid.Tests/Duid.ISerializableTests.cs @@ -1,6 +1,4 @@ -#if !NET9_0_OR_GREATER -using System; -using System.IO; +#if NET48 using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; @@ -32,25 +30,23 @@ public static TheoryData Serializable_Test_TestCases() public void Serializable_Test(Duid duid) { // Arrange - using (var stream = new MemoryStream()) - { + using var stream = new MemoryStream(); #if !NET48 #pragma warning disable SYSLIB0011 #endif - var formatter = new BinaryFormatter(); + var formatter = new BinaryFormatter(); - // Act - formatter.Serialize(stream, duid); // serialize - stream.Seek(0, SeekOrigin.Begin); - var result = formatter.Deserialize(stream) as Duid; // deserialize + // Act + formatter.Serialize(stream, duid); // serialize + stream.Seek(0, SeekOrigin.Begin); + var result = formatter.Deserialize(stream) as Duid; // deserialize #if !NET48 #pragma warning restore SYSLIB0011 #endif - // Assert - Assert.NotNull(result); - Assert.Equal(duid, result); - } + // Assert + Assert.NotNull(result); + Assert.Equal(duid, result); } [Theory] @@ -58,25 +54,23 @@ public void Serializable_Test(Duid duid) public void Serializable_GetHashCode_Test(Duid duid) { // Arrange - using (var stream = new MemoryStream()) - { + using var stream = new MemoryStream(); #if !NET48 #pragma warning disable SYSLIB0011 #endif - var formatter = new BinaryFormatter(); + var formatter = new BinaryFormatter(); - // Act - formatter.Serialize(stream, duid); - stream.Seek(0, SeekOrigin.Begin); - var result = formatter.Deserialize(stream) as Duid; + // Act + formatter.Serialize(stream, duid); + stream.Seek(0, SeekOrigin.Begin); + var result = formatter.Deserialize(stream) as Duid; #if !NET48 #pragma warning restore SYSLIB0011 #endif - // Assert - Assert.NotNull(result); - Assert.Equal(duid.GetHashCode(), result.GetHashCode()); - } + // Assert + Assert.NotNull(result); + Assert.Equal(duid.GetHashCode(), result.GetHashCode()); } #endregion @@ -100,12 +94,12 @@ public void Deserialize_UnrecognizedVersion_ThrowsSerializationException_Test() var ctor = typeof(Duid).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, - new[] { typeof(SerializationInfo), typeof(StreamingContext) }, + [typeof(SerializationInfo), typeof(StreamingContext)], null ); // Act - var exception = Assert.Throws(() => ctor.Invoke(new object[] { info, context })); + var exception = Assert.Throws(() => ctor.Invoke([info, context])); // Assert Assert.IsType(exception.InnerException); @@ -133,12 +127,12 @@ public void Deserialize_NullByteArray_ThrowsSerializationException_Test() var ctor = typeof(Duid).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, - new[] { typeof(SerializationInfo), typeof(StreamingContext) }, + [typeof(SerializationInfo), typeof(StreamingContext)], null ); // Act - var exception = Assert.Throws(() => ctor.Invoke(new object[] { info, context })); + var exception = Assert.Throws(() => ctor.Invoke([info, context])); // Assert Assert.IsType(exception.InnerException); diff --git a/src/NetDuid.Tests/Duid.OperatorsTests.cs b/src/NetDuid.Tests/Duid.OperatorsTests.cs index b17f78e..7235f4e 100644 --- a/src/NetDuid.Tests/Duid.OperatorsTests.cs +++ b/src/NetDuid.Tests/Duid.OperatorsTests.cs @@ -88,13 +88,14 @@ public static TheoryData Operators_Duid_Test_TestCases() TestCase(1, nonNullDuid, null); TestCase(-1, null, nonNullDuid); - // non-null operands (in theory based on CompareTo) + // non-null operands (manually computed: length-first, then byte-by-byte) var otherDuidBytes = new byte[] { 0xFF, 0x00, 0x80 }; var otherDuid = new Duid(otherDuidBytes); - TestCase(nonNullDuid.CompareTo(otherDuid), nonNullDuid, otherDuid); - TestCase(otherDuid.CompareTo(nonNullDuid), otherDuid, nonNullDuid); - TestCase(otherDuid.CompareTo(otherDuid), otherDuid, otherDuid); + // both same length (3), first byte: 0x00 < 0xFF → nonNullDuid < otherDuid + TestCase(-1, nonNullDuid, otherDuid); + TestCase(1, otherDuid, nonNullDuid); + TestCase(0, otherDuid, otherDuid); return theoryData; diff --git a/src/NetDuid.Tests/DuidTests.cs b/src/NetDuid.Tests/DuidTests.cs index b87b417..f835c6d 100644 --- a/src/NetDuid.Tests/DuidTests.cs +++ b/src/NetDuid.Tests/DuidTests.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using NetDuid; using NetDuid.Tests.XunitSerializers; using Xunit; @@ -110,6 +108,19 @@ public void Constructor_Bytes_NullInput_Throws_ArgumentNullException_Test() Assert.Throws(() => new Duid(null)); } + [Fact] + public void Constructor_ListByte_CreatesValidDuid_Test() + { + // Arrange + var bytes = new List { 0x00, 0x01, 0xAA, 0xBB, 0xCC }; + + // Act + var duid = new Duid(bytes); + + // Assert + Assert.Equal(bytes, duid.GetBytes()); + } + #endregion #region DuidType @@ -216,7 +227,7 @@ public void GetBytes_ReturnsIReadOnlyCollection_Test(byte[] inputBytes) var result = duid.GetBytes(); // Assert - Assert.IsAssignableFrom>(result); + Assert.IsType>(result, exactMatch: false); Assert.Equal(inputBytes.Length, result.Count); } @@ -238,7 +249,7 @@ public void GetBytes_ReturnsCorrectBytes_Test(byte[] inputBytes) public void GetBytes_ReturnsReadOnlyView_Test() { // Arrange - var duid = new Duid(new byte[] { 0x00, 0x01, 0x02 }); + var duid = new Duid([0x00, 0x01, 0x02]); // Act var result = duid.GetBytes(); diff --git a/src/NetDuid.Tests/DuidTypeTests.cs b/src/NetDuid.Tests/DuidTypeTests.cs index 3a14210..3253301 100644 --- a/src/NetDuid.Tests/DuidTypeTests.cs +++ b/src/NetDuid.Tests/DuidTypeTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; +using Xunit; namespace NetDuid.Tests { diff --git a/src/NetDuid.Tests/NetDuid.Tests.csproj b/src/NetDuid.Tests/NetDuid.Tests.csproj index dcb2410..bdf9eeb 100644 --- a/src/NetDuid.Tests/NetDuid.Tests.csproj +++ b/src/NetDuid.Tests/NetDuid.Tests.csproj @@ -1,21 +1,33 @@  + + Unit test package for NetDuid + The Production Tools Team + Sandia National Laboratories + Copyright 2026 National Technology & Engineering Solutions of Sandia, LLC + (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains + certain rights in this software + net48;net8.0;net9.0;net10.0 + 14.0 false true Exe - bin\ + bin/ $(OutputPath)$(AssemblyName).xml true NetDuid Tests Sandia National Laboratories The Production Tools Team - Copyright 2025 National Technology &amp; Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. + Copyright 2026 National Technology & Engineering Solutions of Sandia, LLC + (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains + certain rights in this software. - + true false @@ -45,7 +57,7 @@ - + stylecop.json @@ -53,33 +65,41 @@ - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/NetDuid.Tests/StaticTestData.cs b/src/NetDuid.Tests/StaticTestData.cs index b9483ac..6d9997c 100644 --- a/src/NetDuid.Tests/StaticTestData.cs +++ b/src/NetDuid.Tests/StaticTestData.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace NetDuid.Tests +namespace NetDuid.Tests { /// /// A class with static test data diff --git a/src/NetDuid.Tests/XunitSerializers/DuidXunitSerializer.cs b/src/NetDuid.Tests/XunitSerializers/DuidXunitSerializer.cs index 188c043..063a06c 100644 --- a/src/NetDuid.Tests/XunitSerializers/DuidXunitSerializer.cs +++ b/src/NetDuid.Tests/XunitSerializers/DuidXunitSerializer.cs @@ -1,5 +1,4 @@ -using System; -using Xunit.Sdk; +using Xunit.Sdk; namespace NetDuid.Tests.XunitSerializers { diff --git a/src/NetDuid.Tests/packages.lock.json b/src/NetDuid.Tests/packages.lock.json index b2ed796..022f7ab 100644 --- a/src/NetDuid.Tests/packages.lock.json +++ b/src/NetDuid.Tests/packages.lock.json @@ -1,63 +1,76 @@ { - "version": 1, + "version": 2, "dependencies": { ".NETFramework,Version=v4.8": { - "coverlet.collector": { + "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[6.0.4, )", - "resolved": "6.0.4", - "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + "requested": "[18.7.0, )", + "resolved": "18.7.0", + "contentHash": "49xH9j4UzCh2hMohJp53g3wUTvyycECw7CtVht4gfCz5ykudB1uBcF6D0TtgJPjCtP76UPW53bQElKdCeX+dUg==", + "dependencies": { + "Microsoft.CodeCoverage": "18.7.0" + } }, - "Microsoft.NET.Test.Sdk": { + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3" + } + }, + "NSubstitute": { "type": "Direct", - "requested": "[18.0.1, )", - "resolved": "18.0.1", - "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.1" + "Castle.Core": "5.1.1", + "System.Threading.Tasks.Extensions": "4.3.0" } }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, - "System.Reflection.Metadata": { + "xunit.analyzers": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "sMORylY+HmERA41BDGyi5fq1FjUFwwOTgCjcWhkkdb7c9jVLgFQP6xBWdbF2vRpjs0Igy5kvMAkvN9ml3QPS1g==", - "dependencies": { - "System.Collections.Immutable": "10.0.0" - } + "requested": "[1.27.0, )", + "resolved": "1.27.0", + "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g==" }, "xunit.runner.visualstudio": { "type": "Direct", @@ -70,13 +83,18 @@ }, "xunit.v3": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "ABdobDsBRs0JV+Ux6t0sM26noKFVbCkGOvU/x/YeShjI4aYH+thZeOGg8k5etju+5Ud4BZfzQEtVfvnAx8G2cg==", + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==", "dependencies": { - "xunit.v3.mtp-v1": "[3.2.0]" + "xunit.v3.mtp-v1": "[3.2.2]" } }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==" + }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", @@ -93,45 +111,45 @@ "System.Threading.Tasks.Extensions": "4.5.4" } }, - "Microsoft.Bcl.HashCode": { + "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + "resolved": "18.7.0", + "contentHash": "+wFfx9s7D9wegM0RziXMj2kvYDT4qcqXXtyjiQwSZOGQ2wwcOAJQcD6eQXk02jt0MvRNawtp8TJxTrV+wD8X1g==" }, - "Microsoft.CodeCoverage": { + "Microsoft.NETFramework.ReferenceAssemblies.net48": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" + "resolved": "1.0.3", + "contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "4ica7o+ZreUCsXoQLShWeLz6K5q9D/B+VgXvmpPSLsnSpK4DLZfMyltBaBr83Tnm21c1riOKcXXEk920Ael17Q==", + "resolved": "1.9.1", + "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", - "Microsoft.Testing.Platform": "1.9.0", + "Microsoft.Testing.Platform": "1.9.1", "System.Diagnostics.DiagnosticSource": "6.0.0" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "ZC7Xwva/+dREnZ5BU0gozA1uT2cFwt90uY9maPLy1lLpiedR/Fu3K2FiwLg70hJjT+tGblYmxzuNa8z1jQVSGw==", + "resolved": "1.9.1", + "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "OE79Vc5rXwFYciAPY/mqv92XvdhK+pvCHdVHcS0bBpWwWQbnzI18FiSEEYY+lYpB0HHl0fDQgcCK3ZTYKGs8bA==" + "resolved": "1.9.1", + "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "P7c0HfBJ/AqKyRfmaA+cyl8I9jxDEyvQRJvMUH5IZCjACTPQ9wXb0xdA0CmcxhBUtm20GxWvx2fq3fMxG11hDg==", + "resolved": "1.9.1", + "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.TestPlatform.ObjectModel": { @@ -151,23 +169,18 @@ "System.Security.Principal.Windows": "5.0.0" } }, - "StyleCop.Analyzers.Unstable": { - "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" - }, "System.Buffers": { "type": "Transitive", - "resolved": "4.6.1", - "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "BHo23kBFvTFQa0tuDFXcb3Q8QInjm1Xrq+If/xuV8iMlxOOsylsa6sgRK25n5dlcMk8G2f0O/t5AdjZrdVBOXA==", + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", "dependencies": { - "System.Memory": "4.6.3", - "System.Runtime.CompilerServices.Unsafe": "6.1.2" + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Diagnostics.DiagnosticSource": { @@ -181,23 +194,31 @@ }, "System.Memory": { "type": "Transitive", - "resolved": "4.6.3", - "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", "dependencies": { - "System.Buffers": "4.6.1", - "System.Numerics.Vectors": "4.6.1", - "System.Runtime.CompilerServices.Unsafe": "6.1.2" + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" } }, "System.Numerics.Vectors": { "type": "Transitive", - "resolved": "4.6.1", - "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "dependencies": { + "System.Collections.Immutable": "1.5.0" + } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "6.1.2", - "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Security.AccessControl": { "type": "Transitive", @@ -220,15 +241,10 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.3" } }, - "xunit.analyzers": { - "type": "Transitive", - "resolved": "1.25.0", - "contentHash": "J3wD5p2VVOg6TXeOOCrD+7Nl2H/LOZuK1GovDyPbFFnvv8katIULfBCj2sQLIPKT22Ds44j1aHjJle4AU7jCbA==" - }, "xunit.v3.assert": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "AT3jVbptSazsf+aKQt14eyhw0NN9GWj6SaYifJfZ873r86NbPeTlxshjij9aOqmtrR+C1fHwqeneO4mMZKK7ag==", + "resolved": "3.2.2", + "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA==", "dependencies": { "System.Collections.Immutable": "6.0.0", "System.Memory": "4.5.5" @@ -236,59 +252,59 @@ }, "xunit.v3.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "InLYIBD54RVFgX1S5y0g+Mia3coGsA+cf+SvieBH3kO5TrH3d4sb59+4M6nfBlGg8EqUCHbyGKKroQPquYEt7Q==", + "resolved": "3.2.2", + "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0" } }, "xunit.v3.core.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "0B+nZqJXl6BGTXT6kOf4LOnH5qxDvrufctNTjlxjXne4SqYybhCnfYpWRLy9NJ/0JkELyVlap8YK7HH+x/sHeQ==", + "resolved": "3.2.2", + "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==", "dependencies": { - "Microsoft.Testing.Extensions.Telemetry": "1.9.0", - "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.0", - "Microsoft.Testing.Platform": "1.9.0", - "Microsoft.Testing.Platform.MSBuild": "1.9.0", - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.inproc.console": "[3.2.0]" + "Microsoft.Testing.Extensions.Telemetry": "1.9.1", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1", + "Microsoft.Testing.Platform": "1.9.1", + "Microsoft.Testing.Platform.MSBuild": "1.9.1", + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.inproc.console": "[3.2.2]" } }, "xunit.v3.extensibility.core": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "89MWZ5gXT6VJ/qe9OCASBsaenFr00UCccFbkJxzkOsrAkoFjJSDJ+5xqXpZUxTBGJ4Gw91bJ4bwW9Fag0b8xhw==", + "resolved": "3.2.2", + "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==", "dependencies": { - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "Rg5djTBTao7QUEK28EXeacjwC6LnU/NunlJzYrNkhtOgggpueYzGDfmyKu5hDbAEXhAl6vjrzfcCnhZyX8Q+DA==", + "resolved": "3.2.2", + "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==", "dependencies": { - "xunit.analyzers": "1.25.0", - "xunit.v3.assert": "[3.2.0]", - "xunit.v3.core.mtp-v1": "[3.2.0]" + "xunit.analyzers": "1.27.0", + "xunit.v3.assert": "[3.2.2]", + "xunit.v3.core.mtp-v1": "[3.2.2]" } }, "xunit.v3.runner.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "2i1/IvIH9x6M+8TRlr8k6/4NCem44zYzveps7p8XL4jjsIM/1K2ahfcvtom3tu9RQxHF3czT7f3DOAvHp+01gQ==", + "resolved": "3.2.2", + "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==", "dependencies": { "Microsoft.Win32.Registry": "[5.0.0]", - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.runner.inproc.console": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "/nXHLSgKv5OYSjo3bTSzvTYfxbzApTEeip5T85PayDZgGbMHA679zeVhN/1n6//pW/uJ2j7QhDdHh2r98f13hQ==", + "resolved": "3.2.2", + "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==", "dependencies": { - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.common": "[3.2.0]" + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.common": "[3.2.2]" } }, "netduid": { @@ -296,6 +312,12 @@ "dependencies": { "Microsoft.Bcl.HashCode": "[6.0.0, )" } + }, + "Microsoft.Bcl.HashCode": { + "type": "CentralTransitive", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" } }, ".NETFramework,Version=v4.8/win-x86": { @@ -323,60 +345,66 @@ } }, "net10.0": { - "coverlet.collector": { + "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[6.0.4, )", - "resolved": "6.0.4", - "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + "requested": "[18.7.0, )", + "resolved": "18.7.0", + "contentHash": "49xH9j4UzCh2hMohJp53g3wUTvyycECw7CtVht4gfCz5ykudB1uBcF6D0TtgJPjCtP76UPW53bQElKdCeX+dUg==", + "dependencies": { + "Microsoft.CodeCoverage": "18.7.0", + "Microsoft.TestPlatform.TestHost": "18.7.0" + } }, - "Microsoft.NET.Test.Sdk": { + "NSubstitute": { "type": "Direct", - "requested": "[18.0.1, )", - "resolved": "18.0.1", - "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.1", - "Microsoft.TestPlatform.TestHost": "18.0.1" + "Castle.Core": "5.1.1" } }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, - "System.Reflection.Metadata": { + "xunit.analyzers": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "sMORylY+HmERA41BDGyi5fq1FjUFwwOTgCjcWhkkdb7c9jVLgFQP6xBWdbF2vRpjs0Igy5kvMAkvN9ml3QPS1g==" + "requested": "[1.27.0, )", + "resolved": "1.27.0", + "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g==" }, "xunit.runner.visualstudio": { "type": "Direct", @@ -386,11 +414,19 @@ }, "xunit.v3": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "ABdobDsBRs0JV+Ux6t0sM26noKFVbCkGOvU/x/YeShjI4aYH+thZeOGg8k5etju+5Ud4BZfzQEtVfvnAx8G2cg==", + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==", "dependencies": { - "xunit.v3.mtp-v1": "[3.2.0]" + "xunit.v3.mtp-v1": "[3.2.2]" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" } }, "Microsoft.ApplicationInsights": { @@ -405,50 +441,50 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" + "resolved": "18.7.0", + "contentHash": "+wFfx9s7D9wegM0RziXMj2kvYDT4qcqXXtyjiQwSZOGQ2wwcOAJQcD6eQXk02jt0MvRNawtp8TJxTrV+wD8X1g==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "4ica7o+ZreUCsXoQLShWeLz6K5q9D/B+VgXvmpPSLsnSpK4DLZfMyltBaBr83Tnm21c1riOKcXXEk920Ael17Q==", + "resolved": "1.9.1", + "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "ZC7Xwva/+dREnZ5BU0gozA1uT2cFwt90uY9maPLy1lLpiedR/Fu3K2FiwLg70hJjT+tGblYmxzuNa8z1jQVSGw==", + "resolved": "1.9.1", + "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "OE79Vc5rXwFYciAPY/mqv92XvdhK+pvCHdVHcS0bBpWwWQbnzI18FiSEEYY+lYpB0HHl0fDQgcCK3ZTYKGs8bA==" + "resolved": "1.9.1", + "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "P7c0HfBJ/AqKyRfmaA+cyl8I9jxDEyvQRJvMUH5IZCjACTPQ9wXb0xdA0CmcxhBUtm20GxWvx2fq3fMxG11hDg==", + "resolved": "1.9.1", + "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==" + "resolved": "18.7.0", + "contentHash": "6rmgU4q3/WOpOPcncI0YW0Q/QpcQtwR2TTEXDR5+4TfSimPBAk6Z/BgKLeGgp1SOun0ROVUCCafXhRLwsHaPpA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==", + "resolved": "18.7.0", + "contentHash": "kYwfmebCs8992zaxEDkvG7S+YEouTeKfYVKUFEkwh1W2dIoOaevBt80XSKVXCUFEhusjOIm1sFfHBnoJgygrRA==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "18.0.1", + "Microsoft.TestPlatform.ObjectModel": "18.7.0", "Newtonsoft.Json": "13.0.3" } }, @@ -462,76 +498,71 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "StyleCop.Analyzers.Unstable": { + "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" - }, - "xunit.analyzers": { - "type": "Transitive", - "resolved": "1.25.0", - "contentHash": "J3wD5p2VVOg6TXeOOCrD+7Nl2H/LOZuK1GovDyPbFFnvv8katIULfBCj2sQLIPKT22Ds44j1aHjJle4AU7jCbA==" + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "xunit.v3.assert": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "AT3jVbptSazsf+aKQt14eyhw0NN9GWj6SaYifJfZ873r86NbPeTlxshjij9aOqmtrR+C1fHwqeneO4mMZKK7ag==" + "resolved": "3.2.2", + "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA==" }, "xunit.v3.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "InLYIBD54RVFgX1S5y0g+Mia3coGsA+cf+SvieBH3kO5TrH3d4sb59+4M6nfBlGg8EqUCHbyGKKroQPquYEt7Q==", + "resolved": "3.2.2", + "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0" } }, "xunit.v3.core.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "0B+nZqJXl6BGTXT6kOf4LOnH5qxDvrufctNTjlxjXne4SqYybhCnfYpWRLy9NJ/0JkELyVlap8YK7HH+x/sHeQ==", + "resolved": "3.2.2", + "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==", "dependencies": { - "Microsoft.Testing.Extensions.Telemetry": "1.9.0", - "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.0", - "Microsoft.Testing.Platform": "1.9.0", - "Microsoft.Testing.Platform.MSBuild": "1.9.0", - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.inproc.console": "[3.2.0]" + "Microsoft.Testing.Extensions.Telemetry": "1.9.1", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1", + "Microsoft.Testing.Platform": "1.9.1", + "Microsoft.Testing.Platform.MSBuild": "1.9.1", + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.inproc.console": "[3.2.2]" } }, "xunit.v3.extensibility.core": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "89MWZ5gXT6VJ/qe9OCASBsaenFr00UCccFbkJxzkOsrAkoFjJSDJ+5xqXpZUxTBGJ4Gw91bJ4bwW9Fag0b8xhw==", + "resolved": "3.2.2", + "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==", "dependencies": { - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "Rg5djTBTao7QUEK28EXeacjwC6LnU/NunlJzYrNkhtOgggpueYzGDfmyKu5hDbAEXhAl6vjrzfcCnhZyX8Q+DA==", + "resolved": "3.2.2", + "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==", "dependencies": { - "xunit.analyzers": "1.25.0", - "xunit.v3.assert": "[3.2.0]", - "xunit.v3.core.mtp-v1": "[3.2.0]" + "xunit.analyzers": "1.27.0", + "xunit.v3.assert": "[3.2.2]", + "xunit.v3.core.mtp-v1": "[3.2.2]" } }, "xunit.v3.runner.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "2i1/IvIH9x6M+8TRlr8k6/4NCem44zYzveps7p8XL4jjsIM/1K2ahfcvtom3tu9RQxHF3czT7f3DOAvHp+01gQ==", + "resolved": "3.2.2", + "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==", "dependencies": { "Microsoft.Win32.Registry": "[5.0.0]", - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.runner.inproc.console": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "/nXHLSgKv5OYSjo3bTSzvTYfxbzApTEeip5T85PayDZgGbMHA679zeVhN/1n6//pW/uJ2j7QhDdHh2r98f13hQ==", + "resolved": "3.2.2", + "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==", "dependencies": { - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.common": "[3.2.0]" + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.common": "[3.2.2]" } }, "netduid": { @@ -543,66 +574,74 @@ "type": "Transitive", "resolved": "5.0.0", "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" } }, "net8.0": { - "coverlet.collector": { + "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[6.0.4, )", - "resolved": "6.0.4", - "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + "requested": "[18.7.0, )", + "resolved": "18.7.0", + "contentHash": "49xH9j4UzCh2hMohJp53g3wUTvyycECw7CtVht4gfCz5ykudB1uBcF6D0TtgJPjCtP76UPW53bQElKdCeX+dUg==", + "dependencies": { + "Microsoft.CodeCoverage": "18.7.0", + "Microsoft.TestPlatform.TestHost": "18.7.0" + } }, - "Microsoft.NET.Test.Sdk": { + "NSubstitute": { "type": "Direct", - "requested": "[18.0.1, )", - "resolved": "18.0.1", - "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.1", - "Microsoft.TestPlatform.TestHost": "18.0.1" + "Castle.Core": "5.1.1" } }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, - "System.Reflection.Metadata": { + "xunit.analyzers": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "sMORylY+HmERA41BDGyi5fq1FjUFwwOTgCjcWhkkdb7c9jVLgFQP6xBWdbF2vRpjs0Igy5kvMAkvN9ml3QPS1g==", - "dependencies": { - "System.Collections.Immutable": "10.0.0" - } + "requested": "[1.27.0, )", + "resolved": "1.27.0", + "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g==" }, "xunit.runner.visualstudio": { "type": "Direct", @@ -612,11 +651,19 @@ }, "xunit.v3": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "ABdobDsBRs0JV+Ux6t0sM26noKFVbCkGOvU/x/YeShjI4aYH+thZeOGg8k5etju+5Ud4BZfzQEtVfvnAx8G2cg==", + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==", "dependencies": { - "xunit.v3.mtp-v1": "[3.2.0]" + "xunit.v3.mtp-v1": "[3.2.2]" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" } }, "Microsoft.ApplicationInsights": { @@ -631,50 +678,50 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" + "resolved": "18.7.0", + "contentHash": "+wFfx9s7D9wegM0RziXMj2kvYDT4qcqXXtyjiQwSZOGQ2wwcOAJQcD6eQXk02jt0MvRNawtp8TJxTrV+wD8X1g==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "4ica7o+ZreUCsXoQLShWeLz6K5q9D/B+VgXvmpPSLsnSpK4DLZfMyltBaBr83Tnm21c1riOKcXXEk920Ael17Q==", + "resolved": "1.9.1", + "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "ZC7Xwva/+dREnZ5BU0gozA1uT2cFwt90uY9maPLy1lLpiedR/Fu3K2FiwLg70hJjT+tGblYmxzuNa8z1jQVSGw==", + "resolved": "1.9.1", + "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "OE79Vc5rXwFYciAPY/mqv92XvdhK+pvCHdVHcS0bBpWwWQbnzI18FiSEEYY+lYpB0HHl0fDQgcCK3ZTYKGs8bA==" + "resolved": "1.9.1", + "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "P7c0HfBJ/AqKyRfmaA+cyl8I9jxDEyvQRJvMUH5IZCjACTPQ9wXb0xdA0CmcxhBUtm20GxWvx2fq3fMxG11hDg==", + "resolved": "1.9.1", + "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==" + "resolved": "18.7.0", + "contentHash": "6rmgU4q3/WOpOPcncI0YW0Q/QpcQtwR2TTEXDR5+4TfSimPBAk6Z/BgKLeGgp1SOun0ROVUCCafXhRLwsHaPpA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==", + "resolved": "18.7.0", + "contentHash": "kYwfmebCs8992zaxEDkvG7S+YEouTeKfYVKUFEkwh1W2dIoOaevBt80XSKVXCUFEhusjOIm1sFfHBnoJgygrRA==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "18.0.1", + "Microsoft.TestPlatform.ObjectModel": "18.7.0", "Newtonsoft.Json": "13.0.3" } }, @@ -688,81 +735,71 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "StyleCop.Analyzers.Unstable": { - "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" - }, - "System.Collections.Immutable": { + "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "BHo23kBFvTFQa0tuDFXcb3Q8QInjm1Xrq+If/xuV8iMlxOOsylsa6sgRK25n5dlcMk8G2f0O/t5AdjZrdVBOXA==" - }, - "xunit.analyzers": { - "type": "Transitive", - "resolved": "1.25.0", - "contentHash": "J3wD5p2VVOg6TXeOOCrD+7Nl2H/LOZuK1GovDyPbFFnvv8katIULfBCj2sQLIPKT22Ds44j1aHjJle4AU7jCbA==" + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "xunit.v3.assert": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "AT3jVbptSazsf+aKQt14eyhw0NN9GWj6SaYifJfZ873r86NbPeTlxshjij9aOqmtrR+C1fHwqeneO4mMZKK7ag==" + "resolved": "3.2.2", + "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA==" }, "xunit.v3.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "InLYIBD54RVFgX1S5y0g+Mia3coGsA+cf+SvieBH3kO5TrH3d4sb59+4M6nfBlGg8EqUCHbyGKKroQPquYEt7Q==", + "resolved": "3.2.2", + "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0" } }, "xunit.v3.core.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "0B+nZqJXl6BGTXT6kOf4LOnH5qxDvrufctNTjlxjXne4SqYybhCnfYpWRLy9NJ/0JkELyVlap8YK7HH+x/sHeQ==", + "resolved": "3.2.2", + "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==", "dependencies": { - "Microsoft.Testing.Extensions.Telemetry": "1.9.0", - "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.0", - "Microsoft.Testing.Platform": "1.9.0", - "Microsoft.Testing.Platform.MSBuild": "1.9.0", - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.inproc.console": "[3.2.0]" + "Microsoft.Testing.Extensions.Telemetry": "1.9.1", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1", + "Microsoft.Testing.Platform": "1.9.1", + "Microsoft.Testing.Platform.MSBuild": "1.9.1", + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.inproc.console": "[3.2.2]" } }, "xunit.v3.extensibility.core": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "89MWZ5gXT6VJ/qe9OCASBsaenFr00UCccFbkJxzkOsrAkoFjJSDJ+5xqXpZUxTBGJ4Gw91bJ4bwW9Fag0b8xhw==", + "resolved": "3.2.2", + "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==", "dependencies": { - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "Rg5djTBTao7QUEK28EXeacjwC6LnU/NunlJzYrNkhtOgggpueYzGDfmyKu5hDbAEXhAl6vjrzfcCnhZyX8Q+DA==", + "resolved": "3.2.2", + "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==", "dependencies": { - "xunit.analyzers": "1.25.0", - "xunit.v3.assert": "[3.2.0]", - "xunit.v3.core.mtp-v1": "[3.2.0]" + "xunit.analyzers": "1.27.0", + "xunit.v3.assert": "[3.2.2]", + "xunit.v3.core.mtp-v1": "[3.2.2]" } }, "xunit.v3.runner.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "2i1/IvIH9x6M+8TRlr8k6/4NCem44zYzveps7p8XL4jjsIM/1K2ahfcvtom3tu9RQxHF3czT7f3DOAvHp+01gQ==", + "resolved": "3.2.2", + "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==", "dependencies": { "Microsoft.Win32.Registry": "[5.0.0]", - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.runner.inproc.console": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "/nXHLSgKv5OYSjo3bTSzvTYfxbzApTEeip5T85PayDZgGbMHA679zeVhN/1n6//pW/uJ2j7QhDdHh2r98f13hQ==", + "resolved": "3.2.2", + "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==", "dependencies": { - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.common": "[3.2.0]" + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.common": "[3.2.2]" } }, "netduid": { @@ -774,66 +811,74 @@ "type": "Transitive", "resolved": "5.0.0", "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" } }, "net9.0": { - "coverlet.collector": { + "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[6.0.4, )", - "resolved": "6.0.4", - "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + "requested": "[18.7.0, )", + "resolved": "18.7.0", + "contentHash": "49xH9j4UzCh2hMohJp53g3wUTvyycECw7CtVht4gfCz5ykudB1uBcF6D0TtgJPjCtP76UPW53bQElKdCeX+dUg==", + "dependencies": { + "Microsoft.CodeCoverage": "18.7.0", + "Microsoft.TestPlatform.TestHost": "18.7.0" + } }, - "Microsoft.NET.Test.Sdk": { + "NSubstitute": { "type": "Direct", - "requested": "[18.0.1, )", - "resolved": "18.0.1", - "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.1", - "Microsoft.TestPlatform.TestHost": "18.0.1" + "Castle.Core": "5.1.1" } }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, - "System.Reflection.Metadata": { + "xunit.analyzers": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "sMORylY+HmERA41BDGyi5fq1FjUFwwOTgCjcWhkkdb7c9jVLgFQP6xBWdbF2vRpjs0Igy5kvMAkvN9ml3QPS1g==", - "dependencies": { - "System.Collections.Immutable": "10.0.0" - } + "requested": "[1.27.0, )", + "resolved": "1.27.0", + "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g==" }, "xunit.runner.visualstudio": { "type": "Direct", @@ -843,11 +888,19 @@ }, "xunit.v3": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "ABdobDsBRs0JV+Ux6t0sM26noKFVbCkGOvU/x/YeShjI4aYH+thZeOGg8k5etju+5Ud4BZfzQEtVfvnAx8G2cg==", + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==", "dependencies": { - "xunit.v3.mtp-v1": "[3.2.0]" + "xunit.v3.mtp-v1": "[3.2.2]" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" } }, "Microsoft.ApplicationInsights": { @@ -862,50 +915,50 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" + "resolved": "18.7.0", + "contentHash": "+wFfx9s7D9wegM0RziXMj2kvYDT4qcqXXtyjiQwSZOGQ2wwcOAJQcD6eQXk02jt0MvRNawtp8TJxTrV+wD8X1g==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "4ica7o+ZreUCsXoQLShWeLz6K5q9D/B+VgXvmpPSLsnSpK4DLZfMyltBaBr83Tnm21c1riOKcXXEk920Ael17Q==", + "resolved": "1.9.1", + "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "ZC7Xwva/+dREnZ5BU0gozA1uT2cFwt90uY9maPLy1lLpiedR/Fu3K2FiwLg70hJjT+tGblYmxzuNa8z1jQVSGw==", + "resolved": "1.9.1", + "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "OE79Vc5rXwFYciAPY/mqv92XvdhK+pvCHdVHcS0bBpWwWQbnzI18FiSEEYY+lYpB0HHl0fDQgcCK3ZTYKGs8bA==" + "resolved": "1.9.1", + "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", - "resolved": "1.9.0", - "contentHash": "P7c0HfBJ/AqKyRfmaA+cyl8I9jxDEyvQRJvMUH5IZCjACTPQ9wXb0xdA0CmcxhBUtm20GxWvx2fq3fMxG11hDg==", + "resolved": "1.9.1", + "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==", "dependencies": { - "Microsoft.Testing.Platform": "1.9.0" + "Microsoft.Testing.Platform": "1.9.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==" + "resolved": "18.7.0", + "contentHash": "6rmgU4q3/WOpOPcncI0YW0Q/QpcQtwR2TTEXDR5+4TfSimPBAk6Z/BgKLeGgp1SOun0ROVUCCafXhRLwsHaPpA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==", + "resolved": "18.7.0", + "contentHash": "kYwfmebCs8992zaxEDkvG7S+YEouTeKfYVKUFEkwh1W2dIoOaevBt80XSKVXCUFEhusjOIm1sFfHBnoJgygrRA==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "18.0.1", + "Microsoft.TestPlatform.ObjectModel": "18.7.0", "Newtonsoft.Json": "13.0.3" } }, @@ -919,81 +972,71 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "StyleCop.Analyzers.Unstable": { - "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "BHo23kBFvTFQa0tuDFXcb3Q8QInjm1Xrq+If/xuV8iMlxOOsylsa6sgRK25n5dlcMk8G2f0O/t5AdjZrdVBOXA==" - }, - "xunit.analyzers": { + "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "1.25.0", - "contentHash": "J3wD5p2VVOg6TXeOOCrD+7Nl2H/LOZuK1GovDyPbFFnvv8katIULfBCj2sQLIPKT22Ds44j1aHjJle4AU7jCbA==" + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "xunit.v3.assert": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "AT3jVbptSazsf+aKQt14eyhw0NN9GWj6SaYifJfZ873r86NbPeTlxshjij9aOqmtrR+C1fHwqeneO4mMZKK7ag==" + "resolved": "3.2.2", + "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA==" }, "xunit.v3.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "InLYIBD54RVFgX1S5y0g+Mia3coGsA+cf+SvieBH3kO5TrH3d4sb59+4M6nfBlGg8EqUCHbyGKKroQPquYEt7Q==", + "resolved": "3.2.2", + "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0" } }, "xunit.v3.core.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "0B+nZqJXl6BGTXT6kOf4LOnH5qxDvrufctNTjlxjXne4SqYybhCnfYpWRLy9NJ/0JkELyVlap8YK7HH+x/sHeQ==", + "resolved": "3.2.2", + "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==", "dependencies": { - "Microsoft.Testing.Extensions.Telemetry": "1.9.0", - "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.0", - "Microsoft.Testing.Platform": "1.9.0", - "Microsoft.Testing.Platform.MSBuild": "1.9.0", - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.inproc.console": "[3.2.0]" + "Microsoft.Testing.Extensions.Telemetry": "1.9.1", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1", + "Microsoft.Testing.Platform": "1.9.1", + "Microsoft.Testing.Platform.MSBuild": "1.9.1", + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.inproc.console": "[3.2.2]" } }, "xunit.v3.extensibility.core": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "89MWZ5gXT6VJ/qe9OCASBsaenFr00UCccFbkJxzkOsrAkoFjJSDJ+5xqXpZUxTBGJ4Gw91bJ4bwW9Fag0b8xhw==", + "resolved": "3.2.2", + "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==", "dependencies": { - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.mtp-v1": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "Rg5djTBTao7QUEK28EXeacjwC6LnU/NunlJzYrNkhtOgggpueYzGDfmyKu5hDbAEXhAl6vjrzfcCnhZyX8Q+DA==", + "resolved": "3.2.2", + "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==", "dependencies": { - "xunit.analyzers": "1.25.0", - "xunit.v3.assert": "[3.2.0]", - "xunit.v3.core.mtp-v1": "[3.2.0]" + "xunit.analyzers": "1.27.0", + "xunit.v3.assert": "[3.2.2]", + "xunit.v3.core.mtp-v1": "[3.2.2]" } }, "xunit.v3.runner.common": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "2i1/IvIH9x6M+8TRlr8k6/4NCem44zYzveps7p8XL4jjsIM/1K2ahfcvtom3tu9RQxHF3czT7f3DOAvHp+01gQ==", + "resolved": "3.2.2", + "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==", "dependencies": { "Microsoft.Win32.Registry": "[5.0.0]", - "xunit.v3.common": "[3.2.0]" + "xunit.v3.common": "[3.2.2]" } }, "xunit.v3.runner.inproc.console": { "type": "Transitive", - "resolved": "3.2.0", - "contentHash": "/nXHLSgKv5OYSjo3bTSzvTYfxbzApTEeip5T85PayDZgGbMHA679zeVhN/1n6//pW/uJ2j7QhDdHh2r98f13hQ==", + "resolved": "3.2.2", + "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==", "dependencies": { - "xunit.v3.extensibility.core": "[3.2.0]", - "xunit.v3.runner.common": "[3.2.0]" + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.common": "[3.2.2]" } }, "netduid": { @@ -1005,6 +1048,11 @@ "type": "Transitive", "resolved": "5.0.0", "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" } } } diff --git a/src/NetDuid.sln b/src/NetDuid.sln deleted file mode 100644 index 5721dd4..0000000 --- a/src/NetDuid.sln +++ /dev/null @@ -1,86 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDuid", "NetDuid\NetDuid.csproj", "{0EA34755-2619-417B-9BD1-56905C8B5BB5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDuid.Tests", "NetDuid.Tests\NetDuid.Tests.csproj", "{2AE63F6D-4C66-48E0-8788-B38B7CE24CB3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC1CE95C-C60A-4819-BE1E-BD12B4D6DB46}" - ProjectSection(SolutionItems) = preProject - ..\CODE_OF_CONDUCT.md = ..\CODE_OF_CONDUCT.md - global.json = global.json - ..\HUMANS.md = ..\HUMANS.md - ..\LICENSE = ..\LICENSE - ..\README.md = ..\README.md - ..\SECURITY.md = ..\SECURITY.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "codequality", "codequality", "{3E4EB914-9384-4B6E-9FAB-C6667745B5D6}" - ProjectSection(SolutionItems) = preProject - .csharpierrc.json = .csharpierrc.json - .editorconfig = .editorconfig - ..\.editorconfig = ..\.editorconfig - ..\dictionary.dic = ..\dictionary.dic - stylecop.json = stylecop.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{7D39F012-D8DD-40ED-843A-857B08DC7FAA}" - ProjectSection(SolutionItems) = preProject - ..\.gitattributes = ..\.gitattributes - ..\.gitignore = ..\.gitignore - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{F01B1C3E-8AA7-465B-A400-81FF48FCBC17}" - ProjectSection(SolutionItems) = preProject - global.json = global.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{3520B445-ECF9-4733-88AB-7AF5C510E0D1}" - ProjectSection(SolutionItems) = preProject - ..\.github\dependabot.yml = ..\.github\dependabot.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflow", "workflow", "{69D97C45-FF17-47DA-94A9-1569C270859F}" - ProjectSection(SolutionItems) = preProject - ..\.github\workflows\build.yml = ..\.github\workflows\build.yml - ..\.github\workflows\publish.yml = ..\.github\workflows\publish.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issue templates", "issue templates", "{2F126E04-0CB1-4459-BE81-3648741699B7}" - ProjectSection(SolutionItems) = preProject - ..\.github\ISSUE_TEMPLATE\bug_report.md = ..\.github\ISSUE_TEMPLATE\bug_report.md - ..\.github\ISSUE_TEMPLATE\feature_request.md = ..\.github\ISSUE_TEMPLATE\feature_request.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0EA34755-2619-417B-9BD1-56905C8B5BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0EA34755-2619-417B-9BD1-56905C8B5BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0EA34755-2619-417B-9BD1-56905C8B5BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0EA34755-2619-417B-9BD1-56905C8B5BB5}.Release|Any CPU.Build.0 = Release|Any CPU - {2AE63F6D-4C66-48E0-8788-B38B7CE24CB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2AE63F6D-4C66-48E0-8788-B38B7CE24CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2AE63F6D-4C66-48E0-8788-B38B7CE24CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2AE63F6D-4C66-48E0-8788-B38B7CE24CB3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {3E4EB914-9384-4B6E-9FAB-C6667745B5D6} = {FC1CE95C-C60A-4819-BE1E-BD12B4D6DB46} - {7D39F012-D8DD-40ED-843A-857B08DC7FAA} = {FC1CE95C-C60A-4819-BE1E-BD12B4D6DB46} - {F01B1C3E-8AA7-465B-A400-81FF48FCBC17} = {FC1CE95C-C60A-4819-BE1E-BD12B4D6DB46} - {3520B445-ECF9-4733-88AB-7AF5C510E0D1} = {FC1CE95C-C60A-4819-BE1E-BD12B4D6DB46} - {69D97C45-FF17-47DA-94A9-1569C270859F} = {3520B445-ECF9-4733-88AB-7AF5C510E0D1} - {2F126E04-0CB1-4459-BE81-3648741699B7} = {3520B445-ECF9-4733-88AB-7AF5C510E0D1} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A17CAA2D-9832-40D6-A5D2-4B5797F88679} - EndGlobalSection -EndGlobal diff --git a/src/NetDuid/Duid.Factory.cs b/src/NetDuid/Duid.Factory.cs index 1eb1a9c..d552a7e 100644 --- a/src/NetDuid/Duid.Factory.cs +++ b/src/NetDuid/Duid.Factory.cs @@ -1,5 +1,7 @@ -using System; -using System.Globalization; +using System.Globalization; +#if !NETSTANDARD2_0 +using System.Diagnostics.CodeAnalysis; +#endif namespace NetDuid { @@ -23,7 +25,13 @@ public sealed partial class Duid /// string of undelimited hexadecimal octet pairs /// /// - public static bool TryParse(string duidString, out Duid duid) + public static bool TryParse( +#if !NETSTANDARD2_0 + [NotNullWhen(true)] string duidString, [MaybeNullWhen(false)] out Duid duid +#else + string duidString, out Duid duid +#endif + ) { duid = null; @@ -34,13 +42,13 @@ public static bool TryParse(string duidString, out Duid duid) var trimmed = duidString.Trim(); - if (DuidRegexSource.GetDelimitedOctetsRegex().IsMatch(trimmed)) + if (DuidRegexPatterns.GetDelimitedOctetsRegex().IsMatch(trimmed)) { duid = new Duid(DelimitedStringToBytes(trimmed, 1)); return true; } - if (DuidRegexSource.GetUndelimitedOctetsRegex().IsMatch(trimmed)) + if (DuidRegexPatterns.GetUndelimitedOctetsRegex().IsMatch(trimmed)) { duid = new Duid(UndelimitedStringToBytes(trimmed)); return true; @@ -70,11 +78,11 @@ public static Duid Parse(string duidString) var trimmed = duidString.Trim(); - if (DuidRegexSource.GetDelimitedOctetsRegex().IsMatch(trimmed)) + if (DuidRegexPatterns.GetDelimitedOctetsRegex().IsMatch(trimmed)) { return new Duid(DelimitedStringToBytes(trimmed, 1)); } - else if (DuidRegexSource.GetUndelimitedOctetsRegex().IsMatch(trimmed)) + else if (DuidRegexPatterns.GetUndelimitedOctetsRegex().IsMatch(trimmed)) { // convert and return non-delimited string of octets into bytes return new Duid(UndelimitedStringToBytes(trimmed)); @@ -110,69 +118,44 @@ private static byte[] UndelimitedStringToBytes(string str) /// A byte array representing the hexadecimal values in the string. private static byte[] DelimitedStringToBytes(string str, int delimiterLength) { - // delimited bytes are a bit ugly as values between 0x0 and 0xF may optionally not include a leading '0' in byte octets - // byte length is not known beforehand, but the maximum size can be calculated. - // Each byte is represented by 1 or 2 characters plus the length of the delimiter (including a non-existent trailing delimiter - // which will be trimmed on copy to array). - var maxBytesLength = (str.Length + delimiterLength) / (1 + delimiterLength); - var bytes = new byte[maxBytesLength]; - var byteIndex = 0; - - // Iterate though the string a character at a time looking ahead to the the next character - // If the next character is hexadecimal character a full octet is present, if it is not then it is considered the start - // if the delimiter is the next character (characterIndex+1). It is assumed that a delimiter will never start with a - // hexadecimal digit. + var result = new List(); + var characterIndex = 0; while (characterIndex < str.Length) { - var ithChar = HexCharToUpper(str[characterIndex]); // ith character should always be a hex digit; it may also be a '0' prefix + var ithChar = HexCharToUpper(str[characterIndex]); - if (characterIndex == str.Length - 1) // at last position in string, no possibility of next + if (characterIndex == str.Length - 1) { - // process the final character - bytes[byteIndex] = HexCharValue(ithChar); - byteIndex++; // byte was built, go to next index position in the byte array - break; // break the loop + result.Add(HexCharValue(ithChar)); + break; } - // there exists a character after the ith character of the string var nextChar = HexCharToUpper(str[characterIndex + 1]); - var nextCharIsHex = (nextChar >= '0' && nextChar <= '9') || (nextChar >= 'A' && nextChar <= 'F'); + var nextCharIsHex = nextChar is >= '0' and <= '9' or >= 'A' and <= 'F'; - if (nextCharIsHex) // next character is hex + if (nextCharIsHex) { - bytes[byteIndex] = (byte)((HexCharValue(ithChar) << 4) | HexCharValue(nextChar)); // build a byte from two hex characters - characterIndex++; // two digit octet consumed; skip the next character in the iteration + result.Add((byte)((HexCharValue(ithChar) << 4) | HexCharValue(nextChar))); + characterIndex++; } - else // next character must be the start of delimiter + else { - bytes[byteIndex] = HexCharValue(ithChar); + result.Add(HexCharValue(ithChar)); } - byteIndex++; // byte was built, go to next index position in the byte array characterIndex += delimiterLength + 1; } - // build actual byte array based on findings - var result = new byte[byteIndex]; - Buffer.BlockCopy(bytes, 0, result, 0, byteIndex); - return result; + return [.. result]; } /// - /// Converts a lowercase hexadecimal character to its uppercase equivalent. + /// Converts a hexadecimal character to its uppercase equivalent. /// - /// The lowercase hexadecimal character. + /// The hexadecimal character. /// The uppercase equivalent of the hexadecimal character. - private static char HexCharToUpper(char input) - { - if (input >= 'a' && input <= 'f') - { - return (char)(input - ' '); // Upper case is 32 characters before lower case in ASCII; it just so happens space is 32 - } - - return input; - } + private static char HexCharToUpper(char input) => char.ToUpperInvariant(input); /// /// Converts a hexadecimal character to its byte value. diff --git a/src/NetDuid/Duid.IComparable.cs b/src/NetDuid/Duid.IComparable.cs index ef357f5..6f4cc6b 100644 --- a/src/NetDuid/Duid.IComparable.cs +++ b/src/NetDuid/Duid.IComparable.cs @@ -1,6 +1,4 @@ -using System; - -namespace NetDuid +namespace NetDuid { /// /// implementation of and @@ -10,7 +8,7 @@ public sealed partial class Duid : IComparable, IComparable #region CompareTo /// - /// Sorting is not done in mathematical order, but rather by length then value + /// Sorting is not done in mathematical order, but rather by length then value public int CompareTo(Duid other) { if (ReferenceEquals(this, other)) diff --git a/src/NetDuid/Duid.IEquatable.cs b/src/NetDuid/Duid.IEquatable.cs index f77fc63..351060b 100644 --- a/src/NetDuid/Duid.IEquatable.cs +++ b/src/NetDuid/Duid.IEquatable.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace NetDuid +namespace NetDuid { /// /// implementation of @@ -35,8 +32,7 @@ public bool Equals(Duid other) } // considered equal if this and other have the same number of bytes, and their bytes are the same - var otherBytes = other.GetBytes(); - return _duidBytes.Length == otherBytes.Count && _duidBytes.SequenceEqual(otherBytes); + return _duidBytes.Length == other._duidBytes.Length && _duidBytes.SequenceEqual(other._duidBytes); } /// diff --git a/src/NetDuid/Duid.IFormattable.cs b/src/NetDuid/Duid.IFormattable.cs index e1acdb5..969e1d3 100644 --- a/src/NetDuid/Duid.IFormattable.cs +++ b/src/NetDuid/Duid.IFormattable.cs @@ -1,6 +1,4 @@ -using System; - -namespace NetDuid +namespace NetDuid { /// /// implementation of @@ -57,116 +55,84 @@ public sealed partial class Duid : IFormattable /// Thrown when the format string is invalid. public string ToString(string format, IFormatProvider formatProvider) { - // Default format is uppercase with colon delimiter if format is null or empty - if (string.IsNullOrEmpty(format)) - { - format = "U:"; // Default format - } + var (delimiter, toUpper) = ParseFormatString(format); - // Format string should be at most 2 characters long - if (format.Length > 2) - { - throw new FormatException($"Invalid format string: \"{format}\"."); - } - - char? delimiter; - bool toUpper; - - // Determine the formatting options based on the format string - if (format.Length == 1) - { - // Single character format string - switch (format[0]) - { - case 'U': - case 'u': - toUpper = true; - delimiter = null; - break; - case 'L': - case 'l': - toUpper = false; - delimiter = null; - break; - case ':': - toUpper = true; - delimiter = ':'; - break; - case '-': - toUpper = true; - delimiter = '-'; - break; - default: - throw new FormatException($"Invalid format string: \"{format}\"."); - } - } - else - { - // Two character format string - switch (char.ToUpper(format[0])) - { - case 'U': - case 'u': - toUpper = true; - break; - case 'L': - case 'l': - toUpper = false; - break; - default: - throw new FormatException($"Invalid format string: \"{format}\"."); - } - - switch (format[1]) - { - case ':': - delimiter = ':'; - break; - case '-': - delimiter = '-'; - break; - default: - throw new FormatException($"Invalid format string: \"{format}\"."); - } - } - - // Calculate the length of the resulting string - var octetLength = delimiter is null ? 2 : 3; // Each byte is represented by 2 hex characters plus an optional delimiter + var octetLength = delimiter is null ? 2 : 3; var resultLength = _duidBytes.Length * octetLength; var characters = new char[resultLength]; - - // Select the appropriate nibble formatting based on the case preference var nibbleFormatter = toUpper ? (Func)GetUpperHexNibble : GetLowerHexNibble; - // Convert each byte to its hexadecimal representation for (var i = 0; i < _duidBytes.Length; i++) { var @byte = _duidBytes[i]; - var characterIndex = i * octetLength; // Calculate the index in the result array + var characterIndex = i * octetLength; - // Convert the byte to hexadecimal characters - characters[characterIndex] = nibbleFormatter(@byte / 16); - characters[characterIndex + 1] = nibbleFormatter(@byte % 16); + characters[characterIndex] = nibbleFormatter(@byte >> 4); + characters[characterIndex + 1] = nibbleFormatter(@byte & 0x0F); - // Add the delimiter if specified - if (delimiter != null) + if (delimiter is not null) { - characters[characterIndex + 2] = (char)delimiter; + characters[characterIndex + 2] = delimiter.Value; } } - // Return the formatted string, excluding the trailing delimiter if present - if (delimiter is null) - { - return new string(characters); - } - return new string(characters, 0, resultLength - 1); + return delimiter is null ? new string(characters) : new string(characters, 0, resultLength - 1); } #endregion #region utility methods + /// + /// Parses a format string into a delimiter character and case preference. + /// + /// + /// A format string that specifies the formatting options. + /// Valid values: null, empty, "U", "u", "L", "l", + /// ":", "-", "U:", "u:", "U-", "u-", + /// "L:", "l:", "L-", "l-". + /// + /// A tuple containing the delimiter character (or null for no delimiter) and whether to use uppercase hex. + /// Thrown when the format string is invalid. + private static (char? delimiter, bool toUpper) ParseFormatString(string format) + { + if (string.IsNullOrEmpty(format)) + { + return (':', true); + } + + if (format.Length > 2) + { + throw new FormatException($"Invalid format string: \"{format}\"."); + } + + if (format.Length == 1) + { + return format[0] switch + { + 'U' or 'u' => (null, true), + 'L' or 'l' => (null, false), + ':' => (':', true), + '-' => ('-', true), + _ => throw new FormatException($"Invalid format string: \"{format}\"."), + }; + } + + var toUpper = char.ToUpper(format[0]) switch + { + 'U' => true, + 'L' => false, + _ => throw new FormatException($"Invalid format string: \"{format}\"."), + }; + var delimiter = format[1] switch + { + ':' => ':', + '-' => '-', + _ => throw new FormatException($"Invalid format string: \"{format}\"."), + }; + return (delimiter, toUpper); + } + /// /// Converts an integer to its corresponding uppercase hexadecimal character. /// diff --git a/src/NetDuid/Duid.IParsable.cs b/src/NetDuid/Duid.IParsable.cs index 371de02..01078a5 100644 --- a/src/NetDuid/Duid.IParsable.cs +++ b/src/NetDuid/Duid.IParsable.cs @@ -1,5 +1,4 @@ -using System; -#if NET7_0_OR_GREATER +#if NET7_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif @@ -42,7 +41,11 @@ public static bool TryParse( IFormatProvider provider, [MaybeNullWhen(false)] out Duid result #else - string s, IFormatProvider provider, out Duid result + string s, +#pragma warning disable RCS1163, IDE0060// Unused parameter + IFormatProvider provider, +#pragma warning restore IDE0060, RCS1163 // Unused parameter + out Duid result #endif ) { @@ -62,7 +65,11 @@ public static bool TryParse( /// string of undelimited hexadecimal octet pairs /// /// - public static Duid Parse(string s, IFormatProvider provider) + public static Duid Parse(string s, +#pragma warning disable RCS1163, IDE0060// Unused parameter + IFormatProvider provider +#pragma warning restore IDE0060, RCS1163 // Unused parameter + ) { return Parse(s); // Use the existing TryParse method as the provider is not relevant } diff --git a/src/NetDuid/Duid.ISerializable.cs b/src/NetDuid/Duid.ISerializable.cs index 4c1cd5f..15569e6 100644 --- a/src/NetDuid/Duid.ISerializable.cs +++ b/src/NetDuid/Duid.ISerializable.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace NetDuid { diff --git a/src/NetDuid/Duid.Operators.cs b/src/NetDuid/Duid.Operators.cs index 86ac94b..617a891 100644 --- a/src/NetDuid/Duid.Operators.cs +++ b/src/NetDuid/Duid.Operators.cs @@ -60,7 +60,7 @@ public sealed partial class Duid if (lhs is null) { - return !(rhs is null); // null is considered less than any non-null value + return rhs is not null; // null is considered less than any non-null value } if (rhs is null) @@ -89,7 +89,7 @@ public sealed partial class Duid if (lhs is null) { - return !(rhs is null); // null is considered less than any non-null value + return rhs is not null; // null is considered less than any non-null value } if (rhs is null) diff --git a/src/NetDuid/Duid.cs b/src/NetDuid/Duid.cs index 93791f4..c352bbb 100644 --- a/src/NetDuid/Duid.cs +++ b/src/NetDuid/Duid.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using System.Diagnostics; namespace NetDuid { @@ -41,7 +38,7 @@ public Duid(IEnumerable bytes) _duidBytes = ConstructWithBytesGuard(bytes); Type = GetDuidType(); - _lazyHashCode = new Lazy(ComputeHashCode); // for a lazy hash code requiring only a single generation + _lazyHashCode = new Lazy(ComputeHashCode); } /// diff --git a/src/NetDuid/DuidRegexSource.cs b/src/NetDuid/DuidRegexPatterns.cs similarity index 93% rename from src/NetDuid/DuidRegexSource.cs rename to src/NetDuid/DuidRegexPatterns.cs index f22631b..66eb052 100644 --- a/src/NetDuid/DuidRegexSource.cs +++ b/src/NetDuid/DuidRegexPatterns.cs @@ -13,7 +13,7 @@ internal static #if NET7_0_OR_GREATER partial #endif - class DuidRegexSource + class DuidRegexPatterns { /// /// Regex pattern that should match a string of hexadecimal octet pairs delimited by a single dash ('-'), colon (':') or space (' ') character. Leading 0 in pair may be omitted. @@ -53,7 +53,11 @@ public static Regex GetDelimitedOctetsRegex() return DelimitedOctetsRegex; } - private static readonly Regex DelimitedOctetsRegex = new Regex(DelimitedOctetsPattern, RegexOptions.Compiled); + private static readonly Regex DelimitedOctetsRegex = new( + DelimitedOctetsPattern, + RegexOptions.Compiled, + TimeSpan.FromSeconds(1) + ); #endif /// @@ -69,9 +73,10 @@ public static Regex GetUndelimitedOctetsRegex() return UndelimitedOctetsRegex; } - private static readonly Regex UndelimitedOctetsRegex = new Regex( + private static readonly Regex UndelimitedOctetsRegex = new( UndelimitedOctetPattern, - RegexOptions.Compiled | RegexOptions.IgnoreCase + RegexOptions.Compiled | RegexOptions.IgnoreCase, + TimeSpan.FromSeconds(1) ); #endif } diff --git a/src/NetDuid/DuidType.cs b/src/NetDuid/DuidType.cs index e355729..a992373 100644 --- a/src/NetDuid/DuidType.cs +++ b/src/NetDuid/DuidType.cs @@ -6,7 +6,7 @@ public enum DuidType { /// - /// An unspecified DUID type not specified in RFC8415 of RFC6355 + /// An unspecified DUID type not specified in RFC8415 or RFC6355 /// /// DUID type code 0x0000 or within range 0x0005-0xFFFF inclusive Undefined = 0, diff --git a/src/NetDuid/NetDuid.csproj b/src/NetDuid/NetDuid.csproj index f206d42..b7adb33 100644 --- a/src/NetDuid/NetDuid.csproj +++ b/src/NetDuid/NetDuid.csproj @@ -2,7 +2,8 @@ netstandard2.0;net8.0;net9.0;net10.0 - bin\ + 14.0 + bin/ $(OutputPath)$(AssemblyName).xml true @@ -23,17 +24,20 @@ NetDuid NetDuid - A robust and efficient .NET representation of DHCP Unique Identifiers (DUIDs) as specified in RFC 8415 and RFC 6355. - true + A robust and efficient .NET representation of DHCP Unique Identifiers (DUIDs) as + specified in RFC 8415 and RFC 6355. en en-US Apache-2.0 Sandia National Laboratories The Production Tools Team - Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software + Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC + (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains + certain rights in this software README.md DUID;DHCP;DHCPv6;IPv6;rfc8415;rfc6355 - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb https://github.com/sandialabs/NetDuid.git https://github.com/sandialabs/NetDuid git @@ -72,43 +76,45 @@ - + stylecop.json - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - + diff --git a/src/NetDuid/packages.lock.json b/src/NetDuid/packages.lock.json index c1626df..d1b982f 100644 --- a/src/NetDuid/packages.lock.json +++ b/src/NetDuid/packages.lock.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.Bcl.HashCode": { @@ -10,12 +10,13 @@ }, "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "requested": "[10.0.300, )", + "resolved": "10.0.300", + "contentHash": "QzCtLkXVb3l4IxcpvJCbzUwMLihAmLN6vVLjQGSzYSF8d2dvXxqJAZk83RV3gYnp2egz8jRMgSR2woY3vOahTA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "8.0.0", - "Microsoft.SourceLink.Common": "8.0.0" + "Microsoft.Build.Tasks.Git": "10.0.300", + "Microsoft.SourceLink.Common": "10.0.300", + "System.IO.Hashing": "10.0.8" } }, "NETStandard.Library": { @@ -29,41 +30,41 @@ }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + "resolved": "10.0.300", + "contentHash": "P0kaQwVZx4xIUe2FtrLyBadYNXuAljttJUPvjBYRuHhPE8L77L42KakLDkaADRiUrGspoLcMwayjrbQhYTr0zA==", + "dependencies": { + "System.IO.Hashing": "10.0.8" + } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -72,193 +73,225 @@ }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + "resolved": "10.0.300", + "contentHash": "0jlkXaUGjYlWTIVPve5MftjKHnT3SlAtq9BCLV4J9IjdPrxV/+4rMlBSjfr1khG8/GC6KGojjola8E1VvWF0qQ==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+dJsbPJ3FyUbTZNplFj0RCKePFizmv6ewDV46JE9q/IVH4c3xTCftHfHelLsAKf0jryIPqgMb5GpS0x7TAY3mg==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Memory": "4.6.3" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.6.3", + "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Numerics.Vectors": "4.6.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" }, - "StyleCop.Analyzers.Unstable": { + "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" } }, "net10.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "requested": "[10.0.300, )", + "resolved": "10.0.300", + "contentHash": "QzCtLkXVb3l4IxcpvJCbzUwMLihAmLN6vVLjQGSzYSF8d2dvXxqJAZk83RV3gYnp2egz8jRMgSR2woY3vOahTA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "8.0.0", - "Microsoft.SourceLink.Common": "8.0.0" + "Microsoft.Build.Tasks.Git": "10.0.300", + "Microsoft.SourceLink.Common": "10.0.300", + "System.IO.Hashing": "10.0.8" } }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + "resolved": "10.0.300", + "contentHash": "P0kaQwVZx4xIUe2FtrLyBadYNXuAljttJUPvjBYRuHhPE8L77L42KakLDkaADRiUrGspoLcMwayjrbQhYTr0zA==", + "dependencies": { + "System.IO.Hashing": "10.0.8" + } }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + "resolved": "10.0.300", + "contentHash": "0jlkXaUGjYlWTIVPve5MftjKHnT3SlAtq9BCLV4J9IjdPrxV/+4rMlBSjfr1khG8/GC6KGojjola8E1VvWF0qQ==" }, - "StyleCop.Analyzers.Unstable": { + "System.IO.Hashing": { "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + "resolved": "10.0.8", + "contentHash": "+dJsbPJ3FyUbTZNplFj0RCKePFizmv6ewDV46JE9q/IVH4c3xTCftHfHelLsAKf0jryIPqgMb5GpS0x7TAY3mg==" } }, "net8.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "requested": "[10.0.300, )", + "resolved": "10.0.300", + "contentHash": "QzCtLkXVb3l4IxcpvJCbzUwMLihAmLN6vVLjQGSzYSF8d2dvXxqJAZk83RV3gYnp2egz8jRMgSR2woY3vOahTA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "8.0.0", - "Microsoft.SourceLink.Common": "8.0.0" + "Microsoft.Build.Tasks.Git": "10.0.300", + "Microsoft.SourceLink.Common": "10.0.300", + "System.IO.Hashing": "10.0.8" } }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + "resolved": "10.0.300", + "contentHash": "P0kaQwVZx4xIUe2FtrLyBadYNXuAljttJUPvjBYRuHhPE8L77L42KakLDkaADRiUrGspoLcMwayjrbQhYTr0zA==", + "dependencies": { + "System.IO.Hashing": "10.0.8" + } }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + "resolved": "10.0.300", + "contentHash": "0jlkXaUGjYlWTIVPve5MftjKHnT3SlAtq9BCLV4J9IjdPrxV/+4rMlBSjfr1khG8/GC6KGojjola8E1VvWF0qQ==" }, - "StyleCop.Analyzers.Unstable": { + "System.IO.Hashing": { "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + "resolved": "10.0.8", + "contentHash": "+dJsbPJ3FyUbTZNplFj0RCKePFizmv6ewDV46JE9q/IVH4c3xTCftHfHelLsAKf0jryIPqgMb5GpS0x7TAY3mg==" } }, "net9.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "requested": "[10.0.300, )", + "resolved": "10.0.300", + "contentHash": "QzCtLkXVb3l4IxcpvJCbzUwMLihAmLN6vVLjQGSzYSF8d2dvXxqJAZk83RV3gYnp2egz8jRMgSR2woY3vOahTA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "8.0.0", - "Microsoft.SourceLink.Common": "8.0.0" + "Microsoft.Build.Tasks.Git": "10.0.300", + "Microsoft.SourceLink.Common": "10.0.300", + "System.IO.Hashing": "10.0.8" } }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "yMSjze/xMYDF6PCE60/ULWx0tttNyKAndw2KijNxbKil0FX8nvDeEneDZGma8Uifk17RlfZqIXxf1mmBmhRHjg==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "E8fu71Y+vzhniJKy1K13Rzwm8Vjcj7CDFBpflqncKUZfc3SzuF0kMBiyMmgXNPkjWTdE3Z+30HlGgYjHONZY/Q==" }, "Roslynator.CodeAnalysis.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "axessewcgSLn+UAtKyMKPgXpzIldnivynjRe9qsRjrVtrxjctDpFDEjAkcbpDjhR0ccm8/+ihOna60wnXJ/66A==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "XPY6vqHG8jLJ1S0SGzLDtyuDjmkcIAGuLRrsG3SlAuyAOlwnVfA1glgIy6YqXutoi7hwd1fqZC2W9zoMntuZNg==" }, "Roslynator.Formatting.Analyzers": { "type": "Direct", - "requested": "[4.14.1, )", - "resolved": "4.14.1", - "contentHash": "hALZsyELJXhj7WQw7Q7nLI7sELre8AlQIMyGxXpmpK+iBqShPcWHuJMGjIsTMa/bC8Q5xyQ4iFfRtm0bP1zisQ==" + "requested": "[4.15.0, )", + "resolved": "4.15.0", + "contentHash": "/LvSJGJbrx+OYC2GePmiAA9fRTfz37R/oaHC2j8YK/j9lE8ykZESCGTMVGqXF5woc6RbzfJlpYkzeG6fAV6hFQ==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.15.0.120848, )", - "resolved": "10.15.0.120848", - "contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ==" + "requested": "[10.29.0.143774, )", + "resolved": "10.29.0.143774", + "contentHash": "YOBCHTjqb4ak7yjLQhQJ6S0+aTkgnuMcekjCrhWruyIIoWlB1C8W+Fgr18bWit+xx7Gd9sNl5sziOwh6D804Hg==" }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.556, )", - "resolved": "1.2.0-beta.556", - "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.556" - } + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + "resolved": "10.0.300", + "contentHash": "P0kaQwVZx4xIUe2FtrLyBadYNXuAljttJUPvjBYRuHhPE8L77L42KakLDkaADRiUrGspoLcMwayjrbQhYTr0zA==", + "dependencies": { + "System.IO.Hashing": "10.0.8" + } }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + "resolved": "10.0.300", + "contentHash": "0jlkXaUGjYlWTIVPve5MftjKHnT3SlAtq9BCLV4J9IjdPrxV/+4rMlBSjfr1khG8/GC6KGojjola8E1VvWF0qQ==" }, - "StyleCop.Analyzers.Unstable": { + "System.IO.Hashing": { "type": "Transitive", - "resolved": "1.2.0.556", - "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + "resolved": "10.0.8", + "contentHash": "+dJsbPJ3FyUbTZNplFj0RCKePFizmv6ewDV46JE9q/IVH4c3xTCftHfHelLsAKf0jryIPqgMb5GpS0x7TAY3mg==" } } } diff --git a/src/run-benchmarks.ps1 b/src/run-benchmarks.ps1 new file mode 100644 index 0000000..6d61c6f --- /dev/null +++ b/src/run-benchmarks.ps1 @@ -0,0 +1,34 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Run NetDuid benchmarks and export results to src\results\. + +.DESCRIPTION + Builds the benchmark project in Release mode and runs all benchmarks. + Results land in src\results\\ as JSON and Markdown files. + Commit that directory when you want to record a progression snapshot. + +.PARAMETER AdditionalArgs + Extra arguments forwarded to BenchmarkDotNet (e.g. "--filter *Subnet*"). + +.EXAMPLE + .\src\run-benchmarks.ps1 + .\src\run-benchmarks.ps1 -AdditionalArgs "--filter","*Subnet*" +#> + +param( + [string[]]$AdditionalArgs = @() +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = $PSScriptRoot + +dotnet run --project "$ScriptDir\NetDuid.Benchmarks" ` + -c Release ` + -- --artifacts "$ScriptDir\results" --exporters json markdown @AdditionalArgs + +if ($LASTEXITCODE -ne 0) { + Write-Host "Benchmarks failed with exit code $LASTEXITCODE" -ForegroundColor Red + exit $LASTEXITCODE +} diff --git a/src/run-benchmarks.sh b/src/run-benchmarks.sh new file mode 100644 index 0000000..687dc88 --- /dev/null +++ b/src/run-benchmarks.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Run NetDuid benchmarks and export results to src/results/. +# +# Usage: +# ./src/run-benchmarks.sh # run all benchmarks +# ./src/run-benchmarks.sh -- --filter *Subnet* # run a subset +# +# Results land in src/results// as JSON and Markdown files. +# Commit that directory when you want to record a progression snapshot. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +dotnet run --project "${SCRIPT_DIR}/NetDuid.Benchmarks" \ + -c Release \ + -- --artifacts "${SCRIPT_DIR}/results" --exporters json markdown "$@" diff --git a/src/stylecop.json b/stylecop.json similarity index 99% rename from src/stylecop.json rename to stylecop.json index 41588fe..286a3cb 100644 --- a/src/stylecop.json +++ b/stylecop.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": {