├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── test.yml │ └── zizmor.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── MaxMind-logo.png ├── MaxMind.Db.Benchmark ├── MaxMind.Db.Benchmark.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── MaxMind.Db.Test ├── .runsettings ├── DecoderTest.cs ├── GlobalSuppressions.cs ├── Helper │ ├── NoNetworkTypeHolder.cs │ ├── NonSeekableStreamWrapper.cs │ ├── TestUtils.cs │ └── TypeHolder.cs ├── MaxMind.Db.Test.csproj ├── NetworkTest.cs ├── PointerTest.cs ├── Properties │ └── AssemblyInfo.cs ├── ReaderTest.cs ├── ThreadingTest.cs └── runsettings.txt ├── MaxMind.Db.sln ├── MaxMind.Db ├── ArrayBuffer.cs ├── Buffer.cs ├── CachedDictionary.cs ├── ConstructorAttribute.cs ├── CustomDictionary.xml ├── Decoder.cs ├── DeserializationException.cs ├── DictionaryActivatorCreator.cs ├── GlobalSuppressions.cs ├── InjectAttribute.cs ├── InjectableValues.cs ├── InvalidDatabaseException.cs ├── Key.cs ├── ListActivatorCreator.cs ├── MaxMind.Db.csproj ├── MaxMind.Db.ruleset ├── MemoryMapBuffer.cs ├── Metadata.cs ├── Network.cs ├── NetworkAttribute.cs ├── ParameterAttribute.cs ├── Properties │ └── AssemblyInfo.cs ├── Reader.cs ├── ReflectionUtil.cs └── TypeAcivatorCreator.cs ├── MaxMind.snk ├── NuGet.Config ├── README.dev.md ├── README.md ├── dev-bin └── release.ps1 └── releasenotes.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = space 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Coding Conventions #### 19 | 20 | # Organize usings 21 | dotnet_separate_import_directive_groups = false 22 | dotnet_sort_system_directives_first = false 23 | file_header_template = unset 24 | 25 | # this. and Me. preferences 26 | dotnet_style_qualification_for_event = false 27 | dotnet_style_qualification_for_field = false 28 | dotnet_style_qualification_for_method = false 29 | dotnet_style_qualification_for_property = false 30 | 31 | # Language keywords vs BCL types preferences 32 | dotnet_style_predefined_type_for_locals_parameters_members = true 33 | dotnet_style_predefined_type_for_member_access = true 34 | 35 | # Parentheses preferences 36 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 40 | 41 | # Modifier preferences 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 43 | 44 | # Expression-level preferences 45 | dotnet_style_coalesce_expression = true 46 | dotnet_style_collection_initializer = true 47 | dotnet_style_explicit_tuple_names = true 48 | dotnet_style_namespace_match_folder = true 49 | dotnet_style_null_propagation = true 50 | dotnet_style_object_initializer = true 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true 53 | dotnet_style_prefer_compound_assignment = true 54 | dotnet_style_prefer_conditional_expression_over_assignment = true 55 | dotnet_style_prefer_conditional_expression_over_return = true 56 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 57 | dotnet_style_prefer_inferred_tuple_names = true 58 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 59 | dotnet_style_prefer_simplified_boolean_expressions = true 60 | dotnet_style_prefer_simplified_interpolation = true 61 | 62 | # Field preferences 63 | dotnet_style_readonly_field = true 64 | 65 | # Parameter preferences 66 | dotnet_code_quality_unused_parameters = all 67 | 68 | # Suppression preferences 69 | dotnet_remove_unnecessary_suppression_exclusions = none 70 | 71 | # New line preferences 72 | dotnet_style_allow_multiple_blank_lines_experimental = true 73 | dotnet_style_allow_statement_immediately_after_block_experimental = true 74 | 75 | #### C# Coding Conventions #### 76 | 77 | # var preferences 78 | csharp_style_var_elsewhere = true 79 | csharp_style_var_for_built_in_types = true 80 | csharp_style_var_when_type_is_apparent = true 81 | 82 | # Expression-bodied members 83 | csharp_style_expression_bodied_accessors = true 84 | csharp_style_expression_bodied_constructors = false 85 | csharp_style_expression_bodied_indexers = true 86 | csharp_style_expression_bodied_lambdas = true 87 | csharp_style_expression_bodied_local_functions = false 88 | csharp_style_expression_bodied_methods = false 89 | csharp_style_expression_bodied_operators = false 90 | csharp_style_expression_bodied_properties = true 91 | 92 | # Pattern matching preferences 93 | csharp_style_pattern_matching_over_as_with_null_check = true 94 | csharp_style_pattern_matching_over_is_with_cast_check = true 95 | csharp_style_prefer_not_pattern = true 96 | csharp_style_prefer_pattern_matching = true 97 | csharp_style_prefer_switch_expression = true 98 | 99 | # Null-checking preferences 100 | csharp_style_conditional_delegate_call = true 101 | 102 | # Modifier preferences 103 | csharp_prefer_static_local_function = true 104 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 105 | 106 | # Code-block preferences 107 | csharp_prefer_braces = true 108 | csharp_prefer_simple_using_statement = true 109 | csharp_style_namespace_declarations = block_scoped 110 | 111 | # Expression-level preferences 112 | csharp_prefer_simple_default_expression = true 113 | csharp_style_deconstructed_variable_declaration = true 114 | csharp_style_implicit_object_creation_when_type_is_apparent = true 115 | csharp_style_inlined_variable_declaration = true 116 | csharp_style_pattern_local_over_anonymous_function = true 117 | csharp_style_prefer_index_operator = true 118 | csharp_style_prefer_null_check_over_type_check = true 119 | csharp_style_prefer_range_operator = true 120 | csharp_style_throw_expression = true 121 | csharp_style_unused_value_assignment_preference = discard_variable 122 | csharp_style_unused_value_expression_statement_preference = discard_variable 123 | 124 | # 'using' directive preferences 125 | csharp_using_directive_placement = outside_namespace 126 | 127 | # New line preferences 128 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true 129 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true 130 | csharp_style_allow_embedded_statements_on_same_line_experimental = true 131 | 132 | #### C# Formatting Rules #### 133 | 134 | # New line preferences 135 | csharp_new_line_before_catch = true 136 | csharp_new_line_before_else = true 137 | csharp_new_line_before_finally = true 138 | csharp_new_line_before_members_in_anonymous_types = true 139 | csharp_new_line_before_members_in_object_initializers = true 140 | csharp_new_line_before_open_brace = all 141 | csharp_new_line_between_query_expression_clauses = true 142 | 143 | # Indentation preferences 144 | csharp_indent_block_contents = true 145 | csharp_indent_braces = false 146 | csharp_indent_case_contents = true 147 | csharp_indent_case_contents_when_block = true 148 | csharp_indent_labels = no_change 149 | csharp_indent_switch_labels = true 150 | 151 | # Space preferences 152 | csharp_space_after_cast = false 153 | csharp_space_after_colon_in_inheritance_clause = true 154 | csharp_space_after_comma = true 155 | csharp_space_after_dot = false 156 | csharp_space_after_keywords_in_control_flow_statements = true 157 | csharp_space_after_semicolon_in_for_statement = true 158 | csharp_space_around_binary_operators = before_and_after 159 | csharp_space_around_declaration_statements = false 160 | csharp_space_before_colon_in_inheritance_clause = true 161 | csharp_space_before_comma = false 162 | csharp_space_before_dot = false 163 | csharp_space_before_open_square_brackets = false 164 | csharp_space_before_semicolon_in_for_statement = false 165 | csharp_space_between_empty_square_brackets = false 166 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 167 | csharp_space_between_method_call_name_and_opening_parenthesis = false 168 | csharp_space_between_method_call_parameter_list_parentheses = false 169 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 170 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 171 | csharp_space_between_method_declaration_parameter_list_parentheses = false 172 | csharp_space_between_parentheses = false 173 | csharp_space_between_square_brackets = false 174 | 175 | # Wrapping preferences 176 | csharp_preserve_single_line_blocks = true 177 | csharp_preserve_single_line_statements = true 178 | 179 | #### Naming styles #### 180 | 181 | # Naming rules 182 | 183 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 184 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 185 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 186 | 187 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 188 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 189 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 190 | 191 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 192 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 193 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 194 | 195 | # Symbol specifications 196 | 197 | dotnet_naming_symbols.interface.applicable_kinds = interface 198 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 199 | dotnet_naming_symbols.interface.required_modifiers = 200 | 201 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 202 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 203 | dotnet_naming_symbols.types.required_modifiers = 204 | 205 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 206 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 207 | dotnet_naming_symbols.non_field_members.required_modifiers = 208 | 209 | # Naming styles 210 | 211 | dotnet_naming_style.pascal_case.required_prefix = 212 | dotnet_naming_style.pascal_case.required_suffix = 213 | dotnet_naming_style.pascal_case.word_separator = 214 | dotnet_naming_style.pascal_case.capitalization = pascal_case 215 | 216 | dotnet_naming_style.begins_with_i.required_prefix = I 217 | dotnet_naming_style.begins_with_i.required_suffix = 218 | dotnet_naming_style.begins_with_i.word_separator = 219 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 220 | 221 | # Options 222 | 223 | roslynator_accessibility_modifiers = explicit 224 | # Applicable to: rcs1018 225 | 226 | roslynator_accessor_braces_style = multi_line 227 | # Default: multi_line 228 | # Applicable to: rcs0020 229 | 230 | roslynator_array_creation_type_style = implicit 231 | # Applicable to: rcs1014 232 | 233 | roslynator_arrow_token_new_line = before 234 | # Applicable to: rcs0032 235 | 236 | roslynator_binary_operator_new_line = before 237 | # Applicable to: rcs0027 238 | 239 | roslynator_blank_line_between_closing_brace_and_switch_section = true 240 | # Applicable to: rcs0014, rcs1036 241 | 242 | roslynator_blank_line_between_single_line_accessors = true 243 | # Applicable to: rcs0011 244 | 245 | roslynator_blank_line_between_using_directives = separate_groups 246 | # Applicable to: rcs0015 247 | 248 | roslynator_body_style = expression 249 | # Applicable to: rcs1016 250 | 251 | roslynator_conditional_operator_condition_parentheses_style = omit_when_condition_is_single_token 252 | # Applicable to: rcs1051 253 | 254 | roslynator_conditional_operator_new_line = before 255 | # Applicable to: rcs0028 256 | 257 | roslynator_configure_await = true 258 | # Applicable to: rcs1090 259 | 260 | roslynator_empty_string_style = literal 261 | # Applicable to: rcs1078 262 | 263 | roslynator_enum_has_flag_style = operator 264 | # Applicable to: rcs1096 265 | 266 | roslynator_equals_token_new_line = before 267 | # Applicable to: rcs0052 268 | 269 | roslynator_new_line_at_end_of_file = true 270 | # Applicable to: rcs0058 271 | 272 | roslynator_new_line_before_while_in_do_statement = false 273 | # Applicable to: rcs0051 274 | 275 | roslynator_null_conditional_operator_new_line = before 276 | # Applicable to: rcs0059 277 | 278 | roslynator_null_check_style = pattern_matching 279 | # Applicable to: rcs1248 280 | 281 | roslynator_object_creation_parentheses_style = omit 282 | # Applicable to: rcs1050 283 | 284 | roslynator_object_creation_type_style = implicit_when_type_is_obvious 285 | # Applicable to: rcs1250 286 | 287 | roslynator_prefix_field_identifier_with_underscore = true 288 | 289 | roslynator_suppress_unity_script_methods = true 290 | # Applicable to: rcs1213 291 | 292 | roslynator_use_anonymous_function_or_method_group = anonymous_function 293 | # Applicable to: rcs1207 294 | 295 | roslynator_use_block_body_when_declaration_spans_over_multiple_lines = true 296 | # Applicable to: rcs1016 297 | 298 | roslynator_use_block_body_when_expression_spans_over_multiple_lines = true 299 | # Applicable to: rcs1016 300 | 301 | roslynator_use_var_instead_of_implicit_object_creation = true 302 | # Applicable to: rcs1250 303 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directories: 5 | - "**/*" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'dependabot/**' 7 | pull_request: 8 | schedule: 9 | - cron: '0 14 * * 6' 10 | 11 | jobs: 12 | CodeQL-Build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | security-events: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | with: 23 | # We must fetch at least the immediate parents so that if this is 24 | # a pull request then we can checkout the head. 25 | fetch-depth: 2 26 | persist-credentials: false 27 | 28 | # If this run was triggered by a pull request event, then checkout 29 | # the head of the pull request instead of the merge commit. 30 | - run: git checkout HEAD^2 31 | if: ${{ github.event_name == 'pull_request' }} 32 | 33 | - name: Setup .NET 34 | uses: actions/setup-dotnet@v4 35 | with: 36 | dotnet-version: | 37 | 8.0.x 38 | 9.0.x 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v3 43 | # Override language selection by uncommenting this and choosing your languages 44 | # with: 45 | # languages: go, javascript, csharp, python, cpp, java 46 | 47 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 48 | # If this step fails, then you should remove it and run the build manually (see below) 49 | - name: Autobuild 50 | uses: github/codeql-action/autobuild@v3 51 | 52 | # ℹ️ Command-line programs to run using the OS shell. 53 | # 📚 https://git.io/JvXDl 54 | 55 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 56 | # and modify them (or add more) to build your code if your project 57 | # uses a compiled language 58 | 59 | #- run: | 60 | # make bootstrap 61 | # make release 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@v3 65 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '3 20 * * SUN' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | platform: [ubuntu-latest, macos-latest, windows-latest] 15 | runs-on: ${{ matrix.platform }} 16 | name: Dotnet on ${{ matrix.platform }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | persist-credentials: false 22 | 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v4 25 | with: 26 | dotnet-version: | 27 | 8.0.x 28 | 9.0.x 29 | 30 | - name: Build 31 | run: | 32 | dotnet build MaxMind.Db 33 | dotnet build MaxMind.Db.Benchmark 34 | dotnet build MaxMind.Db.Test 35 | 36 | - name: Run benchmark 37 | run: dotnet run -f net8.0 --project MaxMind.Db.Benchmark/MaxMind.Db.Benchmark.csproj 38 | env: 39 | MAXMIND_BENCHMARK_DB: ${{ github.workspace }}/MaxMind.Db.Test/TestData/MaxMind-DB/test-data/GeoIP2-City-Test.mmdb 40 | 41 | - name: Run tests 42 | run: dotnet test MaxMind.Db.Test/MaxMind.Db.Test.csproj 43 | env: 44 | MAXMIND_TEST_BASE_DIR: ${{ github.workspace }}/MaxMind.Db.Test 45 | -------------------------------------------------------------------------------- /.github/workflows/zizmor.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Security Analysis with zizmor 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["**"] 8 | 9 | jobs: 10 | zizmor: 11 | name: zizmor latest via PyPI 12 | runs-on: ubuntu-latest 13 | permissions: 14 | security-events: write 15 | # required for workflows in private repositories 16 | contents: read 17 | actions: read 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | with: 22 | persist-credentials: false 23 | 24 | - name: Install the latest version of uv 25 | uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # 6.1.0 26 | with: 27 | enable-cache: false 28 | 29 | - name: Run zizmor 30 | run: uvx zizmor@1.7.0 --format plain . 31 | env: 32 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # dotnet CLI stuff 6 | .dotnetcli/ 7 | scripts/ 8 | 9 | # mstest test results 10 | TestResults 11 | 12 | ## Ignore Visual Studio temporary files, build results, and 13 | ## files generated by popular Visual Studio add-ons. 14 | 15 | # Visual Studo 2015 cache/options directory 16 | .vs/ 17 | 18 | # User-specific files 19 | *.suo 20 | *.user 21 | *.sln.docstates 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Rr]elease/ 26 | x64/ 27 | *_i.c 28 | *_p.c 29 | *.ilk 30 | *.meta 31 | *.nupkg 32 | *.obj 33 | *.pch 34 | *.pdb 35 | *.pgc 36 | *.pgd 37 | *.rsp 38 | *.sbr 39 | *.tlb 40 | *.tli 41 | *.tlh 42 | *.tmp 43 | *.log 44 | *.vspscc 45 | *.vssscc 46 | .builds 47 | 48 | # Visual C++ cache files 49 | ipch/ 50 | *.aps 51 | *.ncb 52 | *.opensdf 53 | *.sdf 54 | 55 | # Visual Studio profiler 56 | *.psess 57 | *.vsp 58 | *.vspx 59 | 60 | # Guidance Automation Toolkit 61 | *.gpState 62 | 63 | # ReSharper is a .NET coding add-in 64 | _ReSharper* 65 | *.DotSettings 66 | 67 | # NCrunch 68 | *.ncrunch* 69 | .*crunch*.local.xml 70 | 71 | # Installshield output folder 72 | [Ee]xpress 73 | 74 | # DocProject is a documentation generator add-in 75 | DocProject/buildhelp/ 76 | DocProject/Help/*.HxT 77 | DocProject/Help/*.HxC 78 | DocProject/Help/*.hhc 79 | DocProject/Help/*.hhk 80 | DocProject/Help/*.hhp 81 | DocProject/Help/Html2 82 | DocProject/Help/html 83 | 84 | # Click-Once directory 85 | publish 86 | 87 | # Publish Web Output 88 | *.Publish.xml 89 | 90 | # NuGet Packages Directory 91 | packages 92 | *.lock.json 93 | 94 | # Windows Azure Build Output 95 | csx 96 | *.build.csdef 97 | 98 | # Windows Store app package directory 99 | AppPackages/ 100 | 101 | # Others 102 | [Bb]in 103 | [Oo]bj 104 | artifacts 105 | sql 106 | TestResults 107 | [Tt]est[Rr]esult* 108 | *.Cache 109 | ClientBin 110 | [Ss]tyle[Cc]op.* 111 | ~$* 112 | *.dbmdl 113 | Generated_Code #added for RIA/Silverlight projects 114 | 115 | # Backup & report files from converting an old project file to a newer 116 | # Visual Studio version. Backup files are not needed, because we have git ;-) 117 | _UpgradeReport_Files/ 118 | Backup*/ 119 | UpgradeLog*.XML 120 | UPgradeLog.htm 121 | 122 | # MonoDevelop 123 | *.userprefs 124 | test-results 125 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MaxMind.Db.Test/TestData/MaxMind-DB"] 2 | path = MaxMind.Db.Test/TestData/MaxMind-DB 3 | url = https://github.com/maxmind/MaxMind-DB.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /MaxMind-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmind/MaxMind-DB-Reader-dotnet/d78205669bf782f27ffc3033367c29b0459713ae/MaxMind-logo.png -------------------------------------------------------------------------------- /MaxMind.Db.Benchmark/MaxMind.Db.Benchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Benchmark project to validate .NET reader for the MaxMind DB file format 5 | 4.0.0 6 | net9.0;net8.0;net481 7 | net9.0;net8.0 8 | MaxMind.Db.Benchmark 9 | Exe 10 | MaxMind.Db.Benchmark 11 | https://github.com/maxmind/MaxMind-DB-Reader-dotnet 12 | Apache-2.0 13 | false 14 | false 15 | false 16 | false 17 | false 18 | false 19 | false 20 | false 21 | 13.0 22 | enable 23 | latest 24 | true 25 | true 26 | true 27 | 28 | 29 | 30 | AnyCPU 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MaxMind.Db.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | using BenchmarkDotNet.Running; 4 | using MaxMind.Db; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | using System.Linq; 9 | using System.Net; 10 | 11 | BenchmarkRunner.Run(new DebugInProcessConfig()); 12 | 13 | [MemoryDiagnoser] 14 | public class CityBenchmark 15 | { 16 | // A random IP that has city info. 17 | private Reader _reader = null!; 18 | private IPAddress[] _ipAddresses = []; 19 | 20 | [GlobalSetup] 21 | public void GlobalSetup() 22 | { 23 | const string dbPathVarName = "MAXMIND_BENCHMARK_DB"; 24 | string dbPath = Environment.GetEnvironmentVariable(dbPathVarName) ?? 25 | throw new InvalidOperationException($"{dbPathVarName} was not set"); 26 | _reader = new Reader(dbPath); 27 | 28 | const string ipAddressesVarName = "MAXMIND_BENCHMARK_IP_ADDRESSES"; 29 | string ipAddressesStr = Environment.GetEnvironmentVariable(ipAddressesVarName) ?? ""; 30 | _ipAddresses = ipAddressesStr 31 | .Split([','], StringSplitOptions.RemoveEmptyEntries) 32 | .Select(IPAddress.Parse) 33 | .ToArray(); 34 | if (_ipAddresses.Length == 0) 35 | { 36 | Random random = new(Seed: 0); 37 | List list = []; 38 | for (int i = 0; i < 1_000; i += 1) 39 | { 40 | list.Add(new IPAddress(random.Next())); 41 | } 42 | 43 | _ipAddresses = list.ToArray(); 44 | } 45 | } 46 | 47 | [GlobalCleanup] 48 | public void GlobalCleanup() 49 | { 50 | _reader.Dispose(); 51 | } 52 | 53 | [Benchmark] 54 | public int City() 55 | { 56 | int x = 0; 57 | foreach (var ipAddress in _ipAddresses) 58 | { 59 | if (_reader.Find(ipAddress) != null) 60 | { 61 | x += 1; 62 | } 63 | } 64 | 65 | return x; 66 | } 67 | } 68 | 69 | public abstract class AbstractCountryResponse 70 | { 71 | protected AbstractCountryResponse( 72 | Continent? continent = null, 73 | Country? country = null, 74 | Country? registeredCountry = null) 75 | { 76 | Continent = continent ?? new Continent(); 77 | Country = country ?? new Country(); 78 | RegisteredCountry = registeredCountry ?? new Country(); 79 | } 80 | 81 | public Continent Continent { get; internal set; } 82 | public Country Country { get; internal set; } 83 | public Country RegisteredCountry { get; internal set; } 84 | } 85 | 86 | public abstract class AbstractCityResponse : AbstractCountryResponse 87 | { 88 | protected AbstractCityResponse( 89 | City? city = null, 90 | Continent? continent = null, 91 | Country? country = null, 92 | Location? location = null, 93 | Country? registeredCountry = null, 94 | IReadOnlyList? subdivisions = null) 95 | : base(continent, country, registeredCountry) 96 | { 97 | City = city ?? new City(); 98 | Location = location ?? new Location(); 99 | Subdivisions = subdivisions ?? new List().AsReadOnly(); 100 | } 101 | 102 | public City City { get; internal set; } 103 | public Location Location { get; internal set; } 104 | public IReadOnlyList Subdivisions { get; internal set; } 105 | } 106 | 107 | public class CityResponse : AbstractCityResponse 108 | { 109 | [Constructor] 110 | public CityResponse( 111 | City? city = null, 112 | Continent? continent = null, 113 | Country? country = null, 114 | Location? location = null, 115 | [Parameter("registered_country")] Country? registeredCountry = null) 116 | : base(city, continent, country, location, registeredCountry) 117 | { 118 | } 119 | } 120 | 121 | public class City : NamedEntity 122 | { 123 | [Constructor] 124 | public City(int? confidence = null, 125 | [Parameter("geoname_id")] long? geoNameId = null, 126 | IReadOnlyDictionary? names = null, 127 | IReadOnlyList? locales = null) 128 | : base(geoNameId, names, locales) 129 | { 130 | Confidence = confidence; 131 | } 132 | 133 | public int? Confidence { get; internal set; } 134 | } 135 | 136 | public abstract class NamedEntity 137 | { 138 | [Constructor] 139 | protected NamedEntity(long? geoNameId = null, IReadOnlyDictionary? names = null, 140 | IReadOnlyList? locales = null) 141 | { 142 | Names = names ?? new ReadOnlyDictionary(new Dictionary()); 143 | GeoNameId = geoNameId; 144 | Locales = locales ?? new List { "en" }.AsReadOnly(); 145 | } 146 | 147 | public IReadOnlyDictionary Names { get; internal set; } 148 | public long? GeoNameId { get; internal set; } 149 | protected internal IReadOnlyList Locales { get; set; } 150 | public string? Name 151 | { 152 | get 153 | { 154 | var locale = Locales.FirstOrDefault(l => Names.ContainsKey(l)); 155 | return locale == null ? null : Names[locale]; 156 | } 157 | } 158 | } 159 | 160 | public class Continent : NamedEntity 161 | { 162 | [Constructor] 163 | public Continent( 164 | string? code = null, 165 | [Parameter("geoname_id")] long? geoNameId = null, 166 | IReadOnlyDictionary? names = null, 167 | IReadOnlyList? locales = null) 168 | : base(geoNameId, names, locales) 169 | { 170 | Code = code; 171 | } 172 | 173 | public string? Code { get; internal set; } 174 | } 175 | 176 | public class Country : NamedEntity 177 | { 178 | [Constructor] 179 | public Country( 180 | int? confidence = null, 181 | [Parameter("geoname_id")] long? geoNameId = null, 182 | [Parameter("is_in_european_union")] bool isInEuropeanUnion = false, 183 | [Parameter("iso_code")] string? isoCode = null, 184 | IReadOnlyDictionary? names = null, 185 | IReadOnlyList? locales = null) 186 | : base(geoNameId, names, locales) 187 | { 188 | Confidence = confidence; 189 | IsoCode = isoCode; 190 | IsInEuropeanUnion = isInEuropeanUnion; 191 | } 192 | 193 | public int? Confidence { get; internal set; } 194 | public bool IsInEuropeanUnion { get; internal set; } 195 | public string? IsoCode { get; internal set; } 196 | } 197 | 198 | public class Location 199 | { 200 | [Constructor] 201 | public Location( 202 | [Parameter("accuracy_radius")] int? accuracyRadius = null, 203 | double? latitude = null, 204 | double? longitude = null, 205 | [Parameter("time_zone")] string? timeZone = null) 206 | { 207 | AccuracyRadius = accuracyRadius; 208 | Latitude = latitude; 209 | Longitude = longitude; 210 | TimeZone = timeZone; 211 | } 212 | 213 | public int? AccuracyRadius { get; internal set; } 214 | public int? AverageIncome { get; internal set; } 215 | public bool HasCoordinates => Latitude.HasValue && Longitude.HasValue; 216 | public double? Latitude { get; internal set; } 217 | public double? Longitude { get; internal set; } 218 | public int? PopulationDensity { get; internal set; } 219 | public string? TimeZone { get; internal set; } 220 | } 221 | 222 | public class Subdivision : NamedEntity 223 | { 224 | [Constructor] 225 | public Subdivision( 226 | int? confidence = null, 227 | [Parameter("geoname_id")] long? geoNameId = null, 228 | [Parameter("iso_code")] string? isoCode = null, 229 | IReadOnlyDictionary? names = null, 230 | IReadOnlyList? locales = null) 231 | : base(geoNameId, names, locales) 232 | { 233 | Confidence = confidence; 234 | IsoCode = isoCode; 235 | } 236 | 237 | public int? Confidence { get; internal set; } 238 | public string? IsoCode { get; internal set; } 239 | } -------------------------------------------------------------------------------- /MaxMind.Db.Benchmark/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | 6 | #endregion 7 | 8 | // General Information about an assembly is controlled through the following 9 | // set of attributes. Change these attribute values to modify the information 10 | // associated with an assembly. 11 | 12 | [assembly: AssemblyTitle("Test")] 13 | [assembly: AssemblyDescription("")] 14 | [assembly: AssemblyConfiguration("")] 15 | [assembly: AssemblyCompany("Microsoft")] 16 | [assembly: AssemblyProduct("Test")] 17 | [assembly: AssemblyCopyright("Copyright © Microsoft 2015")] 18 | [assembly: AssemblyTrademark("")] 19 | [assembly: AssemblyCulture("")] 20 | 21 | // Setting ComVisible to false makes the types in this assembly not visible 22 | // to COM components. If you need to access a type in this assembly from 23 | // COM, set the ComVisible attribute to true on that type. 24 | 25 | [assembly: ComVisible(false)] 26 | 27 | // The following GUID is for the ID of the typelib if this project is exposed to COM 28 | 29 | [assembly: Guid("44f6ab62-9326-4383-9954-1357a8160621")] 30 | 31 | // Version information for an assembly consists of the following four values: 32 | // 33 | // Major Version 34 | // Minor Version 35 | // Build Number 36 | // Revision 37 | // 38 | // You can specify all the values or you can default the Build and Revision Numbers 39 | // by using the '*' as shown below: 40 | // [assembly: AssemblyVersion("1.0.*")] 41 | 42 | [assembly: AssemblyVersion("1.0.1.0")] 43 | [assembly: AssemblyFileVersion("1.0.1.0")] -------------------------------------------------------------------------------- /MaxMind.Db.Test/.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | x64 5 | 6 | 7 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/DecoderTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Numerics; 6 | using System.Text; 7 | using Xunit; 8 | 9 | #endregion 10 | 11 | namespace MaxMind.Db.Test 12 | { 13 | public static class DecoderTest 14 | { 15 | [Theory] 16 | [MemberData(nameof(TestUInt16))] 17 | [MemberData(nameof(TestUInt32))] 18 | [MemberData(nameof(TestInt32s))] 19 | [MemberData(nameof(TestUInt64s))] 20 | [MemberData(nameof(TestBigIntegers))] 21 | [MemberData(nameof(TestDoubles))] 22 | [MemberData(nameof(TestFloats))] 23 | [MemberData(nameof(TestPointers))] 24 | [MemberData(nameof(TestStrings))] 25 | [MemberData(nameof(TestBooleans))] 26 | [MemberData(nameof(TestBytes))] 27 | [MemberData(nameof(TestMaps))] 28 | [MemberData(nameof(TestArrays))] 29 | public static void TestTypeDecoding(Dictionary tests) where T : class 30 | { 31 | foreach (var entry in tests) 32 | { 33 | var expect = entry.Key; 34 | var input = entry.Value; 35 | 36 | using var database = new ArrayBuffer(input); 37 | var decoder = new Decoder(database, 0, false); 38 | var val = decoder.Decode(0, out _); 39 | Assert.Equal(expect, val); 40 | } 41 | } 42 | 43 | public static IEnumerable TestUInt16() 44 | { 45 | var uint16s = new Dictionary 46 | { 47 | {0, [0xa0] }, 48 | {(1 << 8) - 1, [0xa1, 0xff] }, 49 | {500, [0xa2, 0x1, 0xf4] }, 50 | {10872, [0xa2, 0x2a, 0x78] }, 51 | {(int) ushort.MaxValue, [0xa2, 0xff, 0xff] } 52 | }; 53 | 54 | yield return [uint16s]; 55 | } 56 | 57 | public static IEnumerable TestUInt32() 58 | { 59 | var uint32s = new Dictionary 60 | { 61 | {0L, [0xc0] }, 62 | {(1L << 8) - 1, [0xc1, 0xff] }, 63 | {500L, [0xc2, 0x1, 0xf4] }, 64 | {10872L, [0xc2, 0x2a, 0x78] }, 65 | {(1L << 16) - 1, [0xc2, 0xff, 0xff] }, 66 | {(1L << 24) - 1, [0xc3, 0xff, 0xff, 0xff] }, 67 | {(long) uint.MaxValue, [0xc4, 0xff, 0xff, 0xff, 0xff] } 68 | }; 69 | 70 | yield return [uint32s]; 71 | } 72 | 73 | public static IEnumerable TestInt32s() 74 | { 75 | var int32s = new Dictionary 76 | { 77 | {0, [0x0, 0x1] }, 78 | {-1, [0x4, 0x1, 0xff, 0xff, 0xff, 0xff] }, 79 | {(2 << 7) - 1, [0x1, 0x1, 0xff] }, 80 | {1 - (2 << 7), [0x4, 0x1, 0xff, 0xff, 0xff, 0x1] }, 81 | {500, [0x2, 0x1, 0x1, 0xf4] }, 82 | {-500, [0x4, 0x1, 0xff, 0xff, 0xfe, 0xc] }, 83 | {(2 << 15) - 1, [0x2, 0x1, 0xff, 0xff] }, 84 | {1 - (2 << 15), [0x4, 0x1, 0xff, 0xff, 0x0, 0x1] }, 85 | {(2 << 23) - 1, [0x3, 0x1, 0xff, 0xff, 0xff] }, 86 | {1 - (2 << 23), [0x4, 0x1, 0xff, 0x0, 0x0, 0x1] }, 87 | {int.MaxValue, [0x4, 0x1, 0x7f, 0xff, 0xff, 0xff] }, 88 | {-int.MaxValue, [0x4, 0x1, 0x80, 0x0, 0x0, 0x1] } 89 | }; 90 | 91 | yield return [int32s]; 92 | } 93 | 94 | public static IEnumerable TestUInt64s() 95 | { 96 | var uint64s = new Dictionary 97 | { 98 | {0UL, [0x0, 0x2] }, 99 | {500UL, [0x2, 0x2, 0x1, 0xf4] }, 100 | {10872UL, [0x2, 0x2, 0x2a, 0x78] } 101 | }; 102 | 103 | for (var power = 1; power < 8; power++) 104 | { 105 | var key = UInt64Pow(2, 8 * power) - 1; 106 | var value = new byte[2 + power]; 107 | 108 | value[0] = (byte)power; 109 | value[1] = 0x2; 110 | for (var i = 2; i < value.Length; i++) 111 | { 112 | value[i] = 0xff; 113 | } 114 | 115 | uint64s.Add(key, value); 116 | } 117 | 118 | yield return [uint64s]; 119 | } 120 | 121 | public static ulong UInt64Pow(ulong x, int pow) 122 | { 123 | ulong ret = 1; 124 | while (pow != 0) 125 | { 126 | if ((pow & 1) == 1) 127 | ret *= x; 128 | x *= x; 129 | pow >>= 1; 130 | } 131 | return ret; 132 | } 133 | 134 | public static IEnumerable TestBigIntegers() 135 | { 136 | var bigInts = new Dictionary 137 | { 138 | {new BigInteger(0), [0x0, 0x3] }, 139 | {new BigInteger(500), [0x2, 0x3, 0x1, 0xf4] }, 140 | {new BigInteger(10872), [0x2, 0x3, 0x2a, 0x78] } 141 | }; 142 | 143 | for (var power = 1; power <= 16; power++) 144 | { 145 | var key = BigInteger.Pow(new BigInteger(2), 8 * power) - 1; 146 | var value = new byte[2 + power]; 147 | 148 | value[0] = (byte)power; 149 | value[1] = 0x3; 150 | for (var i = 2; i < value.Length; i++) 151 | { 152 | value[i] = 0xff; 153 | } 154 | 155 | bigInts.Add(key, value); 156 | } 157 | 158 | yield return [bigInts]; 159 | } 160 | 161 | public static IEnumerable TestDoubles() 162 | { 163 | var doubles = new Dictionary 164 | { 165 | {0.0, [0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0] }, 166 | {0.5, [0x68, 0x3F, 0xE0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0] }, 167 | {3.14159265359, [0x68, 0x40, 0x9, 0x21, 0xFB, 0x54, 0x44, 0x2E, 0xEA] }, 168 | {123.0, [0x68, 0x40, 0x5E, 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0] }, 169 | {1073741824.12457, [0x68, 0x41, 0xD0, 0x0, 0x0, 0x0, 0x7, 0xF8, 0xF4] }, 170 | {-0.5, [0x68, 0xBF, 0xE0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0] }, 171 | {-3.14159265359, [0x68, 0xC0, 0x9, 0x21, 0xFB, 0x54, 0x44, 0x2E, 0xEA] }, 172 | {-1073741824.12457, [0x68, 0xC1, 0xD0, 0x0, 0x0, 0x0, 0x7, 0xF8, 0xF4] } 173 | }; 174 | 175 | yield return [doubles]; 176 | } 177 | 178 | public static IEnumerable TestFloats() 179 | { 180 | var floats = new Dictionary 181 | { 182 | {(float) 0.0, [0x4, 0x8, 0x0, 0x0, 0x0, 0x0] }, 183 | {(float) 1.0, [0x4, 0x8, 0x3F, 0x80, 0x0, 0x0] }, 184 | {(float) 1.1, [0x4, 0x8, 0x3F, 0x8C, 0xCC, 0xCD] }, 185 | {(float) 3.14, [0x4, 0x8, 0x40, 0x48, 0xF5, 0xC3] }, 186 | {(float) 9999.99, [0x4, 0x8, 0x46, 0x1C, 0x3F, 0xF6] }, 187 | {(float) -1.0, [0x4, 0x8, 0xBF, 0x80, 0x0, 0x0] }, 188 | {(float) -1.1, [0x4, 0x8, 0xBF, 0x8C, 0xCC, 0xCD] }, 189 | {(float) -3.14, [0x4, 0x8, 0xC0, 0x48, 0xF5, 0xC3] }, 190 | {(float) -9999.99, [0x4, 0x8, 0xC6, 0x1C, 0x3F, 0xF6] } 191 | }; 192 | 193 | yield return [floats]; 194 | } 195 | 196 | public static IEnumerable TestPointers() 197 | { 198 | var pointers = new Dictionary 199 | { 200 | {0L, [0x20, 0x0] }, 201 | {5L, [0x20, 0x5] }, 202 | {10L, [0x20, 0xa] }, 203 | {(1L << 10) - 1, [0x23, 0xff] }, 204 | {3017L, [0x28, 0x3, 0xc9] }, 205 | {(1L << 19) - 5, [0x2f, 0xf7, 0xfb] }, 206 | {(1L << 19) + (1 << 11) - 1, [0x2f, 0xff, 0xff] }, 207 | {(1L << 27) - 2, [0x37, 0xf7, 0xf7, 0xfe] }, 208 | {(1L << 27) + (1 << 19) + (1 << 11) - 1, [0x37, 0xff, 0xff, 0xff] }, 209 | {(1L << 31) - 1, [0x38, 0x7f, 0xff, 0xff, 0xff] } 210 | }; 211 | 212 | yield return [pointers]; 213 | } 214 | 215 | public static IEnumerable TestStrings() 216 | { 217 | yield return [Strings()]; 218 | } 219 | 220 | private static Dictionary Strings() 221 | { 222 | var strings = new Dictionary(); 223 | 224 | AddTestString(strings, 0x40, ""); 225 | AddTestString(strings, 0x41, "1"); 226 | AddTestString(strings, 0x43, "人"); 227 | AddTestString(strings, 0x43, "123"); 228 | AddTestString(strings, 0x5b, "123456789012345678901234567"); 229 | AddTestString(strings, 0x5c, "1234567890123456789012345678"); 230 | AddTestString(strings, [0x5d, 0x0], "12345678901234567890123456789"); 231 | AddTestString(strings, [0x5d, 0x1], "123456789012345678901234567890"); 232 | 233 | AddTestString(strings, [0x5e, 0x0, 0xd7], new string('x', 500)); 234 | AddTestString(strings, [0x5e, 0x6, 0xb3], new string('x', 2000)); 235 | AddTestString(strings, [0x5f, 0x0, 0x10, 0x53], new string('x', 70000)); 236 | return strings; 237 | } 238 | 239 | private static void AddTestString(Dictionary tests, byte ctrl, string str) 240 | { 241 | AddTestString(tests, [ctrl], str); 242 | } 243 | 244 | private static void AddTestString(Dictionary tests, byte[] ctrl, string str) 245 | { 246 | var sb = Encoding.UTF8.GetBytes(str); 247 | var bytes = new byte[ctrl.Length + sb.Length]; 248 | 249 | Array.Copy(ctrl, 0, bytes, 0, ctrl.Length); 250 | Array.Copy(sb, 0, bytes, ctrl.Length, sb.Length); 251 | tests.Add(str, bytes); 252 | } 253 | 254 | public static IEnumerable TestBooleans() 255 | { 256 | var booleans = new Dictionary 257 | { 258 | {false, [0x0, 0x7] }, 259 | {true, [0x1, 0x7] } 260 | }; 261 | 262 | yield return [booleans]; 263 | } 264 | 265 | public static IEnumerable TestBytes() 266 | { 267 | var bytes = new Dictionary(); 268 | 269 | var strings = Strings(); 270 | 271 | foreach (var s in strings.Keys) 272 | { 273 | var ba = strings[s]; 274 | ba[0] ^= 0xc0; 275 | 276 | bytes.Add(Encoding.UTF8.GetBytes(s), ba); 277 | } 278 | 279 | yield return [bytes]; 280 | } 281 | 282 | public static IEnumerable TestMaps() 283 | { 284 | var maps = new Dictionary, byte[]>(); 285 | 286 | var empty = new Dictionary(); 287 | maps.Add(new Dictionary(empty), [0xe0]); 288 | 289 | var one = new Dictionary { { "en", "Foo" } }; 290 | maps.Add(new Dictionary(one), [ 291 | 0xe1, /* en */0x42, 0x65, 0x6e, 292 | /* Foo */0x43, 0x46, 0x6f, 0x6f 293 | ]); 294 | 295 | var two = new Dictionary { { "en", "Foo" }, { "zh", "人" } }; 296 | maps.Add(new Dictionary(two), [ 297 | 0xe2, 298 | /* en */ 299 | 0x42, 0x65, 0x6e, 300 | /* Foo */ 301 | 0x43, 0x46, 0x6f, 0x6f, 302 | /* zh */ 303 | 0x42, 0x7a, 0x68, 304 | /* 人 */ 305 | 0x43, 0xe4, 0xba, 0xba 306 | ]); 307 | 308 | var nested = new Dictionary { { "name", two } }; 309 | 310 | maps.Add(new Dictionary(nested), [ 311 | 0xe1, /* name */ 312 | 0x44, 0x6e, 0x61, 0x6d, 0x65, 0xe2, /* en */ 313 | 0x42, 0x65, 0x6e, 314 | /* Foo */ 315 | 0x43, 0x46, 0x6f, 0x6f, 316 | /* zh */ 317 | 0x42, 0x7a, 0x68, 318 | /* 人 */ 319 | 0x43, 0xe4, 0xba, 0xba 320 | ]); 321 | 322 | var guess = new Dictionary(); 323 | var languages = new List { "en", "zh" }; 324 | guess.Add("languages", languages.AsReadOnly()); 325 | maps.Add(new Dictionary(guess), [ 326 | 0xe1, /* languages */ 327 | 0x49, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 328 | /* array */ 329 | 0x2, 0x4, 330 | /* en */ 331 | 0x42, 0x65, 0x6e, 332 | /* zh */ 333 | 0x42, 0x7a, 0x68 334 | ]); 335 | 336 | yield return [maps]; 337 | } 338 | 339 | public static IEnumerable TestArrays() 340 | { 341 | var arrays = new Dictionary, byte[]>(); 342 | 343 | var f1 = new List { "Foo" }; 344 | arrays.Add(f1, [ 345 | 0x1, 0x4, 346 | /* Foo */ 347 | 0x43, 0x46, 0x6f, 0x6f 348 | ]); 349 | 350 | var f2 = new List { "Foo", "人" }; 351 | arrays.Add(f2, [ 352 | 0x2, 0x4, 353 | /* Foo */ 354 | 0x43, 0x46, 0x6f, 0x6f, 355 | /* 人 */ 356 | 0x43, 0xe4, 0xba, 0xba 357 | ]); 358 | 359 | var empty = new List(); 360 | arrays.Add(empty, [0x0, 0x4]); 361 | 362 | yield return [arrays]; 363 | } 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "", Scope = "type", Target = "~T:MaxMind.Db.Test.DecoderTest")] 9 | [assembly: SuppressMessage("Major Code Smell", "S1118:Utility classes should not have public constructors", Justification = "", Scope = "type", Target = "~T:MaxMind.Db.Test.DecoderTest")] 10 | [assembly: SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.NullStreamThrowsArgumentNullException")] 11 | [assembly: SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestEmptyStream")] 12 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.Test")] 13 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestAsync~System.Threading.Tasks.Task")] 14 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestDecodingToConcurrentDictionary")] 15 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestDecodingToDictionary")] 16 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestDecodingToGenericIDictionary")] 17 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestNonSeekableStream")] 18 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestNonSeekableStreamAsync~System.Threading.Tasks.Task")] 19 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestStream")] 20 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestStreamAsync~System.Threading.Tasks.Task")] 21 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ThreadingTest.TestManyOpens(MaxMind.Db.FileAccessMode)")] 22 | [assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ThreadingTest.TestParallelFor(MaxMind.Db.FileAccessMode)")] 23 | [assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "", Scope = "type", Target = "~T:MaxMind.Db.Test.Helper.NonSeekableStreamWrapper")] 24 | 25 | /* Unmerged change from project 'MaxMind.Db.Test (net6.0)' 26 | Added: 27 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")] 28 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")] 29 | */ 30 | 31 | /* Unmerged change from project 'MaxMind.Db.Test (net7.0)' 32 | Added: 33 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")] 34 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")] 35 | */ 36 | 37 | /* Unmerged change from project 'MaxMind.Db.Test (net8.0)' 38 | Added: 39 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")] 40 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")] 41 | */ 42 | [assembly: SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.NullStreamThrowsArgumentNullExceptionAsync")] 43 | [assembly: SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestEmptyStreamAsync")] 44 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")] 45 | [assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")] 46 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/Helper/NoNetworkTypeHolder.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System.Collections.Generic; 4 | using System.Numerics; 5 | 6 | #endregion 7 | 8 | namespace MaxMind.Db.Test.Helper 9 | { 10 | public class NNInnerMapX 11 | { 12 | [Constructor] 13 | public NNInnerMapX( 14 | string utf8_stringX, 15 | LinkedList arrayX 16 | ) 17 | { 18 | ArrayX = arrayX; 19 | Utf8StringX = utf8_stringX; 20 | } 21 | 22 | public LinkedList ArrayX { get; set; } 23 | 24 | public string Utf8StringX { get; set; } 25 | } 26 | 27 | public class NNInnerMap 28 | { 29 | [Constructor] 30 | public NNInnerMap(NNInnerMapX mapX) 31 | { 32 | MapX = mapX; 33 | } 34 | 35 | public NNInnerMapX MapX { get; set; } 36 | } 37 | 38 | public class NNInnerNonexistant 39 | { 40 | [Constructor] 41 | public NNInnerNonexistant( 42 | // To test this is set even if the parent and grandparent 43 | // don't exist in the database. 44 | [Inject("injected")] string injected 45 | ) 46 | { 47 | Injected = injected; 48 | } 49 | 50 | public string Injected { get; set; } 51 | } 52 | 53 | public class NNNonexistant 54 | { 55 | [Constructor] 56 | public NNNonexistant( 57 | [Parameter("innerNonexistant", true)] NNInnerNonexistant innerNonexistant, 58 | // To test this is set even if the parent map doesn't exist 59 | // in the database. 60 | [Inject("injected")] string injected 61 | ) 62 | { 63 | Injected = injected; 64 | InnerNonexistant = innerNonexistant; 65 | } 66 | 67 | public string Injected { get; set; } 68 | public NNInnerNonexistant InnerNonexistant { get; set; } 69 | } 70 | 71 | public class NoNetworkTypeHolder 72 | { 73 | [Constructor] 74 | public NoNetworkTypeHolder( 75 | string utf8_string, 76 | byte[] bytes, 77 | int uint16, 78 | long uint32, 79 | ulong uint64, 80 | BigInteger uint128, 81 | int int32, 82 | bool boolean, 83 | ICollection array, 84 | [Parameter("double")] double mmDouble, 85 | [Parameter("float")] float mmFloat, 86 | [Parameter("map")] NNInnerMap map, 87 | [Parameter("nonexistant", true)] NNNonexistant nonexistant 88 | ) 89 | { 90 | Array = array; 91 | Boolean = boolean; 92 | Bytes = bytes; 93 | Double = mmDouble; 94 | Float = mmFloat; 95 | 96 | Int32 = int32; 97 | Map = map; 98 | 99 | Uint16 = uint16; 100 | Uint32 = uint32; 101 | Uint64 = uint64; 102 | Uint128 = uint128; 103 | 104 | Utf8String = utf8_string; 105 | 106 | Nonexistant = nonexistant; 107 | } 108 | 109 | public NNNonexistant Nonexistant { get; set; } 110 | 111 | public ICollection Array { get; set; } 112 | public bool Boolean { get; set; } 113 | 114 | public byte[] Bytes { get; set; } 115 | 116 | public double Double { get; set; } 117 | public float Float { get; set; } 118 | 119 | public NNInnerMap Map { get; set; } 120 | 121 | public long Int32 { get; set; } 122 | public int Uint16 { get; set; } 123 | public long Uint32 { get; set; } 124 | public ulong Uint64 { get; set; } 125 | public BigInteger Uint128 { get; set; } 126 | 127 | public string Utf8String { get; set; } 128 | 129 | public override string ToString() 130 | { 131 | return 132 | $"Boolean: {Boolean}, Bytes: {Bytes}, Int32: {Int32}, Uint16: {Uint16}, Uint32: {Uint32}, Uint64: {Uint64}, Uint128: {Uint128}, Utf8String: {Utf8String}"; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /MaxMind.Db.Test/Helper/NonSeekableStreamWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace MaxMind.Db.Test.Helper 5 | { 6 | internal class NonSeekableStreamWrapper 7 | : Stream 8 | { 9 | private readonly Stream _wrappedStream; 10 | 11 | public NonSeekableStreamWrapper(Stream wrappedStream) 12 | { 13 | _wrappedStream = wrappedStream; 14 | } 15 | 16 | public override void Flush() 17 | { 18 | _wrappedStream.Flush(); 19 | } 20 | 21 | public override long Seek(long offset, SeekOrigin origin) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | public override void SetLength(long value) 27 | { 28 | throw new NotImplementedException(); 29 | } 30 | 31 | public override int Read(byte[] buffer, int offset, int count) 32 | { 33 | return _wrappedStream.Read(buffer, offset, count); 34 | } 35 | 36 | public override void Write(byte[] buffer, int offset, int count) 37 | { 38 | _wrappedStream.Write(buffer, offset, count); 39 | } 40 | 41 | protected override void Dispose(bool disposing) 42 | { 43 | _wrappedStream.Dispose(); 44 | } 45 | 46 | public override bool CanRead => _wrappedStream.CanRead; 47 | public override bool CanSeek => false; 48 | public override bool CanWrite => _wrappedStream.CanWrite; 49 | public override long Length => throw new NotSupportedException(); 50 | 51 | public override long Position 52 | { 53 | get => throw new NotSupportedException(); 54 | set => throw new NotSupportedException(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /MaxMind.Db.Test/Helper/TestUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace MaxMind.Db.Test.Helper 6 | { 7 | internal static class TestUtils 8 | { 9 | public static string TestDirectory { get; } = GetTestDirectory(); 10 | 11 | private static string GetTestDirectory() 12 | { 13 | // check if environment variable MAXMIND_TEST_BASE_DIR is set 14 | var dbPath = Environment.GetEnvironmentVariable("MAXMIND_TEST_BASE_DIR"); 15 | 16 | if (!string.IsNullOrEmpty(dbPath)) 17 | { 18 | if (!Directory.Exists(dbPath)) 19 | { 20 | throw new("Path set as environment variable MAXMIND_TEST_BASE_DIR does not exist!"); 21 | } 22 | 23 | return dbPath; 24 | } 25 | 26 | // In Microsoft.NET.Test.Sdk v15.0.0, the current working directory 27 | // is not set to project's root but instead the output directory. 28 | // see: https://github.com/Microsoft/vstest/issues/435. 29 | // 30 | // Let's change the strategry of finding the parent directory of 31 | // TestData directory by walking from cwd backwards upto the 32 | // volume's root. 33 | var currentDirectory = Directory.GetCurrentDirectory(); 34 | var currentDirectoryInfo = new DirectoryInfo(currentDirectory); 35 | 36 | do 37 | { 38 | if (currentDirectoryInfo.EnumerateDirectories("TestData*", SearchOption.AllDirectories).Any()) 39 | { 40 | return currentDirectoryInfo.FullName; 41 | } 42 | currentDirectoryInfo = currentDirectoryInfo.Parent; 43 | } 44 | while (currentDirectoryInfo?.Parent != null); 45 | 46 | return currentDirectory; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/Helper/TypeHolder.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System.Collections.Generic; 4 | using System.Numerics; 5 | 6 | #endregion 7 | 8 | namespace MaxMind.Db.Test.Helper 9 | { 10 | public class InnerMapX 11 | { 12 | [Constructor] 13 | public InnerMapX( 14 | string utf8_stringX, 15 | [Network] Network network, 16 | LinkedList arrayX 17 | ) 18 | { 19 | ArrayX = arrayX; 20 | Network = network; 21 | Utf8StringX = utf8_stringX; 22 | } 23 | 24 | public LinkedList ArrayX { get; set; } 25 | 26 | public Network Network { get; set; } 27 | 28 | public string Utf8StringX { get; set; } 29 | } 30 | 31 | public class InnerMap 32 | { 33 | [Constructor] 34 | public InnerMap(InnerMapX mapX) 35 | { 36 | MapX = mapX; 37 | } 38 | 39 | public InnerMapX MapX { get; set; } 40 | } 41 | 42 | public class InnerNonexistant 43 | { 44 | [Constructor] 45 | public InnerNonexistant( 46 | // To test these are set even if the parent and grandparent 47 | // don't exist in the database. 48 | [Inject("injected")] string injected, 49 | [Network] Network network 50 | ) 51 | { 52 | Injected = injected; 53 | Network = network; 54 | } 55 | 56 | public string Injected { get; set; } 57 | public Network Network { get; } 58 | } 59 | 60 | public class Nonexistant 61 | { 62 | [Constructor] 63 | public Nonexistant( 64 | [Parameter("innerNonexistant", true)] InnerNonexistant innerNonexistant, 65 | // The next two test that they are set even if the parent map doesn't exist 66 | // in the database. 67 | [Inject("injected")] string injected, 68 | [Network] Network network, 69 | // Test that repeated network parameters work, or at least don't blow 70 | // up. Not sure why you would want to do this. 71 | [Network] Network network2 72 | ) 73 | { 74 | Injected = injected; 75 | InnerNonexistant = innerNonexistant; 76 | Network = network; 77 | Network2 = network2; 78 | } 79 | 80 | public string Injected { get; set; } 81 | public InnerNonexistant InnerNonexistant { get; set; } 82 | public Network Network { get; } 83 | public Network Network2 { get; } 84 | } 85 | 86 | public class TypeHolder 87 | { 88 | [Constructor] 89 | public TypeHolder( 90 | string utf8_string, 91 | byte[] bytes, 92 | int uint16, 93 | long uint32, 94 | ulong uint64, 95 | BigInteger uint128, 96 | int int32, 97 | bool boolean, 98 | ICollection array, 99 | [Parameter("double")] double mmDouble, 100 | [Parameter("float")] float mmFloat, 101 | [Parameter("map")] InnerMap map, 102 | [Parameter("nonexistant", true)] Nonexistant nonexistant 103 | ) 104 | { 105 | Array = array; 106 | Boolean = boolean; 107 | Bytes = bytes; 108 | Double = mmDouble; 109 | Float = mmFloat; 110 | 111 | Int32 = int32; 112 | Map = map; 113 | 114 | Uint16 = uint16; 115 | Uint32 = uint32; 116 | Uint64 = uint64; 117 | Uint128 = uint128; 118 | 119 | Utf8String = utf8_string; 120 | 121 | Nonexistant = nonexistant; 122 | } 123 | 124 | public Nonexistant Nonexistant { get; set; } 125 | 126 | public ICollection Array { get; set; } 127 | public bool Boolean { get; set; } 128 | 129 | public byte[] Bytes { get; set; } 130 | 131 | public double Double { get; set; } 132 | public float Float { get; set; } 133 | 134 | public InnerMap Map { get; set; } 135 | 136 | public long Int32 { get; set; } 137 | public int Uint16 { get; set; } 138 | public long Uint32 { get; set; } 139 | public ulong Uint64 { get; set; } 140 | public BigInteger Uint128 { get; set; } 141 | 142 | public string Utf8String { get; set; } 143 | 144 | public override string ToString() 145 | { 146 | return 147 | $"Boolean: {Boolean}, Bytes: {Bytes}, Int32: {Int32}, Uint16: {Uint16}, Uint32: {Uint32}, Uint64: {Uint64}, Uint128: {Uint128}, Utf8String: {Utf8String}"; 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /MaxMind.Db.Test/MaxMind.Db.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test project to validate .NET reader for the MaxMind DB file format 5 | 4.0.0 6 | net9.0;net8.0;net481 7 | net9.0;net8.0 8 | MaxMind.Db.Test 9 | ../MaxMind.snk 10 | true 11 | true 12 | MaxMind.Db.Test 13 | Apache-2.0 14 | AnyCPU 15 | true 16 | false 17 | false 18 | false 19 | false 20 | false 21 | false 22 | false 23 | false 24 | 13.0 25 | enable 26 | latest 27 | true 28 | true 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | runtime; build; native; contentfiles; analyzers 41 | all 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/NetworkTest.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Xunit; 3 | 4 | namespace MaxMind.Db.Test 5 | { 6 | public class NetworkTest 7 | { 8 | [Fact] 9 | public void TestIPv6() 10 | { 11 | var network = new Network( 12 | IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 13 | 28 14 | ); 15 | 16 | Assert.Equal("2001:db0::", network.NetworkAddress.ToString()); 17 | Assert.Equal(28, network.PrefixLength); 18 | Assert.Equal("2001:db0::/28", network.ToString()); 19 | } 20 | 21 | [Fact] 22 | public void TestIPv4() 23 | { 24 | var network = new Network( 25 | IPAddress.Parse("192.168.213.111"), 26 | 31 27 | ); 28 | 29 | Assert.Equal("192.168.213.110", network.NetworkAddress.ToString()); 30 | Assert.Equal(31, network.PrefixLength); 31 | Assert.Equal("192.168.213.110/31", network.ToString()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/PointerTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using MaxMind.Db.Test.Helper; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Xunit; 7 | 8 | #endregion 9 | 10 | namespace MaxMind.Db.Test 11 | { 12 | public class PointerTest 13 | { 14 | [Fact] 15 | public void TestWithPointers() 16 | { 17 | var path = Path.Combine(TestUtils.TestDirectory, "TestData", "MaxMind-DB", "test-data", "maps-with-pointers.raw"); 18 | 19 | using var database = new ArrayBuffer(path); 20 | var decoder = new Decoder(database, 0); 21 | 22 | var node = decoder.Decode>(0, out _); 23 | Assert.Equal("long_value1", node["long_key"]); 24 | 25 | node = decoder.Decode>(22, out _); 26 | Assert.Equal("long_value2", node["long_key"]); 27 | 28 | node = decoder.Decode>(37, out _); 29 | Assert.Equal("long_value1", node["long_key2"]); 30 | 31 | node = decoder.Decode>(50, out _); 32 | Assert.Equal("long_value2", node["long_key2"]); 33 | 34 | node = decoder.Decode>(55, out _); 35 | Assert.Equal("long_value1", node["long_key"]); 36 | 37 | node = decoder.Decode>(57, out _); 38 | Assert.Equal("long_value2", node["long_key2"]); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | 6 | #endregion 7 | 8 | // General Information about an assembly is controlled through the following 9 | // set of attributes. Change these attribute values to modify the information 10 | // associated with an assembly. 11 | 12 | [assembly: AssemblyTitle("MaxMind.MaxMindDb.Test")] 13 | [assembly: AssemblyDescription("")] 14 | [assembly: AssemblyConfiguration("")] 15 | [assembly: AssemblyCompany("")] 16 | [assembly: AssemblyProduct("MaxMind.MaxMindDb.Test")] 17 | [assembly: AssemblyCopyright("Copyright © 2015")] 18 | [assembly: AssemblyTrademark("")] 19 | [assembly: AssemblyCulture("")] 20 | 21 | // Setting ComVisible to false makes the types in this assembly not visible 22 | // to COM components. If you need to access a type in this assembly from 23 | // COM, set the ComVisible attribute to true on that type. 24 | 25 | [assembly: ComVisible(false)] 26 | 27 | // The following GUID is for the ID of the typelib if this project is exposed to COM 28 | 29 | [assembly: Guid("cac31299-2158-48e9-919f-993468e3f87e")] 30 | 31 | // Version information for an assembly consists of the following four values: 32 | // 33 | // Major Version 34 | // Minor Version 35 | // Build Number 36 | // Revision 37 | // 38 | // You can specify all the values or you can default the Build and Revision Numbers 39 | // by using the '*' as shown below: 40 | // [assembly: AssemblyVersion("1.0.*")] 41 | 42 | [assembly: AssemblyVersion("1.0.1.0")] 43 | [assembly: AssemblyFileVersion("1.0.1.0")] 44 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/ReaderTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using MaxMind.Db.Test.Helper; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Net; 10 | using System.Numerics; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | 14 | #endregion 15 | 16 | namespace MaxMind.Db.Test 17 | { 18 | public class ReaderTest 19 | { 20 | private readonly string _testDataRoot = 21 | Path.Combine(TestUtils.TestDirectory, "TestData", "MaxMind-DB", "test-data"); 22 | 23 | [Fact] 24 | public void Test() 25 | { 26 | foreach (var recordSize in new long[] { 24, 28, 32 }) 27 | { 28 | foreach (var ipVersion in new[] { 4, 6 }) 29 | { 30 | var file = Path.Combine(_testDataRoot, 31 | "MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); 32 | var reader = new Reader(file); 33 | using (reader) 34 | { 35 | TestMetadata(reader, ipVersion); 36 | 37 | if (ipVersion == 4) 38 | { 39 | TestIPV4(reader, file); 40 | } 41 | else 42 | { 43 | TestIPV6(reader, file); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | [Fact] 51 | public async Task TestAsync() 52 | { 53 | foreach (var recordSize in new long[] { 24, 28, 32 }) 54 | { 55 | foreach (var ipVersion in new[] { 4, 6 }) 56 | { 57 | var file = Path.Combine(_testDataRoot, 58 | "MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); 59 | var reader = await Reader.CreateAsync(file); 60 | using (reader) 61 | { 62 | TestMetadata(reader, ipVersion); 63 | 64 | if (ipVersion == 4) 65 | { 66 | TestIPV4(reader, file); 67 | } 68 | else 69 | { 70 | TestIPV6(reader, file); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | [Fact] 78 | public void TestStream() 79 | { 80 | foreach (var recordSize in new long[] { 24, 28, 32 }) 81 | { 82 | foreach (var ipVersion in new[] { 4, 6 }) 83 | { 84 | var file = Path.Combine(_testDataRoot, 85 | "MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); 86 | using var streamReader = File.OpenText(file); 87 | using var reader = new Reader(streamReader.BaseStream); 88 | TestMetadata(reader, ipVersion); 89 | 90 | if (ipVersion == 4) 91 | { 92 | TestIPV4(reader, file); 93 | } 94 | else 95 | { 96 | TestIPV6(reader, file); 97 | } 98 | } 99 | } 100 | } 101 | 102 | [Fact] 103 | public async Task TestStreamAsync() 104 | { 105 | foreach (var recordSize in new long[] { 24, 28, 32 }) 106 | { 107 | foreach (var ipVersion in new[] { 4, 6 }) 108 | { 109 | var file = Path.Combine(_testDataRoot, 110 | "MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); 111 | using var streamReader = File.OpenText(file); 112 | using var reader = await Reader.CreateAsync(streamReader.BaseStream); 113 | TestMetadata(reader, ipVersion); 114 | 115 | if (ipVersion == 4) 116 | { 117 | TestIPV4(reader, file); 118 | } 119 | else 120 | { 121 | TestIPV6(reader, file); 122 | } 123 | } 124 | } 125 | } 126 | 127 | [Fact] 128 | public void TestNonSeekableStream() 129 | { 130 | foreach (var recordSize in new long[] { 24, 28, 32 }) 131 | { 132 | foreach (var ipVersion in new[] { 4, 6 }) 133 | { 134 | var file = Path.Combine(_testDataRoot, 135 | "MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); 136 | 137 | using var stream = new NonSeekableStreamWrapper(File.OpenRead(file)); 138 | using var reader = new Reader(stream); 139 | TestMetadata(reader, ipVersion); 140 | 141 | if (ipVersion == 4) 142 | { 143 | TestIPV4(reader, file); 144 | } 145 | else 146 | { 147 | TestIPV6(reader, file); 148 | } 149 | } 150 | } 151 | } 152 | 153 | [Fact] 154 | public async Task TestNonSeekableStreamAsync() 155 | { 156 | foreach (var recordSize in new long[] { 24, 28, 32 }) 157 | { 158 | foreach (var ipVersion in new[] { 4, 6 }) 159 | { 160 | var file = Path.Combine(_testDataRoot, 161 | "MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); 162 | 163 | using var stream = new NonSeekableStreamWrapper(File.OpenRead(file)); 164 | using var reader = await Reader.CreateAsync(stream); 165 | TestMetadata(reader, ipVersion); 166 | 167 | if (ipVersion == 4) 168 | { 169 | TestIPV4(reader, file); 170 | } 171 | else 172 | { 173 | TestIPV6(reader, file); 174 | } 175 | } 176 | } 177 | } 178 | 179 | #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. 180 | [Fact] 181 | public void NullStreamThrowsArgumentNullException() 182 | { 183 | #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. 184 | var ex = Assert.Throws( 185 | () => new Reader((Stream)null)); 186 | #pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. 187 | Assert.Contains("The database stream must not be null", ex.Message); 188 | } 189 | 190 | [Fact] 191 | public async Task NullStreamThrowsArgumentNullExceptionAsync() 192 | { 193 | #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. 194 | var ex = await Assert.ThrowsAsync( 195 | async () => await Reader.CreateAsync((Stream)null)); 196 | #pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. 197 | Assert.Contains("The database stream must not be null", ex.Message); 198 | } 199 | #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. 200 | 201 | [Fact] 202 | public void TestEmptyStream() 203 | { 204 | using var stream = new MemoryStream(); 205 | var ex = Assert.Throws( 206 | () => new Reader(stream)); 207 | Assert.Contains("zero bytes left in the stream", ex.Message); 208 | } 209 | 210 | [Fact] 211 | public async Task TestEmptyStreamAsync() 212 | { 213 | using var stream = new MemoryStream(); 214 | var ex = await Assert.ThrowsAsync( 215 | async () => await Reader.CreateAsync(stream)); 216 | Assert.Contains("zero bytes left in the stream", ex.Message); 217 | } 218 | 219 | [Fact] 220 | public void MetadataPointer() 221 | { 222 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-metadata-pointers.mmdb")); 223 | Assert.Equal("Lots of pointers in metadata", reader.Metadata.DatabaseType); 224 | } 225 | 226 | [Fact] 227 | public void NoIPV4SearchTree() 228 | { 229 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-no-ipv4-search-tree.mmdb")); 230 | Assert.Equal("::0/64", reader.Find(IPAddress.Parse("1.1.1.1"))); 231 | Assert.Equal("::0/64", reader.Find(IPAddress.Parse("192.1.1.1"))); 232 | } 233 | 234 | [Theory] 235 | [InlineData("1.1.1.1", "MaxMind-DB-test-ipv6-32.mmdb", 8, false)] 236 | [InlineData("::1:ffff:ffff", "MaxMind-DB-test-ipv6-24.mmdb", 128, true)] 237 | [InlineData("::2:0:1", "MaxMind-DB-test-ipv6-24.mmdb", 122, true)] 238 | [InlineData("1.1.1.1", "MaxMind-DB-test-ipv4-24.mmdb", 32, true)] 239 | [InlineData("1.1.1.3", "MaxMind-DB-test-ipv4-24.mmdb", 31, true)] 240 | [InlineData("1.1.1.3", "MaxMind-DB-test-decoder.mmdb", 24, true)] 241 | [InlineData("::ffff:1.1.1.128", "MaxMind-DB-test-decoder.mmdb", 120, true)] 242 | [InlineData("::1.1.1.128", "MaxMind-DB-test-decoder.mmdb", 120, true)] 243 | [InlineData("200.0.2.1", "MaxMind-DB-no-ipv4-search-tree.mmdb", 0, true)] 244 | [InlineData("::200.0.2.1", "MaxMind-DB-no-ipv4-search-tree.mmdb", 64, true)] 245 | [InlineData("0:0:0:0:ffff:ffff:ffff:ffff", "MaxMind-DB-no-ipv4-search-tree.mmdb", 64, true)] 246 | [InlineData("ef00::", "MaxMind-DB-no-ipv4-search-tree.mmdb", 1, false)] 247 | public void TestFindPrefixLength(string ipStr, string dbFile, int expectedPrefixLength, bool expectedOK) 248 | { 249 | using var reader = new Reader(Path.Combine(_testDataRoot, dbFile)); 250 | var ip = IPAddress.Parse(ipStr); 251 | var record = reader.Find(ip, out var prefixLength); 252 | 253 | Assert.Equal(expectedPrefixLength, prefixLength); 254 | 255 | if (expectedOK) 256 | { 257 | Assert.NotNull(record); 258 | } 259 | else 260 | { 261 | Assert.Null(record); 262 | } 263 | } 264 | 265 | [Fact] 266 | public void TestDecodingToDictionary() 267 | { 268 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-decoder.mmdb")); 269 | var record = reader.Find>(IPAddress.Parse("::1.1.1.0")); 270 | TestDecodingTypes(record); 271 | } 272 | 273 | [Fact] 274 | public void TestDecodingToGenericIDictionary() 275 | { 276 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-decoder.mmdb")); 277 | var record = reader.Find>(IPAddress.Parse("::1.1.1.0")); 278 | TestDecodingTypes(record); 279 | } 280 | 281 | [Fact] 282 | public void TestDecodingToConcurrentDictionary() 283 | { 284 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-decoder.mmdb")); 285 | var record = reader.Find>(IPAddress.Parse("::1.1.1.0")); 286 | TestDecodingTypes(record); 287 | } 288 | 289 | private static void TestNode( 290 | Reader reader, 291 | Reader.ReaderIteratorNode node, 292 | InjectableValues? injectables = null 293 | ) where T : class 294 | { 295 | var lengthBits = node.Start.GetAddressBytes().Length * 8; 296 | Assert.True(lengthBits >= node.PrefixLength); 297 | 298 | // ensure a lookup back into the db produces correct results 299 | var find = reader.Find(node.Start, injectables); 300 | Assert.NotNull(find); 301 | var find2 = reader.Find(node.Start, injectables); 302 | Assert.NotNull(find2); 303 | Assert.Equivalent(find, find2); 304 | Assert.Equivalent(node.Data, find); 305 | } 306 | 307 | [Fact] 308 | public void TestEnumerateCountryDatabase() 309 | { 310 | var count = 0; 311 | using (var reader = new Reader(Path.Combine(_testDataRoot, "GeoIP2-Country-Test.mmdb"))) 312 | { 313 | foreach (var node in reader.FindAll>()) 314 | { 315 | TestNode(reader, node); 316 | count++; 317 | } 318 | } 319 | 320 | Assert.True(count >= 397); 321 | } 322 | 323 | [Fact] 324 | public void TestEnumerateDecoderDatabase() 325 | { 326 | var count = 0; 327 | var injectables = new InjectableValues(); 328 | injectables.AddValue("injectable", "injectable_value"); 329 | injectables.AddValue("injected", "injected_value"); 330 | using (var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-decoder.mmdb"))) 331 | { 332 | foreach (var node in reader.FindAll(injectables)) 333 | { 334 | TestNode(reader, node, injectables); 335 | count++; 336 | } 337 | } 338 | Assert.Equal(26, count); 339 | } 340 | 341 | private static void TestDecodingTypes(IDictionary? record) 342 | { 343 | if (record == null) 344 | { 345 | throw new Xunit.Sdk.XunitException("unexpected null record value"); 346 | } 347 | Assert.True((bool)record["boolean"]); 348 | 349 | Assert.Equal(new byte[] { 0, 0, 0, 42 }, (byte[])record["bytes"]); 350 | 351 | Assert.Equal("unicode! ☯ - ♫", record["utf8_string"]); 352 | 353 | var array = (List)record["array"]; 354 | Assert.Equal(3, array.Count); 355 | Assert.Equal(1L, array[0]); 356 | Assert.Equal(2L, array[1]); 357 | Assert.Equal(3L, array[2]); 358 | 359 | var map = (Dictionary)record["map"]; 360 | Assert.Single(map); 361 | 362 | var mapX = (Dictionary)map["mapX"]; 363 | Assert.Equal(2, mapX.Count); 364 | Assert.Equal("hello", mapX["utf8_stringX"]); 365 | 366 | var arrayX = (List)mapX["arrayX"]; 367 | Assert.Equal(3, arrayX.Count); 368 | Assert.Equal(7L, arrayX[0]); 369 | Assert.Equal(8L, arrayX[1]); 370 | Assert.Equal(9L, arrayX[2]); 371 | 372 | Assert.Equal(42.123456, (double)record["double"], 9); 373 | Assert.Equal(1.1F, (float)record["float"], 5); 374 | Assert.Equal(-268435456, record["int32"]); 375 | Assert.Equal(100, record["uint16"]); 376 | Assert.Equal(268435456L, record["uint32"]); 377 | Assert.Equal(1152921504606846976UL, record["uint64"]); 378 | Assert.Equal( 379 | BigInteger.Parse("1329227995784915872903807060280344576"), 380 | record["uint128"]); 381 | } 382 | 383 | [Fact] 384 | public void TestDecodingTypesToObject() 385 | { 386 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-decoder.mmdb")); 387 | var injectables = new InjectableValues(); 388 | injectables.AddValue("injected", "injected string"); 389 | var record = reader.Find(IPAddress.Parse("1.1.1.1"), injectables); 390 | if (record == null) 391 | { 392 | throw new Xunit.Sdk.XunitException("unexpected null record value"); 393 | } 394 | Assert.True(record.Boolean); 395 | Assert.Equal(new byte[] { 0, 0, 0, 42 }, record.Bytes); 396 | Assert.Equal("unicode! ☯ - ♫", record.Utf8String); 397 | 398 | Assert.Equal(new List { 1, 2, 3 }, record.Array); 399 | 400 | var mapX = record.Map.MapX; 401 | Assert.Equal("hello", mapX.Utf8StringX); 402 | Assert.Equal(new List { 7, 8, 9 }, mapX.ArrayX); 403 | Assert.Equal("1.1.1.0/24", mapX.Network.ToString()); 404 | 405 | Assert.Equal(42.123456, record.Double, 9); 406 | Assert.Equal(1.1F, record.Float, 5); 407 | Assert.Equal(-268435456, record.Int32); 408 | Assert.Equal(100, record.Uint16); 409 | Assert.Equal(268435456, record.Uint32); 410 | Assert.Equal(1152921504606846976UL, record.Uint64); 411 | Assert.Equal(BigInteger.Parse("1329227995784915872903807060280344576"), record.Uint128); 412 | 413 | Assert.Equal("injected string", record.Nonexistant.Injected); 414 | Assert.Equal("1.1.1.0/24", record.Nonexistant.Network.ToString()); 415 | Assert.Equal("1.1.1.0/24", record.Nonexistant.Network2.ToString()); 416 | 417 | Assert.Equal("injected string", record.Nonexistant.InnerNonexistant.Injected); 418 | Assert.Equal("1.1.1.0/24", record.Nonexistant.InnerNonexistant.Network.ToString()); 419 | } 420 | 421 | [Fact] 422 | public void TestZeros() 423 | { 424 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-decoder.mmdb")); 425 | var record = reader.Find>(IPAddress.Parse("::")); 426 | if (record == null) 427 | { 428 | throw new Xunit.Sdk.XunitException("unexpected null record value"); 429 | } 430 | Assert.False((bool)record["boolean"]); 431 | 432 | Assert.Empty((byte[])record["bytes"]); 433 | 434 | Assert.Empty(record["utf8_string"] as string ?? "null"); 435 | 436 | Assert.IsType>(record["array"]); 437 | Assert.Empty((List)record["array"]); 438 | 439 | Assert.IsType>(record["map"]); 440 | Assert.Empty((Dictionary)record["map"]); 441 | 442 | Assert.Equal(0, (double)record["double"], 9); 443 | Assert.Equal(0, (float)record["float"], 5); 444 | Assert.Equal(0, record["int32"]); 445 | Assert.Equal(0, record["uint16"]); 446 | Assert.Equal(0L, record["uint32"]); 447 | Assert.Equal(0UL, record["uint64"]); 448 | Assert.Equal(new BigInteger(0), record["uint128"]); 449 | } 450 | 451 | [Fact] 452 | public void TestBrokenDatabase() 453 | { 454 | using var reader = new Reader(Path.Combine(_testDataRoot, "GeoIP2-City-Test-Broken-Double-Format.mmdb")); 455 | var ex = Assert.Throws( 456 | () => reader.Find(IPAddress.Parse("2001:220::"))); 457 | Assert.Contains("contains bad data", ex.Message); 458 | } 459 | 460 | [Fact] 461 | public void TestBrokenSearchTreePointer() 462 | { 463 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-broken-pointers-24.mmdb")); 464 | var ex = Assert.Throws( 465 | () => reader.Find(IPAddress.Parse("1.1.1.32"))); 466 | Assert.Contains("search tree is corrupt", ex.Message); 467 | } 468 | 469 | [Fact] 470 | public void TestBrokenDataPointer() 471 | { 472 | using var reader = new Reader(Path.Combine(_testDataRoot, "MaxMind-DB-test-broken-pointers-24.mmdb")); 473 | var ex = Assert.Throws( 474 | () => reader.Find(IPAddress.Parse("1.1.1.16"))); 475 | Assert.Contains("data section contains bad data", ex.Message); 476 | } 477 | 478 | private static void TestIPV6(Reader reader, string file) 479 | { 480 | TestAddresses(reader, 481 | file, 482 | ["::1:ffff:ffff", "::2:0:0", "::2:0:40", "::2:0:50", "::2:0:58"], 483 | new Dictionary 484 | { 485 | {"::2:0:1", "::2:0:0"}, 486 | {"::2:0:33", "::2:0:0"}, 487 | {"::2:0:39", "::2:0:0"}, 488 | {"::2:0:41", "::2:0:40"}, 489 | {"::2:0:49", "::2:0:40"}, 490 | {"::2:0:52", "::2:0:50"}, 491 | {"::2:0:57", "::2:0:50"}, 492 | {"::2:0:59", "::2:0:58"} 493 | }, 494 | ["1.1.1.33", "255.254.253.123", "89fa::"], 495 | new Dictionary 496 | { 497 | {"::2:0:1", 122} 498 | }); 499 | } 500 | 501 | private static void TestIPV4(Reader reader, string file) 502 | { 503 | TestAddresses(reader, 504 | file, 505 | Enumerable.Range(0, 5).Select(i => "1.1.1." + (int)Math.Pow(2, i)), 506 | new Dictionary 507 | { 508 | {"1.1.1.3", "1.1.1.2"}, 509 | {"1.1.1.5", "1.1.1.4"}, 510 | {"1.1.1.7", "1.1.1.4"}, 511 | {"1.1.1.9", "1.1.1.8"}, 512 | {"1.1.1.15", "1.1.1.8"}, 513 | {"1.1.1.17", "1.1.1.16"}, 514 | {"1.1.1.31", "1.1.1.16"} 515 | }, 516 | ["1.1.1.33", "255.254.253.123"], 517 | new Dictionary 518 | { 519 | {"1.1.1.3", 31}, 520 | {"4.0.0.1", 6} 521 | }); 522 | } 523 | 524 | private static void TestAddresses(Reader reader, string file, IEnumerable singleAddresses, 525 | Dictionary pairs, IEnumerable nullAddresses, Dictionary prefixes) 526 | { 527 | #pragma warning disable CS8602 // Dereference of a possibly null reference. 528 | 529 | foreach (var address in singleAddresses) 530 | { 531 | Assert.Equal( 532 | new string([.. address]), 533 | reader.Find>(IPAddress.Parse(address))["ip"]); 534 | } 535 | 536 | foreach (var address in pairs.Keys) 537 | { 538 | Assert.Equal( 539 | pairs[address], 540 | reader.Find>(IPAddress.Parse(address))["ip"]); 541 | } 542 | #pragma warning restore CS8602 // Dereference of a possibly null reference. 543 | 544 | foreach (var address in nullAddresses) 545 | { 546 | Assert.Null( 547 | reader.Find(IPAddress.Parse(address))); 548 | } 549 | 550 | foreach (var address in prefixes.Keys) 551 | { 552 | reader.Find>(IPAddress.Parse(address), out var routingPrefix); 553 | Assert.Equal( 554 | prefixes[address], 555 | routingPrefix); 556 | } 557 | 558 | foreach (var node in reader.FindAll>()) 559 | { 560 | TestNode(reader, node); 561 | } 562 | } 563 | 564 | private static void TestMetadata(Reader reader, int ipVersion) 565 | { 566 | var metadata = reader.Metadata; 567 | 568 | Assert.Equal(2, metadata.BinaryFormatMajorVersion); 569 | Assert.Equal(0, metadata.BinaryFormatMinorVersion); 570 | Assert.Equal(ipVersion, metadata.IPVersion); 571 | Assert.Equal("Test", metadata.DatabaseType); 572 | Assert.Contains("en", metadata.Languages); 573 | Assert.Contains("zh", metadata.Languages); 574 | Assert.Equal("Test Database", metadata.Description["en"]); 575 | Assert.Equal("Test Database Chinese", metadata.Description["zh"]); 576 | Assert.DoesNotContain("gibberish", metadata.Description.Keys); 577 | } 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/ThreadingTest.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using MaxMind.Db.Test.Helper; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | #endregion 13 | 14 | namespace MaxMind.Db.Test 15 | { 16 | public class ThreadingTest 17 | { 18 | private readonly string _testDatabase = 19 | Path.Combine(TestUtils.TestDirectory, "TestData", "MaxMind-DB", "test-data", "GeoIP2-City-Test.mmdb"); 20 | 21 | [Theory] 22 | [InlineData(FileAccessMode.MemoryMapped)] 23 | [InlineData(FileAccessMode.MemoryMappedGlobal)] 24 | [InlineData(FileAccessMode.Memory)] 25 | public void TestParallelFor(FileAccessMode mode) 26 | { 27 | var ipsAndResults = new Dictionary(); 28 | var rand = new Random(); 29 | using var reader = new Reader(_testDatabase, mode); 30 | for (var i = 0; i < 10000; i++) 31 | { 32 | var ip = new IPAddress(rand.Next(int.MaxValue)); 33 | var resp = reader.Find(ip); 34 | if (resp == null || ipsAndResults.ContainsKey(ip)) continue; 35 | ipsAndResults.Add(ip, resp.ToString()); 36 | } 37 | 38 | var ips = ipsAndResults.Keys.ToArray(); 39 | Parallel.For(0, ips.Length, i => 40 | { 41 | var ipAddress = ips[i]; 42 | var result = reader.Find(ipAddress); 43 | if (result == null) 44 | { 45 | throw new Xunit.Sdk.XunitException("unexpected null result value"); 46 | } 47 | var resultString = result.ToString(); 48 | var expectedString = ipsAndResults[ipAddress]; 49 | if (resultString != expectedString) 50 | throw new($"Non-matching result. Expected {expectedString}, found {resultString}"); 51 | }); 52 | } 53 | 54 | [Theory] 55 | [InlineData(FileAccessMode.MemoryMapped)] 56 | [InlineData(FileAccessMode.MemoryMappedGlobal)] 57 | [InlineData(FileAccessMode.Memory)] 58 | [Trait("Category", "BreaksMono")] 59 | public void TestManyOpens(FileAccessMode mode) 60 | { 61 | Parallel.For(0, 50, _ => 62 | { 63 | using var reader = new Reader(_testDatabase, mode); 64 | reader.Find(IPAddress.Parse("175.16.199.0")); 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MaxMind.Db.Test/runsettings.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | x64 5 | 6 | 7 | -------------------------------------------------------------------------------- /MaxMind.Db.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaxMind.Db", "MaxMind.Db\MaxMind.Db.csproj", "{923441A3-A9A9-425F-9ABD-73DF13E0A053}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaxMind.Db.Benchmark", "MaxMind.Db.Benchmark\MaxMind.Db.Benchmark.csproj", "{F1051D87-38BE-4E9C-B7B3-9FA2DFB4FB56}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaxMind.Db.Test", "MaxMind.Db.Test\MaxMind.Db.Test.csproj", "{15A04FFF-BCC5-45F9-84E2-AB51B567E3E9}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {923441A3-A9A9-425F-9ABD-73DF13E0A053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {923441A3-A9A9-425F-9ABD-73DF13E0A053}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {923441A3-A9A9-425F-9ABD-73DF13E0A053}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {923441A3-A9A9-425F-9ABD-73DF13E0A053}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {15A04FFF-BCC5-45F9-84E2-AB51B567E3E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {15A04FFF-BCC5-45F9-84E2-AB51B567E3E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {15A04FFF-BCC5-45F9-84E2-AB51B567E3E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {15A04FFF-BCC5-45F9-84E2-AB51B567E3E9}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {F1051D87-38BE-4E9C-B7B3-9FA2DFB4FB56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {F1051D87-38BE-4E9C-B7B3-9FA2DFB4FB56}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {F1051D87-38BE-4E9C-B7B3-9FA2DFB4FB56}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {F1051D87-38BE-4E9C-B7B3-9FA2DFB4FB56}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(SubversionScc) = preSolution 35 | Svn-Managed = True 36 | Manager = AnkhSVN - Subversion Support for Visual Studio 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /MaxMind.Db/ArrayBuffer.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | #endregion 9 | 10 | namespace MaxMind.Db 11 | { 12 | internal sealed class ArrayBuffer : Buffer 13 | { 14 | private readonly byte[] _fileBytes; 15 | 16 | public ArrayBuffer(byte[] array) 17 | { 18 | Length = array.LongLength; 19 | _fileBytes = array; 20 | } 21 | 22 | public ArrayBuffer(string file) : this(File.ReadAllBytes(file)) 23 | { 24 | } 25 | 26 | internal ArrayBuffer(Stream stream) : this(BytesFromStream(stream)) 27 | { 28 | } 29 | 30 | public static async Task CreateAsync(string file) 31 | { 32 | using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); 33 | return await CreateAsync(stream).ConfigureAwait(false); 34 | } 35 | 36 | internal static async Task CreateAsync(Stream stream) 37 | { 38 | return new ArrayBuffer(await BytesFromStreamAsync(stream).ConfigureAwait(false)); 39 | } 40 | 41 | private static byte[] BytesFromStream(Stream stream) 42 | { 43 | if (stream == null) 44 | { 45 | throw new ArgumentNullException(nameof(stream), "The database stream must not be null."); 46 | } 47 | 48 | byte[] bytes; 49 | 50 | using (var memoryStream = new MemoryStream()) 51 | { 52 | stream.CopyTo(memoryStream); 53 | bytes = memoryStream.ToArray(); 54 | } 55 | 56 | if (bytes.Length == 0) 57 | { 58 | throw new InvalidDatabaseException( 59 | "There are zero bytes left in the stream. Perhaps you need to reset the stream's position."); 60 | } 61 | 62 | return bytes; 63 | } 64 | 65 | private static async Task BytesFromStreamAsync(Stream stream) 66 | { 67 | if (stream == null) 68 | { 69 | throw new ArgumentNullException(nameof(stream), "The database stream must not be null."); 70 | } 71 | 72 | byte[] bytes; 73 | 74 | using (var memoryStream = new MemoryStream()) 75 | { 76 | await stream.CopyToAsync(memoryStream).ConfigureAwait(false); 77 | bytes = memoryStream.ToArray(); 78 | } 79 | 80 | if (bytes.Length == 0) 81 | { 82 | throw new InvalidDatabaseException( 83 | "There are zero bytes left in the stream. Perhaps you need to reset the stream's position."); 84 | } 85 | 86 | return bytes; 87 | } 88 | 89 | public override byte[] Read(long offset, int count) 90 | { 91 | var bytes = new byte[count]; 92 | 93 | if (bytes.Length > 0) 94 | { 95 | Array.Copy(_fileBytes, offset, bytes, 0, bytes.Length); 96 | } 97 | 98 | return bytes; 99 | } 100 | 101 | public override byte ReadOne(long offset) => _fileBytes[offset]; 102 | 103 | public override string ReadString(long offset, int count) 104 | => Encoding.UTF8.GetString(_fileBytes, (int)offset, count); 105 | 106 | /// 107 | /// Read an int from the buffer. 108 | /// 109 | public override int ReadInt(long offset) 110 | { 111 | return _fileBytes[offset] << 24 | 112 | _fileBytes[offset + 1] << 16 | 113 | _fileBytes[offset + 2] << 8 | 114 | _fileBytes[offset + 3]; 115 | } 116 | 117 | /// 118 | /// Read a variable-sized int from the buffer. 119 | /// 120 | public override int ReadVarInt(long offset, int count) 121 | { 122 | return count switch 123 | { 124 | 0 => 0, 125 | 1 => _fileBytes[offset], 126 | 2 => _fileBytes[offset] << 8 | 127 | _fileBytes[offset + 1], 128 | 3 => _fileBytes[offset] << 16 | 129 | _fileBytes[offset + 1] << 8 | 130 | _fileBytes[offset + 2], 131 | 4 => ReadInt(offset), 132 | _ => throw new InvalidDatabaseException($"Unexpected int32 of size {count}"), 133 | }; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /MaxMind.Db/Buffer.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Numerics; 5 | 6 | #endregion 7 | 8 | namespace MaxMind.Db 9 | { 10 | internal abstract class Buffer : IDisposable 11 | { 12 | public abstract byte[] Read(long offset, int count); 13 | 14 | public abstract string ReadString(long offset, int count); 15 | 16 | public abstract int ReadInt(long offset); 17 | 18 | public abstract int ReadVarInt(long offset, int count); 19 | 20 | public abstract byte ReadOne(long offset); 21 | 22 | public long Length { get; protected set; } 23 | 24 | /// 25 | /// Read a big integer from the buffer. 26 | /// 27 | internal BigInteger ReadBigInteger(long offset, int size) 28 | { 29 | // This could be optimized if it ever matters 30 | var buffer = Read(offset, size); 31 | Array.Reverse(buffer); 32 | 33 | // The integer will always be positive. We need to make sure 34 | // the last bit is 0. 35 | if (buffer.Length > 0 && (buffer[buffer.Length - 1] & 0x80) > 0) 36 | { 37 | Array.Resize(ref buffer, buffer.Length + 1); 38 | } 39 | return new BigInteger(buffer); 40 | } 41 | 42 | /// 43 | /// Read a double from the buffer. 44 | /// 45 | internal double ReadDouble(long offset) 46 | { 47 | return BitConverter.Int64BitsToDouble(ReadLong(offset, 8)); 48 | } 49 | 50 | /// 51 | /// Read a float from the buffer. 52 | /// 53 | internal float ReadFloat(long offset) 54 | { 55 | #if NETSTANDARD2_0 56 | var buffer = Read(offset, 4); 57 | Array.Reverse(buffer); 58 | return BitConverter.ToSingle(buffer, 0); 59 | #else 60 | return BitConverter.Int32BitsToSingle(ReadInt(offset)); 61 | #endif 62 | } 63 | 64 | /// 65 | /// Read a long from the buffer. 66 | /// 67 | internal long ReadLong(long offset, int size) 68 | { 69 | long val = 0; 70 | for (var i = 0; i < size; i++) 71 | { 72 | val = (val << 8) | ReadOne(offset + i); 73 | } 74 | return val; 75 | } 76 | 77 | /// 78 | /// Read a uint64 from the buffer. 79 | /// 80 | internal ulong ReadULong(long offset, int size) 81 | { 82 | ulong val = 0; 83 | for (var i = 0; i < size; i++) 84 | { 85 | val = (val << 8) | ReadOne(offset + i); 86 | } 87 | return val; 88 | } 89 | 90 | public void Dispose() 91 | { 92 | Dispose(true); 93 | GC.SuppressFinalize(this); 94 | } 95 | 96 | protected virtual void Dispose(bool disposing) 97 | { 98 | // This is overridden in subclasses. 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /MaxMind.Db/CachedDictionary.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2010 Digital Ruby, LLC - http://www.digitalruby.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #nullable disable 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | 22 | namespace MaxMind.Db 23 | { 24 | /// 25 | /// A dictionary that caches up to N values in memory. Once the dictionary reaches N count, the last item in the internal list is removed. 26 | /// New items are always added to the start of the internal list. 27 | /// 28 | internal class CachedDictionary : IDictionary, IDisposable 29 | { 30 | #region Private variables 31 | 32 | private Dictionary>> dictionary; 33 | private LinkedList> priorityList; 34 | private int maxCount; 35 | 36 | #endregion Private variables 37 | 38 | #region Private methods 39 | 40 | private void MoveToFront(LinkedListNode> node) 41 | { 42 | priorityList.Remove(node); 43 | priorityList.AddFirst(node); 44 | } 45 | 46 | private void InternalAdd(TKey key, TValue value) 47 | { 48 | if (dictionary.Count == maxCount) 49 | { 50 | dictionary.Remove(priorityList.Last.Value.Key); 51 | priorityList.RemoveLast(); 52 | } 53 | priorityList.AddFirst(new KeyValuePair(key, value)); 54 | dictionary.Add(key, priorityList.First); 55 | } 56 | 57 | private bool InternalRemove(TKey key) 58 | { 59 | if (!dictionary.TryGetValue(key, out var node)) 60 | return OnRemoveExternalKey(key); 61 | priorityList.Remove(node); 62 | dictionary.Remove(key); 63 | return true; 64 | } 65 | 66 | #endregion Private methods 67 | 68 | #region Protected methods 69 | 70 | /// 71 | /// Fires when a key is not found in the in memory dictionary. This gives derived classes an opportunity to look in external sources like 72 | /// files or databases for the value that key represents. If the derived class finds a value matching the key in the external source, 73 | /// then the derived class can set value and return true; when this happens the newly added value is added to the priority list. 74 | /// 75 | /// Key (can be replaced by the found key if desired) 76 | /// Value that was found 77 | /// True if found from external source, false if not 78 | protected virtual bool OnGetExternalKeyValue(ref TKey key, out TValue value) 79 | { 80 | value = default; 81 | return false; 82 | } 83 | 84 | /// 85 | /// Sets a new comparer. Clears the cache. 86 | /// 87 | /// New comparer 88 | private void SetComparer(IEqualityComparer comparer) 89 | { 90 | dictionary = new Dictionary>>(comparer); 91 | priorityList = []; 92 | } 93 | 94 | /// 95 | /// Removes an external key. The key will have already been normalized. This implementation does nothing. 96 | /// 97 | /// Key to remove 98 | /// True if the key was removed, false if not 99 | protected virtual bool OnRemoveExternalKey(TKey key) 100 | { 101 | return false; 102 | } 103 | 104 | #endregion Protected methods 105 | 106 | #region Public methods 107 | 108 | /// 109 | /// Constructor 110 | /// 111 | /// Maximum count the in memory dictionary will be allowed to grow to 112 | /// Comparer for TKey (can be null for default) 113 | public CachedDictionary(int maxCount, IEqualityComparer comparer) 114 | { 115 | if (maxCount < 1) 116 | { 117 | throw new ArgumentOutOfRangeException("Maxcount is " + maxCount + ", it must be greater than 0"); 118 | } 119 | comparer ??= EqualityComparer.Default; 120 | this.maxCount = maxCount; 121 | SetComparer(comparer); 122 | } 123 | 124 | /// 125 | /// Disposes of all resources. Derived classes should call this base class method last. 126 | /// 127 | public void Dispose() 128 | { 129 | Dispose(true); 130 | GC.SuppressFinalize(this); 131 | } 132 | 133 | protected virtual void Dispose(bool disposing) 134 | { 135 | if (disposing) 136 | { 137 | dictionary = null; 138 | priorityList = null; 139 | maxCount = 0; 140 | } 141 | } 142 | 143 | #endregion Public methods 144 | 145 | #region IDictionary Members 146 | 147 | /// 148 | /// Adds a key / value pair to the dictionary. If the key already exists, it's value is replaced and moved to the front. 149 | /// 150 | /// Key to add 151 | /// Value to add 152 | public void Add(TKey key, TValue value) 153 | { 154 | InternalRemove(key); 155 | InternalAdd(key, value); 156 | } 157 | 158 | /// 159 | /// Checks to see if the given key is in the dictionary by calling TryGetValue. 160 | /// 161 | /// Key 162 | /// True if in dictionary, false if not 163 | public bool ContainsKey(TKey key) 164 | { 165 | return TryGetValue(key, out _); 166 | } 167 | 168 | /// 169 | /// Removes a key from memory. If there is an external source, the key will be removed from the external source if it is 170 | /// not in the dictionary. 171 | /// 172 | /// Key to remove 173 | /// True if key was removed, false if not 174 | public bool Remove(TKey key) 175 | { 176 | return InternalRemove(key); 177 | } 178 | 179 | /// 180 | /// Attempts to get a value given a key. If the key is not found in memory, it is 181 | /// possible for derived classes to search an external source to find the value. In cases where this 182 | /// is done, the newly found item may replace the leased used item if the dictionary is at max count. 183 | /// 184 | /// Key to find 185 | /// Found value (default of TValue if not found) 186 | /// True if found, false if not 187 | public bool TryGetValue(TKey key, out TValue value) 188 | { 189 | return TryGetValueRef(ref key, out value); 190 | } 191 | 192 | /// 193 | /// Attempts to get a value given a key. If the key is not found in memory, it is 194 | /// possible for derived classes to search an external source to find the value. In cases where this 195 | /// is done, the newly found item may replace the leased used item if the dictionary is at max count. 196 | /// 197 | /// Key to find (receives the found key) 198 | /// Found value (default of TValue if not found) 199 | /// True if found, false if not 200 | private bool TryGetValueRef(ref TKey key, out TValue value) 201 | { 202 | if (dictionary.TryGetValue(key, out var node)) 203 | { 204 | MoveToFront(node); 205 | value = node.Value.Value; 206 | key = node.Value.Key; 207 | return true; 208 | } 209 | 210 | if (OnGetExternalKeyValue(ref key, out value)) 211 | { 212 | Add(key, value); 213 | return true; 214 | } 215 | value = default; 216 | return false; 217 | } 218 | 219 | /// 220 | /// Not supported 221 | /// 222 | /// N/A 223 | /// N/A 224 | public TValue this[TKey key] 225 | { 226 | get => throw new NotSupportedException("Use TryGetValue instead"); 227 | set => throw new NotSupportedException("Use Add instead"); 228 | } 229 | 230 | /// 231 | /// Gets all the keys that are in memory 232 | /// 233 | public ICollection Keys => dictionary.Keys; 234 | 235 | /// 236 | /// Gets all of the values that are in memory, external values are not returned 237 | /// 238 | public ICollection Values 239 | { 240 | get 241 | { 242 | var values = new List(dictionary.Values.Count); 243 | foreach (var node in dictionary.Values) 244 | { 245 | values.Add(node.Value.Value); 246 | } 247 | return values; 248 | } 249 | } 250 | 251 | #endregion IDictionary Members 252 | 253 | #region ICollection> Members 254 | 255 | /// 256 | /// Adds an item with the key and value 257 | /// 258 | /// Item to add 259 | /// An item with the key already exists 260 | public void Add(KeyValuePair item) 261 | { 262 | Add(item.Key, item.Value); 263 | } 264 | 265 | /// 266 | /// Clears the dictionary of all items and priority information 267 | /// 268 | public void Clear() 269 | { 270 | dictionary.Clear(); 271 | priorityList.Clear(); 272 | } 273 | 274 | /// 275 | /// Checks to see if an item exists in the dictionary 276 | /// 277 | /// Item to check for 278 | /// True if key of item exists in dictionary, false if not 279 | public bool Contains(KeyValuePair item) 280 | { 281 | return ContainsKey(item.Key); 282 | } 283 | 284 | /// 285 | /// Copies all items from the in memory dictionary to an array 286 | /// 287 | /// Array 288 | /// Start index to copy into array 289 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 290 | { 291 | foreach (var keyValue in dictionary) 292 | { 293 | array[arrayIndex++] = new KeyValuePair(keyValue.Key, keyValue.Value.Value.Value); 294 | } 295 | } 296 | 297 | /// 298 | /// Number of items in the in memory dictionary 299 | /// 300 | public int Count => dictionary.Count; 301 | 302 | /// 303 | /// Always false 304 | /// 305 | public bool IsReadOnly => false; 306 | 307 | /// 308 | /// Removes an item from the in memory dictionary 309 | /// 310 | /// Item to remove 311 | /// True if an item was removed, false if not 312 | public bool Remove(KeyValuePair item) 313 | { 314 | return Remove(item.Key); 315 | } 316 | 317 | #endregion ICollection> Members 318 | 319 | #region IEnumerable> Members 320 | 321 | /// 322 | /// Enumerates all key value pairs in the dictionary, external values are not enumerated 323 | /// 324 | /// Enumerator 325 | public IEnumerator> GetEnumerator() 326 | { 327 | foreach (var node in dictionary.Values) 328 | { 329 | yield return node.Value; 330 | } 331 | } 332 | 333 | #endregion IEnumerable> Members 334 | 335 | #region IEnumerable Members 336 | 337 | /// 338 | /// Enumerates all key value pairs in the dictionary, external values are not enumerated 339 | /// 340 | /// Enumerator 341 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 342 | { 343 | return GetEnumerator(); 344 | } 345 | 346 | #endregion IEnumerable Members 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /MaxMind.Db/ConstructorAttribute.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Instruct Reader to use the constructor when deserializing. 11 | /// 12 | [AttributeUsage(AttributeTargets.Constructor)] 13 | public sealed class ConstructorAttribute : Attribute 14 | { 15 | } 16 | } -------------------------------------------------------------------------------- /MaxMind.Db/CustomDictionary.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | injectables 7 | ip 8 | MaxMind 9 | MaxMindDbConstructor 10 | 11 | 12 | MaxMind 13 | 14 | 15 | -------------------------------------------------------------------------------- /MaxMind.Db/DeserializationException.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Runtime.Serialization; 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Thrown when there is an error deserializing to the provided type. 11 | /// 12 | [Serializable] 13 | public sealed class DeserializationException : Exception 14 | { 15 | /// 16 | /// Construct a DeserializationException 17 | /// 18 | public DeserializationException() : base() 19 | { 20 | } 21 | 22 | /// 23 | /// Construct a DeserializationException 24 | /// 25 | /// 26 | public DeserializationException(string message) 27 | : base(message) 28 | { 29 | } 30 | 31 | /// 32 | /// Construct a DeserializationException 33 | /// 34 | /// 35 | /// The underlying exception that caused this one. 36 | public DeserializationException(string message, Exception innerException) 37 | : base(message, innerException) 38 | { 39 | } 40 | 41 | /// 42 | /// Construct a DeserializationException 43 | /// 44 | /// The SerializationInfo with data. 45 | /// The source for this deserialization. 46 | #if NET8_0_OR_GREATER 47 | [Obsolete(DiagnosticId = "SYSLIB0051")] 48 | #endif 49 | private DeserializationException(SerializationInfo info, StreamingContext context) : base(info, context) 50 | { 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MaxMind.Db/DictionaryActivatorCreator.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | 9 | #endregion 10 | 11 | namespace MaxMind.Db 12 | { 13 | internal sealed class DictionaryActivatorCreator 14 | { 15 | private readonly ConcurrentDictionary _dictActivators = 16 | new(); 17 | 18 | internal ObjectActivator GetActivator(Type expectedType) 19 | => _dictActivators.GetOrAdd(expectedType, DictionaryActivator); 20 | 21 | private static ObjectActivator DictionaryActivator(Type expectedType) 22 | { 23 | var genericArgs = expectedType.GetGenericArguments(); 24 | if (genericArgs.Length != 2) 25 | { 26 | throw new DeserializationException( 27 | $"Unexpected number of Dictionary generic arguments: {genericArgs.Length}"); 28 | } 29 | 30 | ConstructorInfo? constructor; 31 | if (expectedType.GetTypeInfo().IsInterface) 32 | { 33 | var dictType = typeof(Dictionary<,>).MakeGenericType(genericArgs); 34 | ReflectionUtil.CheckType(expectedType, dictType); 35 | constructor = dictType.GetConstructor([typeof(int)]); 36 | } 37 | else 38 | { 39 | ReflectionUtil.CheckType(typeof(IDictionary), expectedType); 40 | constructor = expectedType.GetConstructor(Type.EmptyTypes); 41 | } 42 | if (constructor == null) 43 | throw new DeserializationException($"Unable to find default constructor for {expectedType}"); 44 | var activator = ReflectionUtil.CreateActivator(constructor); 45 | return activator; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /MaxMind.Db/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | #region 7 | 8 | using System.Diagnostics.CodeAnalysis; 9 | 10 | [assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:MaxMind.Db.Reader.ReaderIteratorNode`1")] 11 | [assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "", Scope = "type", Target = "~T:MaxMind.Db.Reader.ReaderIteratorNode`1")] 12 | [assembly: SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.MemoryMapBuffer.#ctor(System.String,System.Boolean,System.IO.FileInfo)")] 13 | [assembly: SuppressMessage("Major Code Smell", "S4457:Parameter validation in \"async\"/\"await\" methods should be wrapped", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.ArrayBuffer.BytesFromStreamAsync(System.IO.Stream)~System.Threading.Tasks.Task{System.Byte[]}")] 14 | [assembly: SuppressMessage("Style", "IDE0056:Use index operator", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Buffer.ReadBigInteger(System.Int64,System.Int32)~System.Numerics.BigInteger")] 15 | [assembly: SuppressMessage("Style", "IDE0056:Use index operator", Justification = "", Scope = "member", Target = "~M:MaxMind.Db.Reader.FindAll``1(MaxMind.Db.InjectableValues,System.Int32)~System.Collections.Generic.IEnumerable{MaxMind.Db.Reader.ReaderIteratorNode{``0}}")] 16 | 17 | #endregion 18 | -------------------------------------------------------------------------------- /MaxMind.Db/InjectAttribute.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Instruct Reader to map database key to constructor parameter. 11 | /// 12 | [AttributeUsage(AttributeTargets.Parameter)] 13 | public sealed class InjectAttribute : Attribute 14 | { 15 | /// 16 | /// The name to use for the property. 17 | /// 18 | public string ParameterName { get; } 19 | 20 | /// 21 | /// Create a new instance of InjectAttribute. 22 | /// 23 | /// 24 | public InjectAttribute(string parameterName) 25 | { 26 | ParameterName = parameterName; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MaxMind.Db/InjectableValues.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System.Collections.Generic; 4 | 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Values to be injected into classes during deserialization. 11 | /// 12 | public sealed class InjectableValues 13 | { 14 | internal IDictionary Values { get; } = new Dictionary(); 15 | 16 | /// 17 | /// Add a value to be injected into the class during serialization 18 | /// 19 | /// 20 | /// The key name as set with the InectAttribute used to determine 21 | /// where to inject the value. 22 | /// 23 | /// The value to be injected. 24 | public void AddValue(string key, object value) 25 | { 26 | Values.Add(key, value); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MaxMind.Db/InvalidDatabaseException.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Runtime.Serialization; 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Thrown when the MaxMind database file is incorrectly formatted 11 | /// 12 | [Serializable] 13 | public sealed class InvalidDatabaseException : Exception 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// A message that describes the error. 19 | public InvalidDatabaseException(string message) 20 | : base(message) 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The error message that explains the reason for the exception. 28 | /// 29 | /// The exception that is the cause of the current exception. If the 30 | /// parameter is not a null reference, the current exception is raised in a catch 31 | /// block that handles the inner exception. 32 | /// 33 | public InvalidDatabaseException(string message, Exception innerException) 34 | : base(message, innerException) 35 | { 36 | } 37 | 38 | /// 39 | /// Constructor for deserialization. 40 | /// 41 | /// The SerializationInfo with data. 42 | /// The source for this deserialization. 43 | #if NET8_0_OR_GREATER 44 | [Obsolete(DiagnosticId = "SYSLIB0051")] 45 | #endif 46 | private InvalidDatabaseException(SerializationInfo info, StreamingContext context) : base(info, context) 47 | { 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MaxMind.Db/Key.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MaxMind.Db 4 | { 5 | internal readonly struct Key : IEquatable 6 | { 7 | private readonly Buffer buffer; 8 | private readonly long offset; 9 | private readonly int size; 10 | private readonly int hashCode; 11 | 12 | public Key(Buffer buffer, long offset, int size) 13 | { 14 | this.buffer = buffer; 15 | this.offset = offset; 16 | this.size = size; 17 | 18 | var code = 17; 19 | for (var i = 0; i < size; i++) 20 | { 21 | code = (31 * code) + buffer.ReadOne(offset + i); 22 | } 23 | hashCode = code; 24 | } 25 | 26 | public bool Equals(Key other) 27 | { 28 | if (size != other.size) 29 | { 30 | return false; 31 | } 32 | 33 | for (var i = 0; i < size; i++) 34 | { 35 | if (buffer.ReadOne(offset + i) != other.buffer.ReadOne(other.offset + i)) 36 | { 37 | return false; 38 | } 39 | } 40 | 41 | return true; 42 | } 43 | 44 | public override bool Equals(object? obj) 45 | { 46 | if (obj == null || typeof(Key) != obj.GetType()) 47 | { 48 | return false; 49 | } 50 | 51 | return Equals((Key)obj); 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return hashCode; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MaxMind.Db/ListActivatorCreator.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | namespace MaxMind.Db 11 | { 12 | internal sealed class ListActivatorCreator 13 | { 14 | private readonly ConcurrentDictionary _listActivators = 15 | new(); 16 | 17 | internal ObjectActivator GetActivator(Type expectedType) 18 | => _listActivators.GetOrAdd(expectedType, ListActivator); 19 | 20 | private static ObjectActivator ListActivator(Type expectedType) 21 | { 22 | var genericArgs = expectedType.GetGenericArguments(); 23 | var argType = genericArgs.Length switch 24 | { 25 | 0 => typeof(object), 26 | 1 => genericArgs[0], 27 | _ => throw new DeserializationException( 28 | $"Unexpected number of generic arguments for list: {genericArgs.Length}"), 29 | }; 30 | ConstructorInfo? constructor; 31 | var interfaceType = typeof(ICollection<>).MakeGenericType(argType); 32 | var listType = typeof(List<>).MakeGenericType(argType); 33 | if (expectedType.IsAssignableFrom(listType)) 34 | { 35 | constructor = listType.GetConstructor([typeof(int)]); 36 | } 37 | else 38 | { 39 | ReflectionUtil.CheckType(interfaceType, expectedType); 40 | constructor = expectedType.GetConstructor(Type.EmptyTypes); 41 | } 42 | if (constructor == null) 43 | throw new DeserializationException($"Unable to find default constructor for {expectedType}"); 44 | var activator = ReflectionUtil.CreateActivator(constructor); 45 | return activator; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /MaxMind.Db/MaxMind.Db.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | .NET reader for the MaxMind DB file format 5 | 4.2.0 6 | net9.0;net8.0;netstandard2.1;netstandard2.0 7 | true 8 | MaxMind.Db 9 | ../MaxMind.snk 10 | true 11 | true 12 | MaxMind.Db 13 | maxmind;ip;geoip;geoip2;geolocation;maxmind;ipv4;ipv6;mmdb;maxminddb 14 | MaxMind-logo.png 15 | README.md 16 | https://github.com/maxmind/MaxMind-DB-Reader-dotnet 17 | Apache-2.0 18 | true 19 | git 20 | https://github.com/maxmind/MaxMind-DB-Reader-dotnet 21 | false 22 | false 23 | false 24 | false 25 | false 26 | false 27 | false 28 | false 29 | 13.0 30 | true 31 | enable 32 | latest 33 | true 34 | true 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | True 45 | 46 | 47 | 48 | True 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /MaxMind.Db/MaxMind.Db.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | -------------------------------------------------------------------------------- /MaxMind.Db/MemoryMapBuffer.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.IO; 5 | using System.IO.MemoryMappedFiles; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | #endregion 11 | 12 | namespace MaxMind.Db 13 | { 14 | internal sealed class MemoryMapBuffer : Buffer 15 | { 16 | private readonly MemoryMappedFile _memoryMappedFile; 17 | private readonly MemoryMappedViewAccessor _view; 18 | private bool _disposed; 19 | 20 | internal MemoryMapBuffer(string file, bool useGlobalNamespace) : this(file, useGlobalNamespace, new FileInfo(file)) 21 | { 22 | } 23 | 24 | private MemoryMapBuffer(string file, bool useGlobalNamespace, FileInfo fileInfo) 25 | { 26 | using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, 27 | FileShare.Delete | FileShare.Read); 28 | Length = stream.Length; 29 | // Ideally we would use the file ID in the mapName, but it is not 30 | // easily available from C#. 31 | var objectNamespace = useGlobalNamespace ? "Global" : "Local"; 32 | 33 | // We create a sha256 here as there are limitations on mutex names. 34 | using var sha256 = SHA256.Create(); 35 | var suffixTxt = $"{fileInfo.FullName.Replace("\\", "-")}-{Length}"; 36 | var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(suffixTxt)); 37 | var suffix = BitConverter.ToString(hashBytes).Replace("-", ""); 38 | 39 | var mapName = $"{objectNamespace}\\{suffix}"; 40 | var mutexName = $"{mapName}-Mutex"; 41 | 42 | using (var mutex = new Mutex(false, mutexName)) 43 | { 44 | var hasHandle = false; 45 | 46 | try 47 | { 48 | hasHandle = mutex.WaitOne(TimeSpan.FromSeconds(10), false); 49 | if (!hasHandle) 50 | { 51 | throw new TimeoutException("Timeout waiting for mutex."); 52 | } 53 | 54 | _memoryMappedFile = MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.Read); 55 | } 56 | catch (Exception ex) when (ex is IOException or NotImplementedException or PlatformNotSupportedException) 57 | { 58 | // In .NET Core, named maps are not supported for Unices yet: https://github.com/dotnet/corefx/issues/1329 59 | // When executed on unsupported platform, we get the PNSE. In which case, we consruct the memory map by 60 | // setting mapName to null. 61 | if (ex is PlatformNotSupportedException) 62 | mapName = null; 63 | 64 | _memoryMappedFile = MemoryMappedFile.CreateFromFile(stream, mapName, Length, 65 | MemoryMappedFileAccess.Read, HandleInheritability.None, false); 66 | } 67 | finally 68 | { 69 | if (hasHandle) 70 | { 71 | mutex.ReleaseMutex(); 72 | } 73 | } 74 | } 75 | 76 | _view = _memoryMappedFile.CreateViewAccessor(0, Length, MemoryMappedFileAccess.Read); 77 | } 78 | 79 | public override byte[] Read(long offset, int count) 80 | { 81 | var bytes = new byte[count]; 82 | 83 | // Although not explicitly marked as thread safe, from 84 | // reviewing the source code, these operations appear to 85 | // be thread safe as long as only read operations are 86 | // being done. 87 | _view.ReadArray(offset, bytes, 0, bytes.Length); 88 | 89 | return bytes; 90 | } 91 | 92 | public override byte ReadOne(long offset) => _view.ReadByte(offset); 93 | 94 | public override string ReadString(long offset, int count) 95 | { 96 | if (offset + count > _view.Capacity) 97 | { 98 | throw new ArgumentOutOfRangeException( 99 | nameof(offset), 100 | "Attempt to read beyond the end of the MemoryMappedFile."); 101 | } 102 | unsafe 103 | { 104 | var ptr = (byte*)0; 105 | try 106 | { 107 | _view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); 108 | return Encoding.UTF8.GetString(ptr + offset, count); 109 | } 110 | finally 111 | { 112 | _view.SafeMemoryMappedViewHandle.ReleasePointer(); 113 | } 114 | } 115 | } 116 | 117 | /// 118 | /// Read an int from the buffer. 119 | /// 120 | public override int ReadInt(long offset) 121 | { 122 | return _view.ReadByte(offset) << 24 | 123 | _view.ReadByte(offset + 1) << 16 | 124 | _view.ReadByte(offset + 2) << 8 | 125 | _view.ReadByte(offset + 3); 126 | } 127 | 128 | /// 129 | /// Read a variable-sized int from the buffer. 130 | /// 131 | public override int ReadVarInt(long offset, int count) 132 | { 133 | return count switch 134 | { 135 | 0 => 0, 136 | 1 => _view.ReadByte(offset), 137 | 2 => _view.ReadByte(offset) << 8 | 138 | _view.ReadByte(offset + 1), 139 | 3 => _view.ReadByte(offset) << 16 | 140 | _view.ReadByte(offset + 1) << 8 | 141 | _view.ReadByte(offset + 2), 142 | 4 => ReadInt(offset), 143 | _ => throw new InvalidDatabaseException($"Unexpected int32 of size {count}"), 144 | }; 145 | } 146 | 147 | /// 148 | /// Release resources back to the system. 149 | /// 150 | /// 151 | protected override void Dispose(bool disposing) 152 | { 153 | if (_disposed) 154 | return; 155 | 156 | if (disposing) 157 | { 158 | _view.Dispose(); 159 | _memoryMappedFile.Dispose(); 160 | } 161 | 162 | _disposed = true; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /MaxMind.Db/Metadata.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | #endregion 7 | 8 | namespace MaxMind.Db 9 | { 10 | /// 11 | /// Data about the database file itself 12 | /// 13 | public sealed class Metadata 14 | { 15 | /// 16 | /// Construct a metadata object. 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | [Constructor] 28 | [CLSCompliant(false)] 29 | public Metadata( 30 | [Parameter("binary_format_major_version")] int binaryFormatMajorVersion, 31 | [Parameter("binary_format_minor_version")] int binaryFormatMinorVersion, 32 | [Parameter("build_epoch")] ulong buildEpoch, 33 | [Parameter("database_type")] string databaseType, 34 | IDictionary description, 35 | [Parameter("ip_version")] int ipVersion, 36 | IReadOnlyList languages, 37 | [Parameter("node_count")] long nodeCount, 38 | [Parameter("record_size")] int recordSize 39 | ) 40 | { 41 | BinaryFormatMajorVersion = binaryFormatMajorVersion; 42 | BinaryFormatMinorVersion = binaryFormatMinorVersion; 43 | BuildEpoch = buildEpoch; 44 | DatabaseType = databaseType; 45 | Description = description; 46 | IPVersion = ipVersion; 47 | Languages = languages; 48 | NodeCount = nodeCount; 49 | RecordSize = recordSize; 50 | } 51 | 52 | /// 53 | /// The major version number for the MaxMind DB binary format used by the database. 54 | /// 55 | public int BinaryFormatMajorVersion { get; } 56 | 57 | /// 58 | /// The minor version number for the MaxMind DB binary format used by the database. 59 | /// 60 | public int BinaryFormatMinorVersion { get; } 61 | 62 | internal ulong BuildEpoch { get; } 63 | 64 | /// 65 | /// The date-time of the database build. 66 | /// 67 | public DateTime BuildDate => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(BuildEpoch); 68 | 69 | /// 70 | /// The MaxMind DB database type. 71 | /// 72 | public string DatabaseType { get; } 73 | 74 | /// 75 | /// A map from locale codes to the database description in that language. 76 | /// 77 | public IDictionary Description { get; } 78 | 79 | /// 80 | /// The IP version that the database supports. This will be 4 or 6. 81 | /// 82 | public int IPVersion { get; } 83 | 84 | /// 85 | /// A list of locale codes for languages that the database supports. 86 | /// 87 | public IReadOnlyList Languages { get; } 88 | 89 | internal long NodeCount { get; } 90 | 91 | internal int RecordSize { get; } 92 | 93 | internal long NodeByteSize => RecordSize / 4; 94 | 95 | internal long SearchTreeSize => NodeCount * NodeByteSize; 96 | } 97 | } -------------------------------------------------------------------------------- /MaxMind.Db/Network.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace MaxMind.Db 4 | { 5 | /// 6 | /// Network represents an IP network. 7 | /// 8 | public sealed class Network 9 | { 10 | private readonly IPAddress ip; 11 | 12 | /// 13 | /// The prefix length is the number of leading 1 bits in the 14 | /// subnet mask. Sometimes also known as netmask length. 15 | /// 16 | public int PrefixLength { get; } 17 | 18 | /// 19 | /// The first address in the network. 20 | /// 21 | public IPAddress NetworkAddress 22 | { 23 | get 24 | { 25 | var ipBytes = ip.GetAddressBytes(); 26 | var networkBytes = new byte[ipBytes.Length]; 27 | var curPrefix = PrefixLength; 28 | for (var i = 0; i < ipBytes.Length && curPrefix > 0; i++) 29 | { 30 | var b = ipBytes[i]; 31 | if (curPrefix < 8) 32 | { 33 | var shiftN = 8 - curPrefix; 34 | b = (byte)(0xFF & (b >> shiftN) << shiftN); 35 | } 36 | networkBytes[i] = b; 37 | curPrefix -= 8; 38 | } 39 | 40 | return new IPAddress(networkBytes); 41 | } 42 | } 43 | 44 | /// 45 | /// Constructs a Network. 46 | /// 47 | /// 48 | /// An IP address in the network. This does not have to be the 49 | /// first address in the network. 50 | /// 51 | /// The prefix length for the network. 52 | public Network(IPAddress ip, int prefixLength) 53 | { 54 | this.ip = ip; 55 | PrefixLength = prefixLength; 56 | } 57 | 58 | /// 59 | /// A string representation of the network in CIDR notation, e.g., 60 | /// 1.2.3.0/24 or 2001::/8. 61 | /// 62 | public override string ToString() 63 | { 64 | return $"{NetworkAddress}/{PrefixLength}"; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MaxMind.Db/NetworkAttribute.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Instruct Reader to set the parameter to be the network in CIDR format. 11 | /// 12 | [AttributeUsage(AttributeTargets.Parameter)] 13 | public sealed class NetworkAttribute : Attribute 14 | { 15 | /// 16 | /// Create a new instance of NetworkAttribute. 17 | /// 18 | public NetworkAttribute() 19 | { 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /MaxMind.Db/ParameterAttribute.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | 5 | #endregion 6 | 7 | namespace MaxMind.Db 8 | { 9 | /// 10 | /// Instruct Reader to map database key to constructor parameter. 11 | /// 12 | [AttributeUsage(AttributeTargets.Parameter)] 13 | public sealed class ParameterAttribute : Attribute 14 | { 15 | /// 16 | /// The name to use for the property. 17 | /// 18 | public string ParameterName { get; } 19 | 20 | /// 21 | /// Whether to create the object even if the key is not present in 22 | /// the database. If this is false, the default value will be used 23 | /// (null for nullable types). 24 | /// 25 | public bool AlwaysCreate { get; } 26 | 27 | /// 28 | /// Create a new instance of ParameterAttribute. 29 | /// 30 | /// The name of the parameter. 31 | /// 32 | /// Whether to create the object even if the key 33 | /// is not present in the database. If this is false, the default value will be used 34 | /// (null for nullable types) 35 | /// 36 | public ParameterAttribute(string parameterName, bool alwaysCreate = false) 37 | { 38 | ParameterName = parameterName; 39 | AlwaysCreate = alwaysCreate; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /MaxMind.Db/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | 8 | #endregion 9 | 10 | // General Information about an assembly is controlled through the following 11 | // set of attributes. Change these attribute values to modify the information 12 | // associated with an assembly. 13 | 14 | [assembly: AssemblyTitle("MaxMind.Db")] 15 | [assembly: AssemblyDescription(".NET reader for the MaxMind DB file format")] 16 | [assembly: AssemblyConfiguration("")] 17 | [assembly: AssemblyCompany("MaxMind, Inc.")] 18 | [assembly: AssemblyProduct("MaxMind.Db")] 19 | [assembly: AssemblyCopyright("Copyright © 2013-2025 MaxMind, Inc.")] 20 | [assembly: AssemblyTrademark("")] 21 | [assembly: AssemblyCulture("")] 22 | 23 | // Setting ComVisible to false makes the types in this assembly not visible 24 | // to COM components. If you need to access a type in this assembly from 25 | // COM, set the ComVisible attribute to true on that type. 26 | 27 | [assembly: ComVisible(false)] 28 | 29 | // The following GUID is for the ID of the typelib if this project is exposed to COM 30 | 31 | [assembly: Guid("044a98c5-0d88-4273-a769-ef4c72cf1bd9")] 32 | [assembly: CLSCompliant(true)] 33 | 34 | // Version information for an assembly consists of the following four values: 35 | // 36 | // Major Version 37 | // Minor Version 38 | // Build Number 39 | // Revision 40 | // 41 | // You can specify all the values or you can default the Build and Revision Numbers 42 | // by using the '*' as shown below: 43 | // [assembly: AssemblyVersion("1.0.*")] 44 | 45 | [assembly: AssemblyVersion("4.0.0")] 46 | [assembly: AssemblyFileVersion("4.0.0")] 47 | [assembly: InternalsVisibleTo("MaxMind.Db.Test,PublicKey=" + 48 | "0024000004800000940000000602000000240000525341310004000001000100e30b6e4a9425b1" + 49 | "617ffc8bdf79801e67a371f9f650db860dc0dfff92cb63258765a0955c6fcde1da78dbaf5bf84d" + 50 | "0230946435957d2e52dc0d15673e372248dbff3bc8e6c75a632072e52cb0444850dddff5cc2be8" + 51 | "f3e1f8954d7ede7675675a071672d9e97d3153d96b40fd30234be33eeb7fd1a4a78d6342967700" + 52 | "56a2b1e5")] 53 | -------------------------------------------------------------------------------- /MaxMind.Db/Reader.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Threading.Tasks; 10 | 11 | #endregion 12 | 13 | namespace MaxMind.Db 14 | { 15 | /// 16 | /// An enumeration specifying the API to use to read the database 17 | /// 18 | public enum FileAccessMode 19 | { 20 | /// 21 | /// Open the file in memory mapped mode. Does not load into real memory. 22 | /// 23 | MemoryMapped, 24 | 25 | /// 26 | /// Open the file in global memory mapped mode. Requires the 'create global objects' right. Does not load into real memory. 27 | /// 28 | /// 29 | /// For information on the 'create global objects' right, see: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-global-objects 30 | /// 31 | MemoryMappedGlobal, 32 | 33 | /// 34 | /// Load the file into memory. 35 | /// 36 | Memory, 37 | } 38 | 39 | /// 40 | /// Given a MaxMind DB file, this class will retrieve information about an IP address 41 | /// 42 | public sealed class Reader : IDisposable 43 | { 44 | /// 45 | /// A node from the reader iterator 46 | /// 47 | public struct ReaderIteratorNode 48 | { 49 | /// 50 | /// Internal constructor 51 | /// 52 | /// Start ip 53 | /// Prefix length 54 | /// Data 55 | internal ReaderIteratorNode(IPAddress start, int prefixLength, T data) 56 | { 57 | Start = start; 58 | PrefixLength = prefixLength; 59 | Data = data; 60 | } 61 | 62 | /// 63 | /// Start ip address 64 | /// 65 | public IPAddress Start { get; } 66 | 67 | /// 68 | /// Prefix/mask length 69 | /// 70 | public int PrefixLength { get; } 71 | 72 | /// 73 | /// Data 74 | /// 75 | public T Data { get; } 76 | } 77 | 78 | private struct NetNode 79 | { 80 | public byte[] IPBytes { get; set; } 81 | public int Bit { get; set; } 82 | public int Pointer { get; set; } 83 | } 84 | 85 | private const int DataSectionSeparatorSize = 16; 86 | private readonly Buffer _database; 87 | private readonly string? _fileName; 88 | private readonly long _dataPointerOffset; 89 | private readonly int _dbIPVersion; 90 | private readonly long _nodeByteSize; 91 | private readonly long _nodeCount; 92 | private readonly int _recordSize; 93 | 94 | // The property getter was a hotspot during profiling. 95 | 96 | private readonly byte[] _metadataStartMarker = 97 | [ 98 | 0xAB, 0xCD, 0xEF, 77, 97, 120, 77, 105, 110, 100, 46, 99, 111, 99 | 109 100 | ]; 101 | 102 | private bool _disposed; 103 | private readonly int _ipV4Start; 104 | 105 | /// 106 | /// Initializes a new instance of the class. 107 | /// 108 | /// The file. 109 | public Reader(string file) : this(file, FileAccessMode.MemoryMapped) 110 | { 111 | } 112 | 113 | /// 114 | /// Initializes a new instance of the class. 115 | /// 116 | /// The MaxMind DB file. 117 | /// The mode by which to access the DB file. 118 | public Reader(string file, FileAccessMode mode) : this(BufferForMode(file, mode), file) 119 | { 120 | } 121 | 122 | /// 123 | /// Initialize with Stream. The current position of the 124 | /// string must point to the start of the database. The content 125 | /// between the current position and the end of the stream must 126 | /// be a valid MaxMind DB. 127 | /// 128 | /// The stream to use. It will be used from its 129 | /// current position. 130 | /// 131 | public Reader(Stream stream) : this(new ArrayBuffer(stream), null) 132 | { 133 | } 134 | 135 | private Reader(Buffer buffer, string? file) 136 | { 137 | _fileName = file; 138 | _database = buffer; 139 | var start = FindMetadataStart(); 140 | var metaDecode = new Decoder(_database, start); 141 | Metadata = metaDecode.Decode(start, out _); 142 | _dataPointerOffset = Metadata.SearchTreeSize - Metadata.NodeCount; 143 | _dbIPVersion = Metadata.IPVersion; 144 | _nodeByteSize = Metadata.NodeByteSize; 145 | _nodeCount = Metadata.NodeCount; 146 | _recordSize = Metadata.RecordSize; 147 | Decoder = new Decoder(_database, Metadata.SearchTreeSize + DataSectionSeparatorSize); 148 | 149 | if (_dbIPVersion == 6) 150 | { 151 | var node = 0; 152 | for (var i = 0; i < 96 && node < _nodeCount; i++) 153 | { 154 | node = ReadNode(node, 0); 155 | } 156 | _ipV4Start = node; 157 | } 158 | } 159 | 160 | /// 161 | /// Asynchronously initializes a new instance of the class by loading the specified file into memory. 162 | /// 163 | /// The file. 164 | public static async Task CreateAsync(string file) 165 | { 166 | return new Reader(await ArrayBuffer.CreateAsync(file).ConfigureAwait(false), file); 167 | } 168 | 169 | /// 170 | /// Asynchronously initialize with Stream. 171 | /// 172 | /// The stream to use. It will be used from its current position. 173 | /// 174 | public static async Task CreateAsync(Stream stream) 175 | { 176 | return new Reader(await ArrayBuffer.CreateAsync(stream).ConfigureAwait(false), null); 177 | } 178 | 179 | private static Buffer BufferForMode(string file, FileAccessMode mode) 180 | { 181 | return mode switch 182 | { 183 | FileAccessMode.MemoryMapped => new MemoryMapBuffer(file, false), 184 | FileAccessMode.MemoryMappedGlobal => new MemoryMapBuffer(file, true), 185 | FileAccessMode.Memory => new ArrayBuffer(file), 186 | _ => throw new ArgumentException("Unknown file access mode"), 187 | }; 188 | } 189 | 190 | /// 191 | /// The metadata for the open database. 192 | /// 193 | /// 194 | /// The metadata. 195 | /// 196 | public Metadata Metadata { get; } 197 | 198 | private Decoder Decoder { get; } 199 | 200 | /// 201 | /// Release resources back to the system. 202 | /// 203 | public void Dispose() 204 | { 205 | Dispose(true); 206 | GC.SuppressFinalize(this); 207 | } 208 | 209 | /// 210 | /// Release resources back to the system. 211 | /// 212 | /// 213 | private void Dispose(bool disposing) 214 | { 215 | if (_disposed) 216 | return; 217 | 218 | if (disposing) 219 | { 220 | _database.Dispose(); 221 | } 222 | 223 | _disposed = true; 224 | } 225 | 226 | /// 227 | /// Finds the data related to the specified address. 228 | /// 229 | /// The IP address. 230 | /// Value to inject during deserialization 231 | /// An object containing the IP related data 232 | public T? Find(IPAddress ipAddress, InjectableValues? injectables = null) where T : class 233 | { 234 | return Find(ipAddress, out _, injectables); 235 | } 236 | 237 | /// 238 | /// Finds the data related to the specified address. 239 | /// 240 | /// The IP address. 241 | /// The network prefix length for the network record in the database containing the IP address looked up. 242 | /// Value to inject during deserialization 243 | /// An object containing the IP related data 244 | public T? Find(IPAddress ipAddress, out int prefixLength, InjectableValues? injectables = null) where T : class 245 | { 246 | var pointer = FindAddressInTree(ipAddress, out prefixLength); 247 | var network = new Network(ipAddress, prefixLength); 248 | return pointer == 0 ? null : ResolveDataPointer(pointer, injectables, network); 249 | } 250 | 251 | /// 252 | /// Get an enumerator that iterates all data nodes in the database. Do not modify the object as it may be cached. 253 | /// Note that due to caching, the Network attribute on constructor parameters will be ignored. 254 | /// 255 | /// Value to inject during deserialization 256 | /// The size of the data cache. This can greatly speed enumeration at the cost of memory usage. 257 | /// Enumerator for all data nodes 258 | public IEnumerable> FindAll(InjectableValues? injectables = null, int cacheSize = 16384) where T : class 259 | { 260 | var byteCount = _dbIPVersion == 6 ? 16 : 4; 261 | var nodes = new List(); 262 | var root = new NetNode { IPBytes = new byte[byteCount] }; 263 | nodes.Add(root); 264 | var dataCache = new CachedDictionary(cacheSize, null); 265 | while (nodes.Count > 0) 266 | { 267 | var node = nodes[nodes.Count - 1]; 268 | nodes.RemoveAt(nodes.Count - 1); 269 | while (true) 270 | { 271 | if (node.Pointer < _nodeCount) 272 | { 273 | var ipRight = new byte[byteCount]; 274 | Array.Copy(node.IPBytes, ipRight, ipRight.Length); 275 | if (ipRight.Length <= node.Bit >> 3) 276 | { 277 | throw new InvalidDataException("Invalid search tree, bad bit " + node.Bit); 278 | } 279 | ipRight[node.Bit >> 3] |= (byte)(1 << (7 - (node.Bit % 8))); 280 | var rightPointer = ReadNode(node.Pointer, 1); 281 | node.Bit++; 282 | nodes.Add(new NetNode { Pointer = rightPointer, IPBytes = ipRight, Bit = node.Bit }); 283 | node.Pointer = ReadNode(node.Pointer, 0); 284 | } 285 | else 286 | { 287 | if (node.Pointer > _nodeCount) 288 | { 289 | // data node, we are done with this branch 290 | if (!dataCache.TryGetValue(node.Pointer, out var data)) 291 | { 292 | data = ResolveDataPointer(node.Pointer, injectables, null); 293 | dataCache.Add(node.Pointer, data); 294 | } 295 | var isIPV4 = true; 296 | for (var i = 0; i < node.IPBytes.Length - 4; i++) 297 | { 298 | if (node.IPBytes[i] == 0) continue; 299 | 300 | isIPV4 = false; 301 | break; 302 | } 303 | if (!isIPV4 || node.IPBytes.Length == 4) 304 | { 305 | yield return new ReaderIteratorNode(new IPAddress(node.IPBytes), node.Bit, data); 306 | } 307 | else 308 | { 309 | yield return new ReaderIteratorNode(new IPAddress(node.IPBytes.Skip(12).Take(4).ToArray()), node.Bit - 96, data); 310 | } 311 | } 312 | // else node is an empty node (terminator node), we are done with this branch 313 | break; 314 | } 315 | } 316 | } 317 | } 318 | 319 | private T ResolveDataPointer(int pointer, InjectableValues? injectables, Network? network) where T : class 320 | { 321 | var resolved = pointer + _dataPointerOffset; 322 | 323 | if (resolved >= _database.Length) 324 | { 325 | throw new InvalidDatabaseException( 326 | "The MaxMind Db file's search tree is corrupt: " 327 | + "contains pointer larger than the database."); 328 | } 329 | 330 | return Decoder.Decode(resolved, out _, injectables, network); 331 | } 332 | 333 | private int FindAddressInTree(IPAddress address, out int prefixLength) 334 | { 335 | #if NETSTANDARD2_0 336 | byte[] rawAddress; 337 | #else 338 | Span rawAddress = stackalloc byte[address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16]; 339 | if (address.TryWriteBytes(rawAddress, out int rawAddressLength)) 340 | { 341 | rawAddress = rawAddress[..rawAddressLength]; 342 | } 343 | else 344 | #endif 345 | { 346 | // Defensive check. 347 | rawAddress = address.GetAddressBytes(); 348 | } 349 | 350 | return FindAddressInTree(rawAddress, out prefixLength); 351 | } 352 | 353 | #if NETSTANDARD2_0 354 | private int FindAddressInTree(byte[] rawAddress, out int prefixLength) 355 | #else 356 | private int FindAddressInTree(ReadOnlySpan rawAddress, out int prefixLength) 357 | #endif 358 | { 359 | var bitLength = rawAddress.Length * 8; 360 | var record = StartNode(bitLength); 361 | var nodeCount = _nodeCount; 362 | 363 | var i = 0; 364 | for (; i < bitLength && record < nodeCount; i++) 365 | { 366 | var bit = 1 & (rawAddress[i >> 3] >> (7 - (i % 8))); 367 | record = ReadNode(record, bit); 368 | } 369 | prefixLength = i; 370 | if (record == nodeCount) 371 | { 372 | // record is empty 373 | return 0; 374 | } 375 | if (record > nodeCount) 376 | { 377 | // record is a data pointer 378 | return record; 379 | } 380 | throw new InvalidDatabaseException("Something bad happened"); 381 | } 382 | 383 | private int StartNode(int bitLength) 384 | { 385 | // Check if we are looking up an IPv4 address in an IPv6 tree. If this 386 | // is the case, we can skip over the first 96 nodes. 387 | if (_dbIPVersion == 6 && bitLength == 32) 388 | { 389 | return _ipV4Start; 390 | } 391 | // The first node of the tree is always node 0, at the beginning of the 392 | // value 393 | return 0; 394 | } 395 | 396 | private long FindMetadataStart() 397 | { 398 | var dbLength = _database.Length; 399 | var markerLength = (long)_metadataStartMarker.Length; 400 | 401 | for (var i = dbLength - markerLength; i > 0; i--) 402 | { 403 | var j = 0; 404 | for (; j < markerLength; j++) 405 | { 406 | if (_metadataStartMarker[j] != _database.ReadOne(i + j)) 407 | { 408 | break; 409 | } 410 | } 411 | if (j == markerLength) 412 | { 413 | return i + markerLength; 414 | } 415 | } 416 | 417 | throw new InvalidDatabaseException( 418 | $"Could not find a MaxMind Db metadata marker in this file ({_fileName}). Is this a valid MaxMind Db file?"); 419 | } 420 | 421 | private int ReadNode(int nodeNumber, int index) 422 | { 423 | var baseOffset = nodeNumber * _nodeByteSize; 424 | 425 | var size = _recordSize; 426 | 427 | switch (size) 428 | { 429 | case 24: 430 | { 431 | var offset = baseOffset + (index * 3); 432 | return _database.ReadVarInt(offset, 3); 433 | } 434 | case 28: 435 | { 436 | if (index == 0) 437 | { 438 | var v = _database.ReadInt(baseOffset); 439 | return (v & 0xF0) << 20 | (0xFFFFFF & (v >> 8)); 440 | } 441 | return _database.ReadInt(baseOffset + 3) & 0x0FFFFFFF; 442 | } 443 | case 32: 444 | { 445 | var offset = baseOffset + (index * 4); 446 | return _database.ReadInt(offset); 447 | } 448 | } 449 | 450 | throw new InvalidDatabaseException($"Unknown record size: {size}"); 451 | } 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /MaxMind.Db/ReflectionUtil.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | #endregion 8 | 9 | namespace MaxMind.Db 10 | { 11 | internal delegate object ObjectActivator(params object?[] args); 12 | 13 | internal static class ReflectionUtil 14 | { 15 | // Activator.CreateInstance is extremely slow and ConstuctorInfo.Invoke is 16 | // somewhat slow. This faster alternative (when cached) is largely based off 17 | // of: 18 | // http://rogeralsing.com/2008/02/28/linq-expressions-creating-objects/ 19 | internal static ObjectActivator CreateActivator(ConstructorInfo constructor) 20 | { 21 | if (constructor == null) 22 | { 23 | throw new ArgumentNullException(nameof(constructor)); 24 | } 25 | var paramInfo = constructor.GetParameters(); 26 | 27 | var paramExp = Expression.Parameter(typeof(object[]), "args"); 28 | 29 | var argsExp = new Expression[paramInfo.Length]; 30 | for (var i = 0; i < paramInfo.Length; i++) 31 | { 32 | var index = Expression.Constant(i); 33 | var paramType = paramInfo[i].ParameterType; 34 | var accessorExp = Expression.ArrayIndex(paramExp, index); 35 | var castExp = Expression.Convert(accessorExp, paramType); 36 | argsExp[i] = castExp; 37 | } 38 | 39 | var newExp = Expression.New(constructor, argsExp); 40 | var lambda = Expression.Lambda(typeof(ObjectActivator), newExp, paramExp); 41 | return (ObjectActivator)lambda.Compile(); 42 | } 43 | 44 | internal static void CheckType(Type expected, Type from) 45 | { 46 | if (!expected.IsAssignableFrom(from)) 47 | { 48 | throw new DeserializationException($"Could not convert '{from}' to '{expected}'."); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /MaxMind.Db/TypeAcivatorCreator.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | 10 | #endregion 11 | 12 | namespace MaxMind.Db 13 | { 14 | internal class TypeActivator 15 | { 16 | internal readonly ObjectActivator Activator; 17 | internal readonly ParameterInfo[] AlwaysCreatedParameters; 18 | internal readonly object?[] DefaultParameters; 19 | internal readonly Dictionary DeserializationParameters; 20 | internal readonly KeyValuePair[] InjectableParameters; 21 | internal readonly ParameterInfo[] NetworkParameters; 22 | 23 | internal TypeActivator( 24 | ObjectActivator activator, 25 | Dictionary deserializationParameters, 26 | KeyValuePair[] injectables, 27 | ParameterInfo[] networkParameters, 28 | ParameterInfo[] alwaysCreatedParameters 29 | ) 30 | { 31 | Activator = activator; 32 | AlwaysCreatedParameters = alwaysCreatedParameters; 33 | DeserializationParameters = deserializationParameters; 34 | InjectableParameters = injectables; 35 | 36 | NetworkParameters = networkParameters; 37 | Type[] parameterTypes = deserializationParameters.Values.OrderBy(x => x.Position).Select(x => x.ParameterType).ToArray(); 38 | DefaultParameters = parameterTypes.Select(DefaultValue).ToArray(); 39 | } 40 | 41 | private static object? DefaultValue(Type type) 42 | { 43 | if (type.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(type) == null) 44 | { 45 | return System.Activator.CreateInstance(type); 46 | } 47 | return null; 48 | } 49 | } 50 | 51 | internal sealed class TypeAcivatorCreator 52 | { 53 | private readonly ConcurrentDictionary _typeConstructors = 54 | new(); 55 | 56 | internal TypeActivator GetActivator(Type expectedType) 57 | => _typeConstructors.GetOrAdd(expectedType, ClassActivator); 58 | 59 | private static TypeActivator ClassActivator(Type expectedType) 60 | { 61 | var constructors = 62 | expectedType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 63 | .Where(c => c.IsDefined(typeof(ConstructorAttribute), true)) 64 | .ToList(); 65 | if (constructors.Count == 0) 66 | { 67 | throw new DeserializationException( 68 | $"No constructors found for {expectedType} found with MaxMind.Db.Constructor attribute"); 69 | } 70 | if (constructors.Count > 1) 71 | { 72 | throw new DeserializationException( 73 | $"More than one constructor found for {expectedType} found with MaxMind.Db/Constructor attribute"); 74 | } 75 | 76 | var constructor = constructors[0]; 77 | var parameters = constructor.GetParameters(); 78 | var paramNameTypes = new Dictionary(); 79 | var injectables = new List>(); 80 | var networkParams = new List(); 81 | var alwaysCreated = new List(); 82 | foreach (var param in parameters) 83 | { 84 | var injectableAttribute = param.GetCustomAttributes().FirstOrDefault(); 85 | if (injectableAttribute != null) 86 | { 87 | injectables.Add(new KeyValuePair(injectableAttribute.ParameterName, param)); 88 | } 89 | var networkAttribute = param.GetCustomAttributes().FirstOrDefault(); 90 | if (networkAttribute != null) 91 | { 92 | networkParams.Add(param); 93 | } 94 | var paramAttribute = param.GetCustomAttributes().FirstOrDefault(); 95 | string? name; 96 | if (paramAttribute != null) 97 | { 98 | name = paramAttribute.ParameterName; 99 | if (paramAttribute.AlwaysCreate) 100 | alwaysCreated.Add(param); 101 | } 102 | else 103 | { 104 | name = param.Name; 105 | if (name == null) 106 | { 107 | throw new DeserializationException("Unexpected null parameter name"); 108 | } 109 | } 110 | var bytes = Encoding.UTF8.GetBytes(name); 111 | paramNameTypes.Add(new Key(new ArrayBuffer(bytes), 0, bytes.Length), param); 112 | } 113 | var activator = ReflectionUtil.CreateActivator(constructor); 114 | var clsConstructor = new TypeActivator(activator, paramNameTypes, injectables.ToArray(), 115 | networkParams.ToArray(), alwaysCreated.ToArray()); 116 | return clsConstructor; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /MaxMind.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmind/MaxMind-DB-Reader-dotnet/d78205669bf782f27ffc3033367c29b0459713ae/MaxMind.snk -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.dev.md: -------------------------------------------------------------------------------- 1 | To publish the to NuGet: 2 | 3 | 1. Update release notes. 4 | 2. Run `.\dev-bin\release.ps1`. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaxMind DB Reader # 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/MaxMind.Db)](https://www.nuget.org/packages/MaxMind.Db) 4 | 5 | ## Description ## 6 | 7 | This is the .NET API for reading MaxMind DB files. MaxMind DB is a binary file 8 | format that stores data indexed by IP address subnets (IPv4 or IPv6). 9 | 10 | ## Installation ## 11 | 12 | ### NuGet ### 13 | 14 | We recommend installing this library with NuGet. To do this, type the 15 | following into the Visual Studio Package Manager Console: 16 | 17 | ``` 18 | install-package MaxMind.Db 19 | ``` 20 | 21 | ## Usage ## 22 | 23 | *Note:* For accessing MaxMind GeoIP2 databases, we generally recommend using 24 | the GeoIP2 .NET API rather than using this package directly. 25 | 26 | To use the API, you must first create a `Reader` object. The constructor for 27 | the reader object takes a `string` with the path to the MaxMind DB file. 28 | Optionally you may pass a second parameter with a `FileAccessMode` enum with 29 | the value `MemoryMapped` or `Memory`. The default mode is `MemoryMapped`, 30 | which maps the file to virtual memory. This often provides performance 31 | comparable to loading the file into real memory with the `Memory` mode while 32 | using significantly less memory. 33 | 34 | To look up an IP address, pass a `System.Net.IPAddress` object to the 35 | `Find` method on `Reader`. This method will return the result as type `T`. 36 | `T` may either be a generic collection or a class using the 37 | `[MaxMind.Db.Constructor]` attribute to declare which constructor to use 38 | during deserialization and the `[MaxMind.Db.Parameter("name")]` to map the 39 | database key `name` to a particular constructor parameter. 40 | 41 | We recommend reusing the `Reader` object rather than creating a new one for 42 | each lookup. The creation of this object is relatively expensive as it must 43 | read in metadata for the file. 44 | 45 | ## Example Decoding to a Dictionary ## 46 | 47 | ```csharp 48 | 49 | using (var reader = new Reader("GeoIP2-City.mmdb")) 50 | { 51 | var ip = IPAddress.Parse("24.24.24.24"); 52 | var data = reader.Find>(ip); 53 | ... 54 | } 55 | ``` 56 | 57 | ## Example Decoding to a Model Class ## 58 | 59 | ```csharp 60 | using MaxMind.Db; 61 | using System.Net; 62 | 63 | namespace MyCode 64 | { 65 | public class Asn 66 | { 67 | [Constructor] 68 | public AsnResponse( 69 | // The Parameter attribute tells the reader to map the database 70 | // key to the specified constructor parameter. 71 | [Parameter("autonomous_system_number")] long? autonomousSystemNumber, 72 | [Parameter("autonomous_system_organization")] string autonomousSystemOrganization, 73 | 74 | // The Inject attribute allows you to inject arbitrary values 75 | // when deserializing. 76 | [Inject("ip_address")] IPAddress ipAddress), 77 | 78 | // The Network attribute tells the reader to set the constructor 79 | // parameter to be the network associated with the record in the 80 | // database. 81 | [Network] Network network 82 | { 83 | ... 84 | } 85 | 86 | ... 87 | } 88 | 89 | 90 | public class Program 91 | { 92 | private static void Main(string[] args) 93 | { 94 | using (var reader = new Reader("GeoLite2-ASN.mmdb")) 95 | { 96 | var ip = IPAddress.Parse("24.24.24.24"); 97 | var injectables = new InjectableValues(); 98 | injectables.AddValue("ip_address", ip); 99 | var data = reader.Find(ip, injectables); 100 | ... 101 | } 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ## Multi-Threaded Use ## 108 | 109 | This API fully supports use in multi-threaded applications. In such 110 | applications, we suggest creating one `Reader` object and sharing that among 111 | threads. 112 | 113 | ## Format ## 114 | 115 | The MaxMind DB format is an open format for quickly mapping IP addresses to 116 | records. See 117 | [the specification](https://github.com/maxmind/MaxMind-DB/blob/main/MaxMind-DB-spec.md) 118 | for more information on the format. 119 | 120 | ## Bug Tracker ## 121 | 122 | Please report all issues with this code using the 123 | [GitHub issue tracker](https://github.com/maxmind/MaxMind-DB-Reader-dotnet/issues). 124 | 125 | If you are having an issue with a MaxMind database or service that is not 126 | specific to this reader, please 127 | [contact MaxMind support](http://www.maxmind.com/en/support). 128 | 129 | ## Contributing ## 130 | 131 | Patches and pull requests are encouraged. Please include unit tests whenever 132 | possible. 133 | 134 | ## Versioning ## 135 | 136 | The MaxMind DB Reader API uses [Semantic Versioning](http://semver.org/). 137 | 138 | ## Copyright and License ## 139 | 140 | This software is Copyright (c) 2013-2025 by MaxMind, Inc. 141 | 142 | This is free software, licensed under the Apache License, Version 2.0. 143 | -------------------------------------------------------------------------------- /dev-bin/release.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | $DebugPreference = 'Continue' 3 | 4 | $projectFile=(Get-Item "MaxMind.Db\MaxMind.Db.csproj").FullName 5 | $matches = (Get-Content -Encoding UTF8 releasenotes.md) ` | 6 | Select-String '(\d+\.\d+\.\d+(?:-\w+)?) \((\d{4}-\d{2}-\d{2})\)' ` 7 | 8 | $version = $matches.Matches.Groups[1].Value 9 | $date = $matches.Matches.Groups[2].Value 10 | 11 | if((Get-Date -format 'yyyy-MM-dd') -ne $date ) { 12 | Write-Error "$date is not today!" 13 | exit 1 14 | } 15 | 16 | $tag = "v$version" 17 | 18 | if (& git status --porcelain) { 19 | Write-Error '. is not clean' 20 | } 21 | 22 | (Get-Content -Encoding UTF8 $projectFile) ` 23 | -replace '(?<=)[^<]+', $version ` | 24 | Out-File -Encoding UTF8 $projectFile 25 | 26 | 27 | & git diff 28 | 29 | if ((Read-Host -Prompt 'Continue? (y/n)') -ne 'y') { 30 | Write-Error 'Aborting' 31 | } 32 | 33 | & git commit -m "Prepare for $version" -a 34 | 35 | Push-Location MaxMind.Db 36 | 37 | & dotnet restore 38 | & dotnet build -c Release 39 | & dotnet pack -c Release 40 | 41 | Pop-Location 42 | 43 | Push-Location MaxMind.Db.Test 44 | 45 | & dotnet restore 46 | & dotnet test -c Release 47 | 48 | Pop-Location 49 | 50 | if ((Read-Host -Prompt 'Should push? (y/n)') -ne 'y') { 51 | Write-Error 'Aborting' 52 | } 53 | 54 | & gh release create --target "$(git branch --show-current)" -t "$version" "$tag" 55 | & git push -u origin HEAD 56 | 57 | & nuget push "MaxMind.Db/bin/Release/MaxMind.Db.$version.nupkg" -Source https://www.nuget.org/api/v2/package 58 | -------------------------------------------------------------------------------- /releasenotes.md: -------------------------------------------------------------------------------- 1 | # Release Notes # 2 | 3 | ## 4.2.0 (2025-05-05) ## 4 | 5 | * .NET 6.0 and .NET 7.0 have been removed as targets as they have both 6 | reach their end of support from Microsoft. If you are using these versions, 7 | the .NET Standard 2.1 target should continue working for you. 8 | * .NET 9.0 has been added as a target. 9 | * We now use a mutex rather than a lock statement when opening the 10 | database. This is done to reduce the likelihood of a race condition 11 | when process are opening a single database when using 12 | `FileAccessMode.MemoryMappedGlobal`. 13 | * Performance improvements. Pull requests by Grégoire. GitHub #210, #211 14 | and #212. 15 | 16 | ## 4.1.0 (2023-12-05) ## 17 | 18 | * .NET 5.0 has been removed as a target as it has reach its end of life. 19 | However, if you are using .NET 5.0, the .NET Standard 2.1 target should 20 | continue working for you. 21 | * .NET 7.0 and .NET 8.0 have been added as a target. 22 | * Minor performance improvements. 23 | 24 | ## 4.0.0 (2022-02-03) ## 25 | 26 | * This library no longer targets .NET 4.6.1. 27 | * .NET 6.0 was added as a target. 28 | 29 | ## 3.0.0 (2020-11-16) ## 30 | 31 | * This library now requires .NET Framework 4.6.1 or greater or .NET Standard 32 | 2.0 or greater. 33 | * .NET 5.0 was added as a target framework. 34 | * When decoding strings in a memory-mapped file, the reader no longer 35 | allocates a temporary `byte[]`. This significantly improves performance but 36 | requires the use of `unsafe` code. 37 | * `FileAccessMode.MemoryMapped` now works if the database path specified is 38 | a symbolic link to the actual database. 39 | 40 | ## 2.6.1 (2019-12-06) ## 41 | 42 | * `netstandard2.1` was added as a target framework. 43 | 44 | ## 2.6.0 (2019-12-06) ## 45 | 46 | * This library has been updated to support the nullable reference types 47 | introduced in C# 8.0. 48 | 49 | ## 2.5.0 (2019-11-21) ## 50 | 51 | * A `FindAll` method was added to the `MaxMind.Db.Reader` class. This returns 52 | an enumerator that enumerates over the MaxMind DB database. Pull request by 53 | Jeff Johnson. GitHub #47. 54 | * A `CreateAsync` static method was added to asynchronously created a 55 | `MaxMind.Db.Reader` object from database file. Pull request by David 56 | Warner. GitHub #44. 57 | * When deserializing to a class, you may now instruct the reader to set a 58 | constructor parameter to be the network associated with the record. To do 59 | this, use the `Network` attribute. The parameter must be of type 60 | `MaxMind.Db.Network`. GitHub #56. 61 | * As part of #44, the optimization to reduce allocations when loading from 62 | a seekable stream was removed. The optimization could cause poor 63 | performance in some instances and its behavior with regard to the stream 64 | position differed from the documented behavior. 65 | 66 | ## 2.4.0 (2018-04-11) ## 67 | 68 | * Added `FileAccessMode.MemoryMappedGlobal`. When used, this will open the file 69 | in global memory map mode. This requires the "create global objects" right. 70 | Pull request by David Warner. GitHub #43. 71 | 72 | ## 2.3.0 (2017-10-27) ## 73 | 74 | * Reduce the number of allocations when creating a `MaxMind.Db.Reader` from 75 | a seekable stream. Pull request by Maarten Balliauw. GitHub #38. 76 | * A `netstandard2.0` target was added to eliminate additional dependencies 77 | required by the `netstandard1.4` target. Pull request by Adeel Mujahid. 78 | GitHub #39. 79 | * As part of the above work, the separate Mono build files were dropped. As 80 | of Mono 5.0.0, `msbuild` is supported. 81 | 82 | ## 2.2.0 (2017-05-08) ## 83 | 84 | * Switch to the updated MSBuild .NET Core build system. Pull request by Adeel 85 | Mujahid. GitHub #35. 86 | * Move tests from NUnit to xUnit.net. GitHub #35. 87 | 88 | ## 2.1.3 (2016-11-22) ## 89 | 90 | * Update for .NET Core 1.1. 91 | 92 | ## 2.1.2 (2016-08-08) ## 93 | 94 | * Re-build of 2.1.1 to fix signing issue. No code changes. 95 | 96 | ## 2.1.1 (2016-08-01) ## 97 | 98 | * First non-beta release with .NET Core support. 99 | * The tests now use the .NET Core NUnit runner. 100 | 101 | ## 2.1.1-beta1 (2016-06-01) ## 102 | 103 | * Re-release of `2.1.0-beta4` to skip bad `2.1.0` release on NuGet. 104 | 105 | ## 2.1.0-beta4 (2016-06-01) ## 106 | 107 | * Update for .NET Core RC2. Pull request by Adeel Mujahid. GitHub #28. 108 | 109 | ## 2.1.0-beta3 (2016-05-12) ## 110 | 111 | * The assemblies are now signed again. 112 | 113 | ## 2.1.0-beta2 (2016-05-10) ## 114 | 115 | * Remove unnecessary Newtonsoft.Json dependency. 116 | 117 | ## 2.1.0-beta1 (2016-05-10) ## 118 | 119 | * .NET Core support. Switched to `dotnet/cli` for building. Pull request by 120 | Adeel Mujahid. GitHub #26 & #27. 121 | 122 | ## 2.0.0 (2016-04-15) ## 123 | 124 | * No changes since 2.0.0-beta3. 125 | 126 | ## 2.0.0-beta3 (2016-03-24) ## 127 | 128 | * The Reader class now has an overloaded method that takes an integer out 129 | parameter. This parameter is set the the network prefix length for the 130 | record containing the IP address in the database. Pull request by Ed Dorsey. 131 | GitHub #22 & #23. 132 | 133 | ## 2.0.0-beta2 (2016-01-29) ## 134 | 135 | * Minor refactoring. No substantial changes since beta1. 136 | 137 | ## 2.0.0-beta1 (2016-01-18) ## 138 | 139 | * Significant API changes. The `Find` method now takes a type parameter 140 | specifying the type to deserialize to. Note that `JToken` is _not_ supported 141 | for this. You can either deserialize to an arbitrary collection or to 142 | model classes that use the `MaxMind.Db.Constructor` and 143 | `MaxMind.Db.Parameter` attributes to identify the constructors and 144 | parameters to deserialize to. 145 | * The API now significantly faster. 146 | 147 | ## 1.2.0 (2015-09-23) ## 148 | 149 | * Production release. No changes. 150 | 151 | ## 1.2.0-beta1 (2015-09-08) ## 152 | 153 | * The assembly now has a strong name. 154 | * An internal use of `JTokenReader` is now disposed of after use. 155 | * A null stream passed to the `Reader(Stream)` constructor will now throw an 156 | `ArgumentNullException`. 157 | 158 | ## 1.1.0 (2015-07-21) ## 159 | 160 | * Minor code cleanup. 161 | 162 | ## 1.1.0-beta1 (2015-06-30) ## 163 | 164 | * A `IOException: Not enough storage is available to process this command` 165 | when using the memory-mapped mode with 32-bit builds or many threads was 166 | fixed. Closes GH #5. 167 | * Use of streams was replaced with direct access for both the memory-mapped 168 | file mode and the memory mode. This should increase performance in most 169 | cases. 170 | * When using memory-mapped mode, the file is now opened with 171 | `FileShare.Delete`, allowing other processes to delete or replace the 172 | database when it is in use. The reader object will continue using the old 173 | database. 174 | * The Json.NET dependency was updated to 7.0.1. 175 | 176 | ## 1.0.1 (2015-05-19) ## 177 | 178 | * Improved the exception thrown when the constructor for `Reader` is called 179 | with an empty stream. 180 | * Updated Newtonsoft.Json dependency to 6.0.8. 181 | * Minor code cleanup. 182 | 183 | ## 1.0.0 (2014-09-29) ## 184 | 185 | * First production release. 186 | 187 | ## 0.3.0 (2014-09-24) ## 188 | 189 | * Added public `Metadata` property on `Reader`. 190 | 191 | ## 0.2.3 (2014-04-09) ## 192 | 193 | * The database is now loadable from a `Stream`. 194 | 195 | ## 0.2.2 (2013-12-24) ## 196 | 197 | * Fixed a bug that occurred when using the memory-mode in a multi-threaded 198 | application. When using a single `Reader` from multiple threads in memory- 199 | mode, the internal state of the object could become corrupt if you replaced 200 | the MaxMind database file on disk with another database file. 201 | 202 | ## 0.2.1 (2013-11-15) ## 203 | 204 | * Fixed bug that caused an exception to be thrown when two threads created a 205 | `MaxMind.Db.Reader` object at the same time. This was fixed using a 206 | synchronization lock around the code that opens/creates the memory map. We 207 | recommend that you share a `Reader` object across threads rather than 208 | create one for each thread or lookup. 209 | 210 | ## 0.2.0 (2013-10-25) ## 211 | 212 | * Changed namespace from `MaxMind.DB` to `MaxMind.Db` to conform with 213 | Microsoft's recommendations. 214 | * Replaced custom `BigInteger` implementation with 215 | `System.Numerics.BigInteger`. 216 | * Made `Metadata` an internal property on `Reader`. 217 | 218 | ## 0.1.0 (2013-10-22) ## 219 | 220 | * Initial release 221 | --------------------------------------------------------------------------------