Full Code of lionsoul2014/ip2region for AI

master cd40e3a1d532
302 files
155.6 MB
317.8k tokens
Showing preview only (1,135K chars total). The displayed content is truncated. Use the JSON API for full output.
Repository: lionsoul2014/ip2region
Branch: master
Commit: cd40e3a1d532
Files: 302
Total size: 155.6 MB

Directory structure:
gitextract_jvnkjpne/

├── .gitattributes
├── .gitignore
├── LICENSE.md
├── README.md
├── README_zh.md
├── binding/
│   ├── c/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── main.c
│   │   ├── test_util.c
│   │   ├── xdb_api.h
│   │   ├── xdb_searcher.c
│   │   └── xdb_util.c
│   ├── cangjie/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── cjpm.toml
│   │   ├── example/
│   │   │   ├── cjpm.toml
│   │   │   └── src/
│   │   │       └── main.cj
│   │   └── src/
│   │       ├── main.cj
│   │       ├── service/
│   │       │   ├── config.cj
│   │       │   ├── ip2region.cj
│   │       │   └── searcher_pool.cj
│   │       ├── tests/
│   │       │   ├── service/
│   │       │   │   ├── config_test.cj
│   │       │   │   ├── ip2region_test.cj
│   │       │   │   └── searcher_pool_test.cj
│   │       │   ├── tests.cj
│   │       │   └── xdb/
│   │       │       ├── header_test.cj
│   │       │       ├── searcher_test.cj
│   │       │       └── util_test.cj
│   │       └── xdb/
│   │           ├── header.cj
│   │           ├── searcher.cj
│   │           ├── util.cj
│   │           └── version.cj
│   ├── cpp/
│   │   ├── .gitignore
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── src/
│   │   │   ├── base.cc
│   │   │   ├── base.h
│   │   │   ├── bench.cc
│   │   │   ├── bench.h
│   │   │   ├── edit.cc
│   │   │   ├── edit.h
│   │   │   ├── header.cc
│   │   │   ├── header.h
│   │   │   ├── ip.cc
│   │   │   ├── ip.h
│   │   │   ├── make.cc
│   │   │   ├── make.h
│   │   │   ├── search.cc
│   │   │   └── search.h
│   │   └── test/
│   │       ├── bench.cc
│   │       ├── edit_v4.cc
│   │       ├── edit_v6.cc
│   │       ├── header.cc
│   │       ├── make.cc
│   │       └── search.cc
│   ├── csharp/
│   │   ├── .editorconfig
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── IP2Region.Net/
│   │   │   ├── Abstractions/
│   │   │   │   └── ISearcher.cs
│   │   │   ├── Extensions/
│   │   │   │   └── ServiceCollectionExtensions.cs
│   │   │   ├── IP2Region.Net.csproj
│   │   │   ├── Internal/
│   │   │   │   ├── CacheStrategyFactory.cs
│   │   │   │   ├── ContentCacheStrategy.cs
│   │   │   │   ├── FileCacheStrategy.cs
│   │   │   │   ├── ICacheStrategy.cs
│   │   │   │   └── VectorIndexCacheStrategy.cs
│   │   │   └── XDB/
│   │   │       ├── CachePolicy.cs
│   │   │       ├── Searcher.cs
│   │   │       ├── Util.cs
│   │   │       └── XdbVersion.cs
│   │   ├── IP2Region.Net.BenchMark/
│   │   │   ├── Benmarks.cs
│   │   │   ├── IP2Region.Net.BenchMark.csproj
│   │   │   └── Program.cs
│   │   ├── IP2Region.Net.Test/
│   │   │   ├── IP2Region.Net.Test.csproj
│   │   │   ├── SearcherTest.cs
│   │   │   ├── UtilTest.cs
│   │   │   └── XdbTest.cs
│   │   ├── IP2Region.Net.slnx
│   │   └── README.md
│   ├── erlang/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── benchmarks/
│   │   │   └── xdb-benchmark.sh
│   │   ├── include/
│   │   │   └── ip2region.hrl
│   │   ├── priv/
│   │   │   └── dummy
│   │   ├── rebar.config
│   │   ├── src/
│   │   │   ├── ip2region.app.src
│   │   │   ├── ip2region_app.erl
│   │   │   ├── ip2region_sup.erl
│   │   │   ├── ip2region_util.erl
│   │   │   ├── ip2region_worker.erl
│   │   │   ├── ip2region_xdb.erl
│   │   │   ├── xdb.erl
│   │   │   └── xdb_benchmark.erl
│   │   └── test/
│   │       ├── ip2region_sup_test.erl
│   │       ├── ip2region_test_util.erl
│   │       ├── ip2region_util_test.erl
│   │       ├── ip2region_worker_test.erl
│   │       ├── ip2region_xdb_test.erl
│   │       └── xdb_test.erl
│   ├── golang/
│   │   ├── LICENSE.md
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── main.go
│   │   ├── service/
│   │   │   ├── config.go
│   │   │   ├── config_test.go
│   │   │   ├── ip2region.go
│   │   │   ├── ip2region_test.go
│   │   │   ├── searcher_pool.go
│   │   │   └── searcher_pool_test.go
│   │   └── xdb/
│   │       ├── header.go
│   │       ├── searcher.go
│   │       ├── util.go
│   │       ├── util_test.go
│   │       └── version.go
│   ├── java/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── pom.xml
│   │   └── src/
│   │       ├── main/
│   │       │   └── java/
│   │       │       └── org/
│   │       │           └── lionsoul/
│   │       │               └── ip2region/
│   │       │                   ├── SearcherTest.java
│   │       │                   ├── service/
│   │       │                   │   ├── Config.java
│   │       │                   │   ├── ConfigBuilder.java
│   │       │                   │   ├── InvalidConfigException.java
│   │       │                   │   ├── Ip2Region.java
│   │       │                   │   └── SearcherPool.java
│   │       │                   └── xdb/
│   │       │                       ├── Header.java
│   │       │                       ├── IPv4.java
│   │       │                       ├── IPv6.java
│   │       │                       ├── InetAddressException.java
│   │       │                       ├── LittleEndian.java
│   │       │                       ├── Log.java
│   │       │                       ├── LongByteArray.java
│   │       │                       ├── Searcher.java
│   │       │                       ├── Util.java
│   │       │                       ├── Version.java
│   │       │                       └── XdbException.java
│   │       └── test/
│   │           └── java/
│   │               └── org/
│   │                   └── lionsoul/
│   │                       └── ip2region/
│   │                           ├── service/
│   │                           │   ├── ConfigTest.java
│   │                           │   ├── Ip2RegionTest.java
│   │                           │   └── SearcherPoolTest.java
│   │                           └── xdb/
│   │                               ├── BufferTest.java
│   │                               ├── IPv4Test.java
│   │                               ├── LittleEndianTest.java
│   │                               ├── UtilTest.java
│   │                               └── VersionTest.java
│   ├── javascript/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── index.d.ts
│   │   ├── index.js
│   │   ├── package.json
│   │   ├── searcher.js
│   │   ├── tests/
│   │   │   ├── bench.app.js
│   │   │   ├── search.app.js
│   │   │   ├── searcher.test.js
│   │   │   └── util.test.js
│   │   ├── tsconfig.json
│   │   └── util.js
│   ├── lua/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── bench_test.lua
│   │   ├── search_test.lua
│   │   ├── util_test.lua
│   │   └── xdb_searcher.lua
│   ├── lua_c/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── bench_test.lua
│   │   ├── search_test.lua
│   │   ├── util_test.lua
│   │   └── xdb_searcher.c
│   ├── nginx/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── config
│   │   ├── src/
│   │   │   ├── ngx_http_ip2region_module.c
│   │   │   └── ngx_http_ip2region_module.h
│   │   └── t/
│   │       └── http_ip2region.t
│   ├── nodejs/
│   │   └── README.md
│   ├── php/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── batch_test.php
│   │   ├── bench_test.php
│   │   ├── search_test.php
│   │   └── xdb/
│   │       ├── Searcher.class.php
│   │       └── util_test.php
│   ├── python/
│   │   ├── .gitignore
│   │   ├── LICENSE
│   │   ├── MANIFEST.in
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── bench_test.py
│   │   ├── ip2region/
│   │   │   ├── __init__.py
│   │   │   ├── searcher.py
│   │   │   └── util.py
│   │   ├── search_test.py
│   │   ├── setup.py
│   │   └── util_test.py
│   ├── rust/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── example/
│   │   │   ├── Cargo.toml
│   │   │   └── src/
│   │   │       ├── cmd.rs
│   │   │       └── main.rs
│   │   └── ip2region/
│   │       ├── Cargo.toml
│   │       ├── benches/
│   │       │   └── search.rs
│   │       └── src/
│   │           ├── error.rs
│   │           ├── ip_value.rs
│   │           ├── lib.rs
│   │           └── searcher.rs
│   └── typescript/
│       └── README.md
├── data/
│   ├── ip2region_v4.xdb
│   ├── ip2region_v6.xdb
│   ├── ipv4_source.txt
│   ├── ipv6_source.txt
│   └── sample/
│       ├── github-issue-196.fix
│       ├── github-issue-200.fix
│       ├── github-issue-243.fix
│       ├── github-issue-287.bug
│       ├── ip.test.txt
│       ├── segments.tests
│       └── segments.tests.mixed
└── maker/
    ├── c/
    │   └── ReadMe.md
    ├── cpp/
    │   └── README.md
    ├── csharp/
    │   ├── .gitignore
    │   ├── IP2RegionMaker/
    │   │   ├── IP2RegionMaker.csproj
    │   │   ├── Program.cs
    │   │   ├── Properties/
    │   │   │   └── PublishProfiles/
    │   │   │       └── FolderProfile.pubxml
    │   │   └── XDB/
    │   │       ├── IndexPolicy.cs
    │   │       ├── Maker.cs
    │   │       ├── Segment.cs
    │   │       └── Util.cs
    │   ├── IP2RegionMaker.Test/
    │   │   ├── IP2RegionMaker.Test.csproj
    │   │   ├── Usings.cs
    │   │   └── UtilTest.cs
    │   ├── README.md
    │   └── README_zh.md
    ├── golang/
    │   ├── Dockerfile
    │   ├── LICENSE.md
    │   ├── Makefile
    │   ├── README.md
    │   ├── README_zh.md
    │   ├── cmd/
    │   │   ├── bench.go
    │   │   ├── edit.go
    │   │   ├── generate.go
    │   │   ├── process.go
    │   │   ├── search.go
    │   │   └── util.go
    │   ├── go.mod
    │   ├── go.sum
    │   ├── main.go
    │   ├── make.bat
    │   └── xdb/
    │       ├── editor.go
    │       ├── index.go
    │       ├── maker.go
    │       ├── processor.go
    │       ├── region.go
    │       ├── searcher.go
    │       ├── segment.go
    │       ├── util.go
    │       ├── util_test.go
    │       └── version.go
    ├── java/
    │   ├── README.md
    │   ├── README_zh.md
    │   ├── pom.xml
    │   └── src/
    │       ├── main/
    │       │   └── java/
    │       │       └── org/
    │       │           └── lionsoul/
    │       │               └── ip2region/
    │       │                   ├── MakerApp.java
    │       │                   └── xdb/
    │       │                       ├── IPv4.java
    │       │                       ├── IPv6.java
    │       │                       ├── IndexPolicy.java
    │       │                       ├── InvalidInetAddressException.java
    │       │                       ├── LittleEndian.java
    │       │                       ├── Log.java
    │       │                       ├── Maker.java
    │       │                       ├── Segment.java
    │       │                       ├── Util.java
    │       │                       └── Version.java
    │       └── test/
    │           └── java/
    │               └── org/
    │                   └── lionsoul/
    │                       └── ip2region/
    │                           └── xdb/
    │                               ├── IndexPolicyTest.java
    │                               ├── LittleEndianTest.java
    │                               ├── MakerTest.java
    │                               ├── SegmentTest.java
    │                               ├── UtilTest.java
    │                               └── VersionTest.java
    ├── python/
    │   ├── README.md
    │   ├── README_zh.md
    │   ├── main.py
    │   └── xdb/
    │       ├── __init__.py
    │       ├── index.py
    │       ├── maker.py
    │       ├── segment.py
    │       └── util.py
    └── rust/
        ├── README.md
        ├── README_zh.md
        └── maker/
            ├── Cargo.toml
            └── src/
                ├── command.rs
                ├── error.rs
                ├── header.rs
                ├── lib.rs
                ├── main.rs
                ├── maker.rs
                └── segment.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================


================================================
FILE: .gitignore
================================================
*.class
*.out
*.o
*.pyc
*~
*.log
*.la
*.so
*.iml
META-INF/
.DS_Store

# Binary Files #
*.jar
!dbMaker-*.jar

# ignore all xdb except the one in ./data/
*.xdb
!/data/*.xdb

# Package Files #
.settings/
.classpath
.project

# vim swp file #
*.swp
.idea
.vscode

# binding
/binding/java/classes/
/binding/java/doc/
/binding/java/target/
/binding/java/*.jar

# python
/**/__pycache__

# clang
/binding/c/xdb_searcher
/binding/c/test_util
/binding/c/cmake-build-debug

# lua/luc_c
/binding/lua_c/cmake-build-debug

# golang
/binding/golang/searcher
/binding/golang/xdb_searcher
/binding/golang/golang

# rust
Cargo.lock
target


# VS ignore cases
/**/*.sln
/binding/c#/**/.vs/
/binding/c#/**/packages
/binding/c#/**/bin
/binding/c#/**/obj

# Nodejs
/binding/nodejs/tests/unitTests/__snapshots__
/binding/nodejs/coverage
/binding/nodejs/node_modules
/binding/nodejs/.nyc_output
/binging/nodejs/package-lock.json

# Javascript
/binding/javascript/tests/unitTests/__snapshots__
/binding/javascript/coverage
/binding/javascript/node_modules
/binding/javascript/.nyc_output
/binding/javascript/package-lock.json

# maker
## golang
/maker/golang/dbmaker
/maker/golang/xdb_maker
/maker/golang/golang

#erlang
/binding/erlang/_build
/binding/erlang/doc

#vscode
.vscode
build

# git worktrees
.worktrees/


================================================
FILE: LICENSE.md
================================================
ip2region is dual-licensed under the Apache License 2.0 and the MIT License.

You may use, distribute, and modify this software under either license,
at your option. The full text of both licenses appears below.

SPDX-License-Identifier: Apache-2.0 OR MIT

==============================================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

==========================================================================
The following license applies to the ip2region library
--------------------------------------------------------------------------
Copyright (c) 2015 Lionsoul<chenxin619315@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)

# ip2region

[ip2region](https://ip2region.net) - is an offline IP address localization library and IP localization data management framework. It supports both `IPv4` and `IPv6` with query efficiency at the 10-microsecond level. It provides `xdb` data generation and query client implementations for many mainstream programming languages.


# Features

### 1. Offline Localization Library

The project itself provides raw data for both IPv4 (`data/ipv4_source.txt`) and IPv6 (`data/ipv6_source.txt`), along with corresponding xdb files (`data/ip2region_v4.xdb` and `data/ip2region_v6.xdb`) to achieve city-level query localization. The field format is: `Country|Province|City|ISP|iso-alpha2-code`. Localization information for China is entirely in Chinese, while regional information for non-China areas is entirely in English.

### 2. Data Management Framework

`xdb` supports hundreds of millions of IP data segment rows. Region information supports full customization. The region information of the built-in data is fixed in the format: `Country|Province|City|ISP|iso-alpha2-Code`. You can append data for specific business needs to the region, such as: GPS information/International Standard Regional Codes/Zip codes, etc. In other words, you can fully use ip2region to manage your own IP localization data.

### 3. Data Deduplication and Compression

The `xdb` format generation program automatically processes the input raw data, checks and completes the merging of adjacent IP segments, and performs deduplication and compression of identical regional information.

### 4. High-Speed Query Response

Even for queries based entirely on the `xdb` file, the single query response time is at the 10-microsecond level. Memory-accelerated queries can be enabled through the following two methods:

1. `vIndex` Index Caching: Uses a fixed `512KiB` of memory to cache vector index data, reducing one disk IO operation and maintaining average query efficiency within 100 microseconds.
2. Entire `xdb` File Caching: Loads the entire `xdb` file into memory. Memory usage is equal to the `xdb` file size. There is no disk IO operation, maintaining 10-microsecond level query efficiency.

### 5. Unified Query Interface

`xdb` provides version-compatible query implementations. A unified API can simultaneously provide queries for both IPv4 and IPv6 data and return unified data.


# `xdb` Query

For API introductions, usage documentation, and test programs, please refer to the README introduction under the corresponding `searcher` query client. All query binding implementations are as follows:

| Language | Description | IPv4 Support | IPv6 Support |
| --- | --- | --- | --- |
| [Golang](binding/golang/README.md)         | golang query client          | :white_check_mark: | :white_check_mark: |
| [PHP](binding/php/README.md)               | php query client             | :white_check_mark: | :white_check_mark: |
| [Java](binding/java/README.md)             | java query client            | :white_check_mark: | :white_check_mark: |
| [C](binding/c/README.md)                   | C[std=c99] query client      | :white_check_mark: | :white_check_mark: |
| [Lua_c](binding/lua_c/README.md)           | lua c extension query client | :white_check_mark: | :white_check_mark: |
| [Lua](binding/lua/README.md)               | lua query client             | :white_check_mark: | :white_check_mark: |
| [Rust](binding/rust/README.md)             | rust query client            | :white_check_mark: | :white_check_mark: |
| [Python](binding/python/README.md)         | python query client          | :white_check_mark: | :white_check_mark: |
| [Javascript](binding/javascript/README.md) | javascript query client      | :white_check_mark: | :white_check_mark: |
| [Csharp](binding/csharp)                   | csharp query client          | :white_check_mark: | :white_check_mark: |
| [Erlang](binding/erlang/README.md)         | erlang query client          | :white_check_mark: | :white_check_mark: | 
| [Nginx](binding/nginx)                     | nginx extension query client | :white_check_mark: | :white_check_mark: |
| [C++](binding/cpp/README.md)               | C++ query client             | :white_check_mark: | :white_check_mark: |
| [Cangjie](binding/cangjie/README.md)       | Cangjie query client         | :white_check_mark: | :white_check_mark: |

The following toolchain implementations are contributed by community developers via third-party repositories:

| Language | Description |
| --- | --- |
| [ip2region-composer](https://github.com/zoujingli/ip2region)    | php composer management client       |
| [ip2region-ts](https://github.com/Steven-Qiang/ts-ip2region2)   | node.js addon management client      |
| [ruby-ip2region](https://github.com/jicheng1014/ruby-ip2region) | ruby xdb query client implementation |
| [Ip2regionTool](https://github.com/orestonce/Ip2regionTool)     | ip2region data conversion tool       |


# `xdb` Generation

For API introductions, usage documentation, and test programs, please refer to the README documents under the following `maker` generation programs:

| Language | Description | IPv4 Support | IPv6 Support |
| --- | --- | --- | --- |
| [Golang](maker/golang/README.md) | golang xdb generation program | :white_check_mark: | :white_check_mark: |
| [Java](maker/java/README.md)     | java xdb generation program   | :white_check_mark: | :white_check_mark: |
| [Python](maker/python/README.md) | python xdb generation program | :white_check_mark: | :x:                |
| [Csharp](maker/csharp/README.md) | csharp xdb generation program | :white_check_mark: | :x:                |
| [Rust](maker/rust/README.md)     | rust xdb generation program   | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp)                 | C++ xdb generation program    | :white_check_mark: | :white_check_mark: |


# `xdb` Update

The core of the ip2region project lies in **researching the design and implementation of IP data storage and fast querying**. The raw data `./data/ipv4_source.txt` and `./data/ipv6_source.txt` included in the project are updated irregularly. For scenarios with high requirements for data accuracy and update frequency, it is recommended to purchase commercial offline data from the [Ip2Region Community](https://ip2region.net/products/offline) or third-party vendors. You can try to update the data yourself using the following methods:

### Manual Editing and Updating

You can modify the data yourself based on the raw IP data provided by ip2region in `./data/ipv4_source.txt` and `./data/ipv6_source.txt` using the editing tools provided by ip2region. Currently, the data sources include:

1. Data provided by the ip2region community (please refer to the official account at the bottom for community notifications)
2. Project Issues tagged with `[Data_Updates]`
3. Other custom data: e.g., data provided by customers, data obtained through GPS and WIFI positioning, or legal and compliant data from other platforms.

For instructions on using the raw IP data editing tools, please refer to the README documents under the following `maker` generation programs:

| Language | Description | IPv4 Support | IPv6 Support |
| --- | --- | --- | --- |
| [Golang](maker/golang/README.md#xdb-data-editing) | golang IP raw data editor | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp/README.md)                        | C++ IP raw data editor    | :white_check_mark: | :white_check_mark: |

### Detection Automatic Update

If you want to update data via your own API or data source, you can refer to the update algorithm based on the "Detection Algorithm" shared in the following videos to write your own update program:

1. [Data Update Implementation Video Sharing - part1](https://www.bilibili.com/video/BV1934y1E7Q5/)
2. [Data Update Implementation Video Sharing - part2](https://www.bilibili.com/video/BV1pF411j7Aw/)


# Official Community

The Ip2Region official community was officially launched on `2025/06/12`. On one hand, it provides stable [commercial offline data](https://ip2region.net/products/offline) services. On the other hand, it facilitates the strengthening of the IP toolchain and data services outside the core code, such as [usage documentation](https://ip2region.net/doc/), [query testing](https://ip2region.net/search/demo), and data correction. For more information and services regarding the community, please visit the [Ip2Region Official Community](https://ip2region.net/).


# Related Remarks

### 1. xdb Technical Documents:

1. xdb Data Structure Analysis: ["ip2region xdb - Data Structure Description"](https://ip2region.net/doc/xdb/structure)
2. xdb Query Process Analysis: ["ip2region xdb - Query Process Description"](https://ip2region.net/doc/xdb/search)
3. xdb Generation Process Analysis: ["ip2region xdb - Generation Process Description"](https://ip2region.net/doc/xdb/generate)
4. xdb File Generation Tutorial: ["ip2region xdb - File Generation Tutorial"](https://ip2region.net/doc/data/xdb_make)
5. xdb Concurrent Safety Query: ["ip2region xdb - Concurrent Safety Query"](https://ip2region.net/doc/xdb/concurrent)
6. xdb Data Update Method: ["ip2region Data Update and Use of xdb Data Editor"](https://mp.weixin.qq.com/s/cZH5qIn4E5rQFy6N32RCzA)

### 3. Technical Information Blogs

1. WeChat Official Account - lionsoul-org, the author's active technical sharing channel
2. [Ip2Region Official Community](https://ip2region.net)


================================================
FILE: README_zh.md
================================================
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)

# ip2region

[ip2region](https://ip2region.net) - 是一个离线IP地址定位库和IP定位数据管理框架,同时支持 `IPv4` 和 `IPv6` ,10微秒级别的查询效率,提供了众多主流编程语言的 `xdb` 数据生成和查询客户端实现。


# 项目特性

### 1、离线定位库

项目本身同时了提供了一份 IPv4 (`data/ipv4_source.txt`) 和 IPv6 (`data/ipv6_source.txt`) 的原始数据和对应的 xdb 文件(`data/ip2region_v4.xdb` 和 `data/ip2region_v6.xdb`) 用于实现精确到城市的查询定位功能,字段格式为:`国家|省份|城市|ISP|iso-alpha2-code(国家两字母简称)`,中国的定位信息全部为中文,非中国地区的地域信息全部为英文。

### 2、数据管理框架

`xdb` 支持亿级别的 IP 数据段行数,region 信息支持完全自定义,自带数据的 region 信息固定了格式为:`国家|省份|城市|ISP|iso-alpha2-Code`,你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。

### 3、数据去重和压缩

`xdb` 格式生成程序会自动处理输入的原始数据,检查并且完成相连 IP 段的的合并以及相同地域信息的去重和压缩。

### 4、极速查询响应

即使是完全基于 `xdb` 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:

1. `vIndex` 索引缓存 :使用固定的 `512KiB` 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在100微秒之内。
2. `xdb` 整个文件缓存:将整个 `xdb` 文件全部加载到内存,内存占用等同于 `xdb` 文件大小,无磁盘 IO 操作,保持10微秒级别的查询效率。

### 5、统一的查询接口

`xdb` 提供了版本兼容的查询实现,一个统一的 API 可以同时提供对 IPv4 和 IPv6 数据的查询并且返回统一的数据。


# `xdb` 查询

API 介绍,使用文档和测试程序请参考对应 `searcher` 查询客户端下的 README 介绍,全部查询 binding 实现情况如下:
| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 |
| --- | --- | --- | --- |
| [Golang](binding/golang/README_zh.md)         | golang 查询客户端       | :white_check_mark: | :white_check_mark: |
| [PHP](binding/php/README_zh.md)               | php 查询客户端          | :white_check_mark: | :white_check_mark: |
| [Java](binding/java/README_zh.md)             | java 查询客户端         | :white_check_mark: | :white_check_mark: |
| [C](binding/c/README_zh.md)                   | C[std=c99] 查询客户端   | :white_check_mark: | :white_check_mark: |
| [Lua_c](binding/lua_c/README_zh.md)           | lua c 扩展查询客户端    | :white_check_mark: | :white_check_mark: |
| [Lua](binding/lua/README_zh.md)               | lua 查询客户端          | :white_check_mark: | :white_check_mark: |
| [Rust](binding/rust/README_zh.md)             | rust 查询客户端         | :white_check_mark: | :white_check_mark: |
| [Python](binding/python/README_zh.md)         | python 查询客户端       | :white_check_mark: | :white_check_mark: |
| [Javascript](binding/javascript/README_zh.md) | javascript 查询客户端   | :white_check_mark: | :white_check_mark: |
| [Csharp](binding/csharp)                      | csharp 查询客户端       | :white_check_mark: | :white_check_mark: |
| [Erlang](binding/erlang/README_zh.md)         | erlang 查询客户端       | :white_check_mark: | :white_check_mark: |
| [Nginx](binding/nginx)                        | nginx 扩展查询客户端    | :white_check_mark: | :white_check_mark: |
| [C++](binding/cpp/README_zh.md)               | C++ 查询客户端          | :white_check_mark: | :white_check_mark: |
| [Cangjie](binding/cangjie/README.md)          | 仓颉 查询客户端         | :white_check_mark: | :white_check_mark: |


以下工具链实现由社区开发者通过第三方仓库贡献:
| 编程语言 | 描述 |
| --- | --- |
| [ip2region-composer](https://github.com/zoujingli/ip2region)    | php composer 管理客户端 |
| [ip2region-ts](https://github.com/Steven-Qiang/ts-ip2region2)   | node.js addon 管理客户端|
| [ruby-ip2region](https://github.com/jicheng1014/ruby-ip2region) | ruby xdb 查询客户端实现 |
| [Ip2regionTool](https://github.com/orestonce/Ip2regionTool)     | ip2region 数据转换工具  |


# `xdb` 生成

API 介绍,使用文档和测试程序请参考如下 `maker` 生成程序下的 README 文档:

| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 |
| --- | --- | --- | --- |
| [Golang](maker/golang/README_zh.md) | golang xdb 生成程序  | :white_check_mark: | :white_check_mark: |
| [Java](maker/java/README_zh.md)     | java xdb 生成程序    | :white_check_mark: | :white_check_mark: |
| [Python](maker/python/README_zh.md) | python xdb 生成程序  | :white_check_mark: | :x:                |
| [Csharp](maker/csharp/README_zh.md) | csharp xdb 生成程序  | :white_check_mark: | :x:                |
| [Rust](maker/rust/README_zh.md)     | rust xdb 生成程序    | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp)                    | C++ xdb 生成程序     | :white_check_mark: | :white_check_mark: |


# `xdb` 更新

ip2region 项目的核心在于 <b>研究 IP 数据的存储和快速查询的设计和实现</b>, 项目自带的 `./data/ipv4_source.txt` 和 `./data/ipv6_source.txt` 原始数据不定期更新,对于数据精度和更新频率要求很高的使用场景建议到 [Ip2Region社区](https://ip2region.net/products/offline) 或者第三方购买商用离线数据,你可以使用如下几种方式来尝试自己更新数据:

### 手动编辑更新
你可以基于 ip2region 自带的 `./data/ipv4_source.txt` 和 `./data/ipv6_source.txt` 原始 IP 数据用 ip2region 提供的编辑工具来自己修改,目前数据源有如下几种方式:
1. ip2region 社区提供的数据(请参考地底部的公众号关注社区通知)
2. ip2region Github/Gitee 中带有 `[数据源补充]` 标签的 Issue
3. 其他自定义数据:例如客户提供的数据,或者通过 GPS 和 WIFI 定位得到的数据,或者来自其他平台的合法合规的数据

原始 IP 数据编辑工具使用方法请参考如下的 `maker` 生成程序下的 README 文档:
| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 |
| --- | --- | --- | --- |
| [Golang](maker/golang/README_zh.md#xdb-数据编辑) | golang IP 原始数据编辑器 | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp)                                 | C++ IP 原始数据编辑器    | :white_check_mark: | :white_check_mark: |


### 检测自动更新
如果你想通过你自己的 API 或数据源来更新数据,你可以参考以下视频分享的 `基于检测算法` 的更新算法来自己编写一个更新程序:
1. [数据更新实现视频分享 - part1](https://www.bilibili.com/video/BV1934y1E7Q5/)
2. [数据更新实现视频分享 - part2](https://www.bilibili.com/video/BV1pF411j7Aw/)


# 官方社区
Ip2Region 官方社区正式上线于 `2025/06/12` 日,一方面提供了稳定的 [商用离线数据](https://ip2region.net/products/offline) 服务,另一方面便于在核心代码外强化 IP 工具链和数据服务,例如 [使用文档](https://ip2region.net/doc/),[查询测试](https://ip2region.net/search/demo),数据纠错等,更多关于社区的信息和服务请访问 [Ip2Region 官方社区](https://ip2region.net/)。


# 相关备注

### 1、xdb 技术文档:
1. xdb 数据结构分析:[“ip2region xdb-数据结构描述“](https://ip2region.net/doc/xdb/structure)
2. xdb 查询过程分析:[“ip2region xdb-查询过程描述”](https://ip2region.net/doc/xdb/search)
3. xdb 生成过程分析:[“ip2region xdb-生成过程描述”](https://ip2region.net/doc/xdb/generate)
4. xdb 文件生成教程:[“ip2region xdb-文件生成教程”](https://ip2region.net/doc/data/xdb_make)
5. xdb 并发安全查询:[“ip2region xdb-并发安全查询”](https://ip2region.net/doc/xdb/concurrent)
6. xdb 数据更新方法:[“ip2region 数据更新和 xdb 数据编辑器的使用”](https://mp.weixin.qq.com/s/cZH5qIn4E5rQFy6N32RCzA)

### 3、技术信息博客
1. 微信公众号 - lionsoul-org,作者活跃的技术分享渠道
2. [Ip2Region 官方社区](https://ip2region.net)


================================================
FILE: binding/c/Makefile
================================================
all: xdb_searcher test_util

xdb_searcher: xdb_api.h xdb_util.c xdb_searcher.c main.c
	gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher

test_util: xdb_api.h xdb_util.c test_util.c
	gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util

xdb_searcher.o: xdb_searcher.c
	gcc -std=c99 -Wall -c xdb_searcher.c

xdb_util.o: xdb_util.c
	gcc -std=c99 -Wall -c xdb_util.c

xdb_searcher_lib: xdb_util.o xdb_searcher.o
	mkdir -p build/lib
	mkdir -p build/include
	ar -rc build/lib/libxdb.a `find . -name "*.o"`
	cp xdb_api.h build/include

clean:
	find ./ -name \*.o  | xargs rm -f
	find ./ -name test_util | xargs rm -f
	find ./ -name xdb_searcher | xargs rm -f
	rm -rf build

.PHONY: all clean xdb_searcher test_util


================================================
FILE: binding/c/README.md
================================================
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)

# ip2region c Query Client

# Usage

### About Query API

The prototype of the Query API is as follows:

```c
// Query via string IP
int xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);
// Query via binary IP returned by xdb_parse_ip
int xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);
```

If the query fails, a non-`0` error code will be returned. If the query is successful, the `region` information string can be obtained from `xdb_region_buffer_t`. If the input IP cannot be found, `xdb_region_buffer_t` will receive an empty string `""`.

### About IPv4 and IPv6

This xdb query client implementation supports both IPv4 and IPv6 queries. The usage is as follows:

```c
#include "xdb_api.h";

// For IPv4: Set xdb path to the v4 xdb file, specify IP version as IPv4
const char *db_path  = "../../data/ip2region_v4.xdb";  // or your ipv4 xdb path
xdb_version_t *version = XDB_IPv4;

// For IPv6: Set xdb path to the v6 xdb file, specify IP version as IPv6
const char *db_path  = "../../data/ip2region_v6.xdb";  // or your ipv6 xdb path
xdb_version_t *version = XDB_IPv6;

// The IP version of the xdb specified by db_path must be consistent with the version, otherwise an error will occur during query execution
// Note: The following demonstration directly uses db_path and version variables
```

### XDB File Verification

It is recommended that you proactively verify the applicability of the xdb file, as some future new features may cause the current Searcher version to be incompatible with the xdb file you are using. Verification can avoid unpredictable errors during runtime. You do not need to verify every time; for example, verify when the service starts or manually call a command to confirm version matching. Do not run verification every time a Searcher is created, as this will affect query response speed, especially in high-concurrency scenarios.

```c
#include "xdb_api.h";

int errcode = xdb_verify(db_path);
if ($err != 0) {
    // Applicability verification failed!!!
    // The current query client implementation is not suitable for querying the xdb file specified by db_path.
    // You should stop the service and use a suitable xdb file or upgrade to a Searcher implementation compatible with db_path.
    printf("failed to verify xdb file `%s`, errcode: %d\n", db_path, errcode);
    return;
}

// Verification passed, the current Searcher can be safely used for query operations on the xdb pointed to by dbPath
```

### File-Based Query

```c
#include <stdio.h>
#include "xdb_api.h"

int main(int argc, char *argv[]) {
    xdb_searcher_t searcher;
    char region_buffer[512] = {'\0'};
    xdb_region_buffer_t region;

    // Initialize region_buffer_t using region_buffer from stack space
    int err = xdb_region_buffer_init(&region, region_buffer, sizeof(region_buffer));
    if (err != 0) {
        printf("failed to init the region buffer with errcode=%d\n", err);
        return 1;
    }

    // Initialize winsock when the service starts; no need to call repeatedly, only needed on Windows systems
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return 1;
    }

    // 1. Initialize xdb query object from db_path.
    // @Note: Use the db_path and version described above to create the searcher
    err = xdb_new_with_file_only(version, &searcher, db_path);
    if (err != 0) {
        printf("failed to create xdb searcher from `%s` with errno=%d\n", db_path, err);
        return 1;
    }

    // 2. Call search API to query, both IPv4 and IPv6 are supported.
    const char *ip_string = "1.2.3.4";
    // ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6

    long cost_time = 0, s_time = xdb_now();
    err = xdb_search_by_string(&searcher, ip_string, &region);
    cost_time = (int) (xdb_now() - s_time);
    if (err != 0) {
        printf("failed search(%s) with errno=%d\n", ip_string, err);
    } else {
        printf("{region: %s, took: %d μs}", region.value, cost_time);
    }

    // Clean up memory resources for region info; must be called after every search
    xdb_region_buffer_free(&region);

    // Note: For concurrent use, each thread needs to define and initialize its own searcher query object independently.

    // 3. Close xdb searcher
    xdb_close(&searcher);
    xdb_clean_winsock();    // Call on Windows
    return 0;
}
```

### Caching `VectorIndex`

We can pre-load VectorIndex data from the xdb file and cache it globally. Using the global VectorIndex cache every time a Searcher object is created can reduce a fixed IO operation, thereby accelerating queries and reducing IO pressure.

```c
#include <stdio.h>
#include "xdb_api.h"

int main(int argc, char *argv[]) {
    xdb_vector_index_t *v_index;
    xdb_searcher_t searcher;
    xdb_region_buffer_t region;

    // Initialize region_buffer with NULL to let it manage memory allocation automatically
    int err = xdb_region_buffer_init(&region, NULL, 0);
    if (err != 0) {
        printf("failed to init the region buffer with errcode=%d\n", err);
        return 0;
    }

    // Initialize winsock when the service starts; no need to call repeatedly, only needed on Windows systems
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return 1;
    }

    // 1. Load VectorIndex from the db_path described above.
    // Obtain v_index to create a global cache for subsequent repeated use.
    // Note: v_index does not need to be loaded every time; it is recommended to load it once at service startup as a global resource.
    v_index = xdb_load_vector_index_from_file(db_path);
    if (v_index == NULL) {
        printf("failed to load vector index from `%s`\n", db_path);
        return 1;
    }

    // 2. Use the global VectorIndex variable to create an xdb searcher with VectorIndex cache.
    // @Note: Use the db_path and version described above to create the searcher
    err = xdb_new_with_vector_index(version, &searcher, db_path, v_index);
    if (err != 0) {
        printf("failed to create vector index cached searcher with errcode=%d\n", err);
        return 2;
    }


    // 3. Call search API to query, both IPv4 and IPv6 are supported
    const char *ip_string = "1.2.3.4";
    // ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6

    long cost_time = 0, s_time = xdb_now();
    err = xdb_search_by_string(&searcher, ip_string, &region);
    cost_time = (int) (xdb_now() - s_time);
    if (err != 0) {
        printf("failed search(%s) with errno=%d\n", ip_string, err);
    } else {
        printf("{region: %s, took: %d μs}", region.value, cost_time);
    }

    // Clean up memory resources for region info; must be called after every search
    xdb_region_buffer_free(&region);


    // Note: For concurrent use, each thread needs to define and initialize its own searcher query object independently.

    // 4. Close xdb searcher; if the service is being shut down, the memory for v_index also needs to be freed.
    xdb_close(&searcher);
    xdb_close_vector_index(v_index);
    xdb_clean_winsock();
    return 0;
}
```

### Caching the Entire `xdb` File

We can also pre-load the entire xdb file into memory and then create a query object based on this data to achieve fully memory-based queries, similar to the previous memory search.

```c
#include <stdio.h>
#include "xdb_api.h"

int main(int argc, char *argv[]) {
    xdb_content_t *c_buffer;
    xdb_searcher_t searcher;
    xdb_region_buffer_t region;

    // Initialize region_buffer with NULL to let it manage memory allocation automatically
    int err = xdb_region_buffer_init(&region, NULL, 0);
    if (err != 0) {
        printf("failed to init the region buffer with errcode=%d\n", err);
        return 0;
    }


    // Initialize winsock when the service starts; no need to call repeatedly, only needed on Windows systems
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return 1;
    }

    // 1. Load the entire xdb data from the db_path described above.
    c_buffer = xdb_load_content_from_file(db_path);
    if (v_index == NULL) {
        printf("failed to load xdb content from `%s`\n", db_path);
        return 1;
    }

    // 2. Use the global c_buffer variable to create a fully memory-based xdb query object.
    // @Note: Use the version described above to create the searcher.
    err = xdb_new_with_buffer(version, &searcher, c_buffer);
    if (err != 0) {
        printf("failed to create content cached searcher with errcode=%d\n", err);
        return 2;
    }

    // 3. Call search API to query, both IPv4 and IPv6 are supported
    const char *ip_string = "1.2.3.4";
    // ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6

    long cost_time = 0, s_time = xdb_now();
    err = xdb_search_by_string(&searcher, ip_string, &region);
    cost_time = (int) (xdb_now() - s_time);
    if (err != 0) {
        printf("failed search(%s) with errno=%d\n", ip_string, err);
    } else {
        printf("{region: %s, took: %d μs}", region.value, cost_time);
    }

    // Clean up memory resources for region info; must be called after every search
    xdb_region_buffer_free(&region);


    // Note: For concurrent use, xdb query objects created this way can be safely used for concurrency.
    // It is recommended to create them when the service starts and then use them safely in parallel until the service shuts down.

    // 4. Close xdb searcher; memory for c_buffer needs to be freed when shutting down the service.
    xdb_close(&searcher);
    xdb_close_content(c_buffer);
    xdb_clean_winsock();
    return 0;
}
```

### About Storage of Location Information

In older implementations, search-related functions relied on a specified `region_buffer` memory to store location information, which had significant limitations.
The new implementation provides an `xdb_region_buffer_t` object to manage these memory allocations. You can still specify a fixed `region_buffer` to create memory management for the region; this is suitable when the maximum length of your location information is known, which helps reduce memory fragmentation during runtime. If the length of the location information is uncertain or if your program is not suited for pre-allocating a block of memory, you can initialize `xdb_region_buffer_t` by specifying `NULL`. In this case, the object will automatically manage memory allocation, making it suitable for storing location information of any length, though this approach will certainly increase memory fragmentation over long-term operation.

```c
// 1. Create region_buffer by specifying a memory block
char buffer[512];
xdb_region_buffer_t region;
int err = xdb_region_buffer_init(&region, buffer, sizeof(buffer));
if (err != 0) {
    // Initialization failed
    printf("failed to init region buffer width errcode=%d", err);
    return;
}

// 2. Create region_buffer by specifying NULL to let it allocate memory as needed automatically
xdb_region_buffer_t region;
int err = xdb_region_buffer_init(&region, NULL, 0);
if (err != 0) {
    // Initialization failed
    printf("failed to init region buffer width errcode=%d", err);
    return;
}


// Note: After each query call, you must manually call the function to free memory.
// The search function will report an error if used with uncleaned region info.
xdb_region_buffer_free(&region);
```

# Compiling the Test Program

Compile and obtain the `xdb_searcher` executable as follows:

```bash
# cd to the c binding root directory
➜  c git:(master) ✗ make
gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
```

# Query Testing

Test queries against xdb via the `xdb_searcher search` command:

```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher search
./xdb_searcher search [command options]
options:
 --db string              ip2region binary xdb file path
 --cache-policy string    cache policy: file/vectorIndex/content
```

Example: performing IPv4 query testing using the default data/ip2region_v4.xdb:

```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v4.xdb
ip2region xdb searcher test program
source xdb: ../../data/ip2region_v4.xdb (IPv4, vectorIndex)
type 'quit' to exit
ip2region>> 1.2.3.4
{region: Australia|Queensland|Brisbane|0|AU, io_count: 5, took: 39 μs}
ip2region>> 120.229.45.2
{region: 中国|广东省|深圳市|移动|CN, io_count: 3, took: 13 μs}
```

Example: performing IPv6 query testing using the default data/ip2region_v6.xdb:

```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v6.xdb
ip2region xdb searcher test program
source xdb: ../../data/ip2region_v6.xdb (IPv6, vectorIndex)
type 'quit' to exit
ip2region>> ::
{region: , io_count: 1, took: 38 μs}
ip2region>> 2604:bc80:8001:11a4:ffff:ffff:ffff:ffff
{region: United States|Florida|Miami|velia.net Internetdienste GmbH|US, io_count: 14, took: 76 μs}
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
{region: 中国|广东省|深圳市|电信|CN, io_count: 8, took: 42 μs}
```

Enter an IP to perform a query; enter `quit` to exit the test program. You can also set `cache-policy` to file/vectorIndex/content respectively to test the efficiency of the three different cache implementations.

# bench Testing

Perform bench testing via the `xdb_searcher bench` command. This ensures there are no errors in the query program and the `xdb` file, while also providing average query performance through a large number of queries:

```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher bench                                  
./xdb_searcher bench [command options]
options:
 --db string              ip2region binary xdb file path
 --src string             source ip text file path
 --cache-policy string    cache policy: file/vectorIndex/content
```

Example: performing IPv4 bench testing via the default data/ip2region_v4.xdb and data/ipv4_source.txt:

```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v4.xdb --src=../../data/ipv4_source.txt
Bench finished, {cache_policy: vectorIndex, total: 1367686, took: 7.640s, cost: 5 μs/op}
```

Example: performing IPv6 bench testing via the default data/ip2region_v6.xdb and data/ipv6_source.txt:

```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v6.xdb --src=../../data/ipv6_source.txt
Bench finished, {cache_policy: vectorIndex, total: 34159862, took: 857.750s, cost: 24 μs/op}
```

You can set the `cache-policy` parameter to test the efficiency of different cache mechanisms (file/vectorIndex/content). @Note: Please ensure that the `src` file used for benching is the same source file used to generate the corresponding `xdb` file.


================================================
FILE: binding/c/README_zh.md
================================================
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)

# ip2region c 查询客户端


# 使用方式

### 关于查询 API
查询 API 的原型如下:
```c
// 通过字符串 IP 进行查询
int xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);
// 通过 xdb_parse_ip 返回的二进制 IP 进行查询
int xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);
```
如果查询失败将会返回非 `0` 的错误代码,如果查询成功 xdb_region_buffer_t 可以获取到字符串的 `region` 信息,如果输入的 IP 找不到相关的信息,xdb_region_buffer_t 将会得到一个空的字符串 `""`。

### 关于 IPv4 和 IPv6
该 xdb 查询客户端实现同时支持对 IPv4 和 IPv6 的查询,使用方式如下:
```c
#include "xdb_api.h";

// 如果是 IPv4: 设置 xdb 路径为 v4 的 xdb 文件,IP版本指定为 IPv4
const char *db_path  = "../../data/ip2region_v4.xdb";  // 或者你的 ipv4 xdb 的路径
xdb_version_t *version = XDB_IPv4;

// 如果是 IPv6: 设置 xdb 路径为 v6 的 xdb 文件,IP版本指定为 IPv6
const char *db_path  = "../../data/ip2region_v6.xdb";  // 或者你的 ipv6 xdb 路径
xdb_version_t *version = XDB_IPv6;

// db_path 指定的 xdb 的 IP 版本必须和 version 指定的一致,不然查询执行的时候会报错
// 备注:以下演示直接使用 db_path 和 version 变量
```

### XDB 文件验证
建议您主动去验证 xdb 文件的适用性,因为后期的一些新功能可能会导致目前的 Searcher 版本无法适用你使用的 xdb 文件,验证可以避免运行过程中的一些不可预测的错误。 你不需要每次都去验证,例如在服务启动的时候,或者手动调用命令验证确认版本匹配即可,不要在每次创建的 Searcher 的时候运行验证,这样会影响查询的响应速度,尤其是高并发的使用场景。
```c
#include "xdb_api.h";

int errcode = xdb_verify(db_path);
if ($err != 0) {
    // 适用性验证失败!!!
    // 当前查询客户端实现不适用于 db_path 指定的 xdb 文件的查询.
    // 应该停止启动服务,使用合适的 xdb 文件或者升级到适合 db_path 的 Searcher 实现。
    printf("failed to verify xdb file `%s`, errcode: %d\n", db_path, errcode);
    return;
}

// 验证通过,当前使用的 Searcher 可以安全的用于对 dbPath 指向的 xdb 的查询操作
```

### 完全基于文件的查询

```c
#include <stdio.h>
#include "xdb_api.h"

int main(int argc, char *argv[]) {
    xdb_searcher_t searcher;
    char region_buffer[512] = {'\0'};
    xdb_region_buffer_t region;

    // 使用栈空间的 region_buffer 初始化 region_buffer_t
    int err = xdb_region_buffer_init(&region, region_buffer, sizeof(region_buffer));
    if (err != 0) {
        printf("failed to init the region buffer with errcode=%d\n", err);
        return 1;
    }

    // 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return 1;
    }

    // 1、从 db_path 初始化 xdb 查询对象.
    // @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
    err = xdb_new_with_file_only(version, &searcher, db_path);
    if (err != 0) {
        printf("failed to create xdb searcher from `%s` with errno=%d\n", db_path, err);
        return 1;
    }

    // 2、调用 search API 查询,IPv4 和 IPv6 都支持.
    const char *ip_string = "1.2.3.4";
    // ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6

    long cost_time = 0, s_time = xdb_now();
    err = xdb_search_by_string(&searcher, ip_string, &region);
    cost_time = (int) (xdb_now() - s_time);
    if (err != 0) {
        printf("failed search(%s) with errno=%d\n", ip_string, err);
    } else {
        printf("{region: %s, took: %d μs}", region.value, cost_time);
    }

    // 清理 region 信息的内存资源,每次 search 之后都得调用
    xdb_region_buffer_free(&region);

    // 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。

    // 3、关闭 xdb 查询器
    xdb_close(&searcher);
    xdb_clean_winsock();    // windows 下调用
    return 0;
}
```

### 缓存 `VectorIndex` 索引

我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
```c
#include <stdio.h>
#include "xdb_api.h"

int main(int argc, char *argv[]) {
    xdb_vector_index_t *v_index;
    xdb_searcher_t searcher;
    xdb_region_buffer_t region;

    // 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
    int err = xdb_region_buffer_init(&region, NULL, 0);
    if (err != 0) {
        printf("failed to init the region buffer with errcode=%d\n", err);
        return 0;
    }

    // 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return 1;
    }

    // 1、从顶部描述的 db_path 加载 VectorIndex 索引。
    // 得到 v_index 做成全局缓存,便于后续反复使用。
    // 注意:v_index 不需要每次都加载,建议在服务启动的时候加载一次,然后做成全局资源。
    v_index = xdb_load_vector_index_from_file(db_path);
    if (v_index == NULL) {
        printf("failed to load vector index from `%s`\n", db_path);
        return 1;
    }

    // 2、使用全局的 VectorIndex 变量创建带 VectorIndex 缓存的 xdb 查询对象.
    // @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
    err = xdb_new_with_vector_index(version, &searcher, db_path, v_index);
    if (err != 0) {
        printf("failed to create vector index cached searcher with errcode=%d\n", err);
        return 2;
    }


    // 3、调用 search API 查询,IPv4 和 IPv6 都支持
    const char *ip_string = "1.2.3.4";
    // ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6

    long cost_time = 0, s_time = xdb_now();
    err = xdb_search_by_string(&searcher, ip_string, &region);
    cost_time = (int) (xdb_now() - s_time);
    if (err != 0) {
        printf("failed search(%s) with errno=%d\n", ip_string, err);
    } else {
        printf("{region: %s, took: %d μs}", region.value, cost_time);
    }

    // 清理 region 信息的内存资源,每次 search 之后都得调用
    xdb_region_buffer_free(&region);


    // 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。

    // 4、关闭 xdb 查询器,如果是要关闭服务,也需要释放 v_index 的内存。
    xdb_close(&searcher);
    xdb_close_vector_index(v_index);
    xdb_clean_winsock();
    return 0;
}
```

### 缓存整个 `xdb` 文件

我们也可以预先加载整个 xdb 文件到内存,然后基于这个数据创建查询对象来实现完全基于内存的查询,类似之前的 memory search。
```c
#include <stdio.h>
#include "xdb_api.h"

int main(int argc, char *argv[]) {
    xdb_content_t *c_buffer;
    xdb_searcher_t searcher;
    xdb_region_buffer_t region;

    // 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
    int err = xdb_region_buffer_init(&region, NULL, 0);
    if (err != 0) {
        printf("failed to init the region buffer with errcode=%d\n", err);
        return 0;
    }


    // 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return 1;
    }

    // 1、从 顶部描述的 db_path 加载整个 xdb 的数据。
    c_buffer = xdb_load_content_from_file(db_path);
    if (v_index == NULL) {
        printf("failed to load xdb content from `%s`\n", db_path);
        return 1;
    }

    // 2、使用全局的 c_buffer 变量创建一个完全基于内存的 xdb 查询对象.
    // @Note: 使用顶部描述的 version 来创建 searcher.
    err = xdb_new_with_buffer(version, &searcher, c_buffer);
    if (err != 0) {
        printf("failed to create content cached searcher with errcode=%d\n", err);
        return 2;
    }

    // 3、调用 search API 查询,IPv4 和 IPv6 都支持
    const char *ip_string = "1.2.3.4";
    // ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6

    long cost_time = 0, s_time = xdb_now();
    err = xdb_search_by_string(&searcher, ip_string, &region);
    cost_time = (int) (xdb_now() - s_time);
    if (err != 0) {
        printf("failed search(%s) with errno=%d\n", ip_string, err);
    } else {
        printf("{region: %s, took: %d μs}", region.value, cost_time);
    }

    // 清理 region 信息的内存资源,每次 search 之后都得调用
    xdb_region_buffer_free(&region);


    // 备注:并发使用,使用这种方式创建的 xdb 查询对象可以安全用于并发。
    // 建议在服务启动的时候创建好,然后一直安全并发使用,直到服务关闭。

    // 4、关闭 xdb 查询器,关闭服务的时候需要释放 c_buffer 的内存。
    xdb_close(&searcher);
    xdb_close_content(c_buffer);
    xdb_clean_winsock();
    return 0;
}
```

### 关于定位信息的存储
在旧版本的实现中,search相关的函数都是依靠指定一个 `region_buffer` 内存来用于存储地域信息,这种方式还是有很大的局限性。
新的实现提供了一个 `xdb_region_buffer_t` 对象来管理这些内存的分配,你依然可以指定一个固定的 `region_buffer` 来创建 region 的内存管理,这个情况适合当你的地域信息的最大长度是可知的,这种方式可以减少运行过程中内存的碎片堆积。如果地域信息的长度不确定或者你的程序不适合提前分配一块内存来管理,你可以通过指定 NULL 的方式来初始化 `xdb_region_buffer_t`,这样对象会自动管理内存的分配,也适合任意长度的地域信息的存储,不过这种方式在长期的运行过程中肯定会增加内存碎片的堆积。
```c
// 1, 通过指定一块内存来创建 region_buffer
char buffer[512];
xdb_region_buffer_t region;
int err = xdb_region_buffer_init(&region, buffer, sizeof(buffer));
if (err != 0) {
    // 初始化失败
    printf("failed to init region buffer width errcode=%d", err);
    return;
}

// 2,通过指定 NULL 来创建 region_buffer,让其自动按需分配内存
xdb_region_buffer_t region;
int err = xdb_region_buffer_init(&region, NULL, 0);
if (err != 0) {
    // 初始化失败
    printf("failed to init region buffer width errcode=%d", err);
    return;
}


// 备注:在每次调用 search 完成 IP 定位信息的查询后,你需要手动调用函数来释放内存 .
// search 函数使用未经清理的 region 信息会报错。
xdb_region_buffer_free(&region);
```


# 测试程序编译

通过如下方式编译得到 xdb_searcher 可执行程序:
```bash
# cd 到 c binding 根目录
➜  c git:(master) ✗ make
gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
```


# 查询测试

通过 `xdb_searcher search` 命令来测试对 xdb 的查询:
```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher search
./xdb_searcher search [command options]
options:
 --db string              ip2region binary xdb file path
 --cache-policy string    cache policy: file/vectorIndex/content
```

例如:使用默认的 data/ip2region_v4.xdb 进行 IPv4 查询测试:
```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v4.xdb
ip2region xdb searcher test program
source xdb: ../../data/ip2region_v4.xdb (IPv4, vectorIndex)
type 'quit' to exit
ip2region>> 1.2.3.4
{region: Australia|Queensland|Brisbane|0|AU, io_count: 5, took: 39 μs}
ip2region>> 120.229.45.2
{region: 中国|广东省|深圳市|移动|CN, io_count: 3, took: 13 μs}
```

例如:使用默认的 data/ip2region_v6.xdb 进行 IPv6 查询测试:
```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v6.xdb
ip2region xdb searcher test program
source xdb: ../../data/ip2region_v6.xdb (IPv6, vectorIndex)
type 'quit' to exit
ip2region>> ::
{region: , io_count: 1, took: 38 μs}
ip2region>> 2604:bc80:8001:11a4:ffff:ffff:ffff:ffff
{region: United States|Florida|Miami|velia.net Internetdienste GmbH|US, io_count: 14, took: 76 μs}
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
{region: 中国|广东省|深圳市|电信|CN, io_count: 8, took: 42 μs}
```

输入 ip 即可进行查询,输入 quit 即可退出测试程序。也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同的缓存实现的效率。



# bench 测试

通过 `xdb_searcher bench` 命令来进行 bench 测试,一方面确保查询程序和 `xdb` 文件没有错误,另一方面可以通过大量的查询得到平均的查询性能:
```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher bench                                  
./xdb_searcher bench [command options]
options:
 --db string              ip2region binary xdb file path
 --src string             source ip text file path
 --cache-policy string    cache policy: file/vectorIndex/content
```

例如:通过默认的 data/ip2region_v4.xdb 和 data/ipv4_source.txt 来进行 IPv4 的 bench 测试:
```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v4.xdb --src=../../data/ipv4_source.txt
Bench finished, {cache_policy: vectorIndex, total: 1367686, took: 7.640s, cost: 5 μs/op}
```

例如:通过默认的 data/ip2region_v6.xdb 和 data/ipv6_source.txt 来进行 IPv6 的 bench 测试:
```bash
➜  c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v6.xdb --src=../../data/ipv6_source.txt
Bench finished, {cache_policy: vectorIndex, total: 34159862, took: 857.750s, cost: 24 μs/op}
```

可以设置 `cache-policy` 参数来分别测试 file/vectorIndex/content 不同缓存实现机制的效率。 @Note:请注意 bench 使用的 src 文件需要是生成对应的 xdb 文件相同的源文件。


================================================
FILE: binding/c/main.c
================================================
// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.

// ---
// @Author Lion <chenxin619315@gmail.com>
// @Date   2022/06/28

#include "stdio.h"
#include "xdb_api.h"

struct searcher_test_entry {
    xdb_searcher_t searcher;
    xdb_vector_index_t *v_index;
    xdb_content_t *c_buffer;

    // xdb region buffer
    // char region_buffer[256];
    xdb_region_buffer_t region;
};
typedef struct searcher_test_entry searcher_test_t;

int init_searcher_test(searcher_test_t *test, char *db_path, char *cache_policy) {
    int err, errcode = 0;
    FILE *handle = fopen(db_path, "rb");
    if (handle == NULL) {
        return -1;
    }

    // auto detect the version from the xdb header
    xdb_header_t *header = xdb_load_header(handle);
    if (header == NULL) {
        printf("failed to load header from `%s`\n", db_path);
        errcode = 1;
        goto defer;
    }

    // verify the current xdb
    err = xdb_verify_from_header(handle, header);
    if (err != 0) {
        printf("failed to verify xdb file `%s` with errno=%d\n", db_path, err);
        errcode = 2;
        goto defer;
    }

    xdb_version_t *version = xdb_version_from_header(header);
    if (version == NULL) {
        printf("failed to load version from header\n");
        errcode = 3;
        goto defer;
    }

    test->v_index = NULL;
    test->c_buffer = NULL;

    if (strcmp(cache_policy, "file") == 0) {
        err = xdb_new_with_file_only(version, &test->searcher, db_path);
        if (err != 0) {
            printf("failed to create searcher with errcode=%d\n", err);
            errcode = 4;
            goto defer;
        }
    } else if (strcmp(cache_policy, "vectorIndex") == 0) {
        test->v_index = xdb_load_vector_index_from_file(db_path);
        if (test->v_index == NULL) {
            printf("failed to load vector index from `%s`\n", db_path);
            errcode = 4;
            goto defer;
        }

        err = xdb_new_with_vector_index(version, &test->searcher, db_path, test->v_index);
        if (err != 0) {
            printf("failed to create vector index cached searcher with errcode=%d\n", err);
            errcode = 5;
            goto defer;
        }
    } else if (strcmp(cache_policy, "content") == 0) {
        test->c_buffer = xdb_load_content_from_file(db_path);
        if (test->c_buffer == NULL) {
            printf("failed to load xdb content from `%s`\n", db_path);
            errcode = 4;
            goto defer;
        }

        err = xdb_new_with_buffer(version, &test->searcher, test->c_buffer);
        if (err != 0) {
            printf("failed to create content cached searcher with errcode=%d\n", err);
            errcode = 5;
            goto defer;
        }
    } else {
        printf("invalid cache policy `%s`, options: file/vectorIndex/content\n", cache_policy);
        errcode = 6;
        goto defer;
    }

    // init the region buffer
    // err = xdb_region_buffer_init(&test->region, test->region_buffer, sizeof(test->region_buffer));
    err = xdb_region_buffer_init(&test->region, NULL, 0);
    if (err != 0) {
        printf("failed to init the region buffer with err=%d\n", err);
        errcode = 7;
        goto defer;
    }

defer:
    if (header != NULL) {
        xdb_free_header(header);
    }

    if (handle != NULL) {
        fclose(handle);
    }

    return errcode;
}

void destroy_searcher_test(searcher_test_t *test) {
    xdb_close(&test->searcher);

    // check and free the vector index
    if (test->v_index != NULL) {
        xdb_free_vector_index(test->v_index);
        test->v_index = NULL;
    }

    // check and free the content buffer
    if (test->c_buffer != NULL) {
        xdb_free_content(test->c_buffer);
        test->c_buffer = NULL;
    }
}

//read a line from a command line.
static char *get_line(FILE *fp, char *__dst) {
    register int c;
    register char *cs;

    cs = __dst;
    while ( ( c = getc( fp ) ) != EOF ) {
        if ( c == '\n' ) break;
        *cs++ = c;
    }
    *cs = '\0';

    return ( c == EOF && cs == __dst ) ? NULL : __dst;
}

void print_help(char *argv[]) {
    printf("ip2region xdb searcher\n");
    printf("%s [command] [command options]\n", argv[0]);
    printf("Command: \n");
    printf("  search    search input test\n");
    printf("  bench     search bench test\n");
}

void test_search(int argc, char *argv[]) {
    int i, n, err;

    // for args parse
    char *r, key[33] = {'\0'}, val[256] = {'\0'};
    char db_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};

    // for search
    long s_time, c_time;
    char line[512] = {'\0'};

    // ip parse
    xdb_version_t *version;
    bytes_ip_t ip_bytes[16] = {'\0'};

    searcher_test_t test;

    for (i = 2; i < argc; i++) {
        r = argv[i];
        if (strlen(r) < 5) {
            continue;
        }

        if (r[0] != '-' || r[1] != '-') {
            continue;
        }

        if (strchr(r, '=') == NULL) {
            printf("missing = for args pair '%s'\n", r);
            return;
        }

        n = sscanf(r+2, "%32[^=]=%255[^\n]", key, val);
        if (n != 2) {
            printf("invalid option flag `%s`\n", r);
            return;
        }

        // printf("key=%s, val=%s\n", key, val);
        if (strcmp(key, "db") == 0) {
            snprintf(db_file, sizeof(db_file), "%s", val);
        } else if (strcmp(key, "cache-policy") == 0) {
            memcpy(cache_policy, val, sizeof(cache_policy) - 1);
            // snprintf(cache_policy, sizeof(cache_policy), "%s", val);
        } else {
            printf("undefined option `%s`\n", r);
            return;
        }
    }

    if (strlen(db_file) < 1) {
        printf("%s search [command options]\n", argv[0]);
        printf("options:\n");
        printf(" --db string              ip2region binary xdb file path\n");
        printf(" --cache-policy string    cache policy: file/vectorIndex/content\n");
        return;
    }

    // init the win sock
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return;
    }

    // printf("db_file=%s, cache_policy=%s\n", db_file, cache_policy);
    err = init_searcher_test(&test, db_file, cache_policy);
    if (err != 0) {
        // init program will print the error reasons;
        return;
    }

    printf("ip2region xdb searcher test program\n"
            "source xdb: %s (%s, %s)\n"
            "type 'quit' to exit\n", db_file, xdb_get_version(&test.searcher)->name, cache_policy);
    while ( 1 ) {
        printf("ip2region>> ");
        get_line(stdin, line);
        if ( strlen(line) < 2 ) {
            continue;
        }

        if (strcmp(line, "quit") == 0 ) {
            break;
        }

        version = xdb_parse_ip(line, ip_bytes, sizeof(ip_bytes));
        if (version == NULL) {
            printf("invalid ip address `%s`\n", line);
            continue;
        }

        s_time = xdb_now();
        err = xdb_search(&test.searcher, ip_bytes, version->bytes, &test.region);
        if (err != 0) {
            printf("{err: %d, io_count: %d}\n", err, xdb_get_io_count(&test.searcher));
        } else {
            c_time = xdb_now() - s_time;
            printf("{region: %s, io_count: %d, took: %ld μs}\n", test.region.value, xdb_get_io_count(&test.searcher), c_time);
        }

        // free the region
        xdb_region_buffer_free(&test.region);
    }

    destroy_searcher_test(&test);
    xdb_clean_winsock();
    printf("searcher test program exited, thanks for trying\n");
}

void test_bench(int argc, char *argv[]) {
    int i, n, err;
    char *r, key[33] = {'\0'}, val[256] = {'\0'};
    char db_file[256] = {'\0'}, src_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};

    FILE *handle;
    char line[1024] = {'\0'}, sip_str[INET6_ADDRSTRLEN+1] = {'\0'}, eip_str[INET6_ADDRSTRLEN+1] = {'\0'};
    char src_region[512] = {'\0'};
    int count = 0, took;
    long s_time, t_time, c_time = 0;

    // ip parse
    xdb_version_t *s_version, *e_version;
    bytes_ip_t sip_bytes[16] = {'\0'}, eip_bytes[16] = {'\0'};
    string_ip_t ip_string[INET6_ADDRSTRLEN] = {'\0'};
    bytes_ip_t *ip_list[2];

    searcher_test_t test;
    for (i = 2; i < argc; i++) {
        r = argv[i];
        if (strlen(r) < 5) {
            continue;
        }

        if (r[0] != '-' || r[1] != '-') {
            continue;
        }

        if (strchr(r, '=') == NULL) {
            printf("missing = for args pair '%s'\n", r);
            return;
        }

        n = sscanf(r+2, "%32[^=]=%255[^\n]", key, val);
        if (n != 2) {
            printf("invalid option flag `%s`\n", r);
            return;
        }

        if (strcmp(key, "db") == 0) {
            snprintf(db_file, sizeof(db_file), "%s", val);
        } else if (strcmp(key, "src") == 0) {
            snprintf(src_file, sizeof(src_file), "%s", val);
        } else if (strcmp(key, "cache-policy") == 0) {
            memcpy(cache_policy, val, sizeof(cache_policy) - 1);
        } else {
            printf("undefined option `%s`\n", r);
            return;
        }
    }

    if (strlen(db_file) < 1 || strlen(src_file) < 1) {
        printf("%s bench [command options]\n", argv[0]);
        printf("options:\n");
        printf(" --db string              ip2region binary xdb file path\n");
        printf(" --src string             source ip text file path\n");
        printf(" --cache-policy string    cache policy: file/vectorIndex/content\n");
        return;
    }

    // init the win sock
    err = xdb_init_winsock();
    if (err != 0) {
        printf("failed to init the winsock with errno=%d\n", err);
        return;
    }

    // printf("db_file=%s, src_file=%s, cache_policy=%s\n", db_file, src_file, cache_policy);
    s_time = xdb_now();
    err = init_searcher_test(&test, db_file, cache_policy);
    if (err != 0) {
        // the init function will print the details;
        return;
    }

    // open the source file
    handle = fopen(src_file, "r");
    if (handle == NULL) {
        printf("failed to open source text file `%s`\n", src_file);
        return;
    }

    while(fgets(line, sizeof(line), handle) != NULL) {
        n = sscanf(line, "%46[^|]|%46[^|]|%511[^\n]", sip_str, eip_str, src_region);
        if (n != 3) {
            printf("invalid ip segment line `%s`\n", line);
            return;
        }

        s_version = xdb_parse_ip(sip_str, sip_bytes, sizeof(sip_bytes));
        if (s_version == NULL) {
            printf("invalid start ip `%s`\n", sip_str);
            return;
        }

        e_version = xdb_parse_ip(eip_str, eip_bytes, sizeof(eip_bytes));
        if (e_version == NULL) {
            printf("invalid end ip `%s`\n", sip_str);
            return;
        }

        if (s_version->id != e_version->id) {
            printf("start ip and end ip version not match for line `%s`\n", line);
            return;
        }

        if (xdb_ip_sub_compare(sip_bytes, s_version->bytes, (string_ip_t *) eip_bytes, 0) > 0) {
            printf("start ip(%s) should not be greater than end ip(%s)\n", sip_str, eip_str);
            return;
        }

        ip_list[0] = sip_bytes;
        ip_list[1] = eip_bytes;
        for (i = 0; i < 2; i++) {
            t_time = xdb_now();
            err = xdb_search(&test.searcher, ip_list[i], s_version->bytes, &test.region);
            c_time += xdb_now() - t_time;
            if (err != 0) {
                xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
                printf("failed to search ip `%s` with errno=%d\n", ip_string, err);
                return;
            }

            // check the region info
            if (strcmp(test.region.value, src_region) != 0) {
                xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
                printf("failed to search(%s) with (%s != %s)\n", ip_string, test.region.value, src_region);
                return;
            }

            // free the region buffer
            xdb_region_buffer_free(&test.region);

            count++;
        }
    };

    took = xdb_now() - s_time;
    destroy_searcher_test(&test);
    xdb_clean_winsock();
    fclose(handle);
    printf("Bench finished, {cache_policy: %s, total: %d, took: %.3fs, cost: %d μs/op}\n",
           cache_policy, count, took/1e6, count == 0 ? 0 : (int)(c_time/count));
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        print_help(argv);
        return 0;
    }

    char *opt = argv[1];
    if (strcmp(opt, "search") == 0) {
        test_search(argc, argv);
    } else if (strcmp(opt, "bench") == 0) {
        test_bench(argc, argv);
    } else {
        print_help(argv);
    }

    return 0;
}


================================================
FILE: binding/c/test_util.c
================================================
// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.

// ---
// @Author Lion <chenxin619315@gmail.com>
// @Date   2022/06/27

#include "stdio.h"
#include "xdb_api.h"

typedef void (* test_func_ptr) ();
struct test_func_entry {
    char *name;
    test_func_ptr func;
};
typedef struct test_func_entry test_func_t;

void test_load_header() {
    xdb_header_t *header = xdb_load_header_from_file("../../data/ip2region_v4.xdb");
    if (header == NULL) {
        printf("failed to load header");
    } else {
        printf("header loaded: {\n"
           "    version: %d, \n"
           "    index_policy: %d, \n"
           "    created_at: %u, \n"
           "    start_index_ptr: %d, \n"
           "    end_index_ptr: %d\n"
           "    ip_version: %d\n"
           "    runtime_ptr_bytes: %d\n"
           "    length: %d\n"
           "}\n",
           header->version, header->index_policy, header->created_at,
           header->start_index_ptr, header->end_index_ptr, 
           header->ip_version, header->runtime_ptr_bytes, header->length
       );
    }

    xdb_free_header(header);
}

void test_load_vector_index() {
    xdb_vector_index_t *v_index = xdb_load_vector_index_from_file("../../data/ip2region_v4.xdb");
    if (v_index == NULL) {
        printf("failed to load vector index from file\n");
    } else {
        printf("vector index loaded from file, length=%d\n", v_index->length);
    }

    xdb_free_vector_index(v_index);
}

void test_load_content() {
    xdb_content_t *content = xdb_load_content_from_file("../../data/ip2region_v4.xdb");
    if (content == NULL) {
        printf("failed to load content from file\n");
    } else {
        printf("content loaded from file, length=%d\n", content->length);
    }

    xdb_free_content(content);
}

void test_parse_ip() {
    const char *ip_list[] = {
        "1.0.0.0", "58.251.30.115", "192.168.1.100",
        "::", "2c0f:fff0::", "2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "240e:982:e617:ffff:ffff:ffff:ffff:ffff", 
        "219.xx.xx.11", "::xx:ffff",
        NULL
    };

    int errcode;
    xdb_version_t *version;
    bytes_ip_t ip_bytes[16] = {'\0'};
    string_ip_t ip_string[INET6_ADDRSTRLEN] = {'\0'};

    // init the sock env (for windows)
    if ((errcode = xdb_init_winsock()) != 0) {
        printf("failed to init the winsock");
        return;
    }

    for (int i = 0;; i++) {
        if (ip_list[i] == NULL) {
            break;
        }

        version = xdb_parse_ip(ip_list[i], ip_bytes, sizeof(ip_bytes));
        if (version == NULL) {
            printf("failed to parse ip `%s`\n", ip_list[i]);
            continue;
        }

        xdb_ip_to_string(ip_bytes, version->bytes, ip_string, sizeof(ip_string));
        printf("ip: %s (version=v%d), toString: %s\n", ip_list[i], version->id, ip_string);
    }

    // clean up the winsock
    xdb_clean_winsock();
}

struct ip_pair {
    const char *sip;
    const char *eip;
};
void test_ip_compare() {
    struct ip_pair ip_pair_list[] = {
        {"1.0.0.0", "1.0.0.1"},
        {"192.168.1.101", "192.168.1.90"},
        {"219.133.111.87", "114.114.114.114"},
        {"1.0.4.0", "1.0.1.0"},
        {"1.0.4.0", "1.0.3.255"},
        {"2000::", "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
        {"2001:4:112::", "2001:4:112:ffff:ffff:ffff:ffff:ffff"},
        {"ffff::", "2001:4:ffff:ffff:ffff:ffff:ffff:ffff"},
        {NULL, NULL}
    };

    struct ip_pair *pair_ptr = NULL;
    bytes_ip_t sip_bytes[16] = {'\0'};
    bytes_ip_t eip_bytes[16] = {'\0'};
    xdb_version_t *s_version, *e_version;
    int errcode;

    // init the sock env (for windows)
    if ((errcode = xdb_init_winsock()) != 0) {
        printf("failed to init the winsock");
        return;
    }

    for (int i = 0; ;i++) {
        pair_ptr = &ip_pair_list[i];
        if (pair_ptr->sip == NULL) {
            break;
        }

        s_version = xdb_parse_ip(pair_ptr->sip, sip_bytes, sizeof(sip_bytes));
        if (s_version == NULL) {
            printf("failed to parse sip `%s`", pair_ptr->sip);
            continue;
        }

        e_version = xdb_parse_ip(pair_ptr->eip, eip_bytes, sizeof(eip_bytes));
        if (e_version == NULL) {
            printf("failed to parse eip `%s`", pair_ptr->eip);
            continue;
        }

        if (s_version->id != e_version->id) {
            printf("sip and eip version not match `%s` != `%s`\n", s_version->name, e_version->name);
            continue;
        }

        printf(
            "ip_sub_compare(%s, %s): %d\n", 
            pair_ptr->sip, pair_ptr->eip, 
            xdb_ip_sub_compare(sip_bytes, s_version->bytes, (string_ip_t *) eip_bytes, 0)
        );
    }

    // clean up the winsock
    xdb_clean_winsock();
}

// please register your function heare
static test_func_t _test_function_list[] = {
    // xdb buffer
    {"test_load_header", test_load_header},
    {"test_load_vector_index", test_load_vector_index},
    {"test_load_content", test_load_content},

    // ip utils
    {"test_parse_ip", test_parse_ip},
    {"test_ip_compare", test_ip_compare},

    {NULL, NULL}
};

// valgrind --tool=memcheck --leak-check=full ./a.out
int main(int argc, char *argv[]) {
    int i;
    char *name;

    // check and call the function
    if (argc < 2) {
        printf("please specified the function name to call\n");
        return 1;
    }

    name = argv[1];
    test_func_ptr func = NULL;
    for (i = 0; ; i++) {
        if (_test_function_list[i].name == NULL) {
            break;
        }

        if (strcmp(name, _test_function_list[i].name) == 0) {
            func = _test_function_list[i].func;
            break;
        }
    }

    if (func == NULL) {
        printf("can't find test function `%s`\n", name);
        return 1;
    }
    
    // call the function
    func();

    return 0;
}


================================================
FILE: binding/c/xdb_api.h
================================================
// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.

// ---
// @Author Lion <chenxin619315@gmail.com>
// @Date   2022/06/27

#ifndef C_IP2REGION_XDB_H
#define C_IP2REGION_XDB_H

// @Note: 
// this define must be put before any header include
// force the LFS for ftell
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if ( defined(WIN32) || defined(_WIN32) || defined(__WINDOWS_) || defined(WINNT) )
#   define XDB_PUBLIC(type)    extern __declspec(dllexport) type
#   define XDB_PRIVATE(type)   static type
#   define XDB_WINDOWS
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#elif (defined(linux) || defined(_UNIX) || defined(__APPLE__) || defined(unix) || defined(__unix) || defined(__unix__) || defined(__linux__) || defined(linux) || defined(__linux))
#   define XDB_PUBLIC(type)    extern type
#   define XDB_PRIVATE(type)   static inline type
#   define XDB_LINUX
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#   define XDB_PUBLIC(type) type
#   define XDB_PRIVATE(type) static type
#endif

#define xdb_calloc( _blocks, _bytes )  calloc( _blocks, _bytes )
#define xdb_malloc( _bytes )           malloc( _bytes )
#define xdb_free( _ptr )               free( _ptr )

// public constants define
#define xdb_structure_20 2
#define xdb_structure_30 3
#define xdb_header_info_length 256
#define xdb_vector_index_rows  256
#define xdb_vector_index_cols  256
#define xdb_vector_index_size  8
#define xdb_v4_index_size 14    // 4 + 4 + 2 + 4
#define xdb_v6_index_size 38    // 16 + 16 + 2 + 4

// --- ip version info
#define xdb_ipv4_id 4
#define xdb_ipv6_id 6
#define xdb_ipv4_bytes 4
#define xdb_ipv6_bytes 16
// cache of vector_index_row × vector_index_rows × vector_index_size
#define xdb_vector_index_length 524288

// --- xdb buffer functions

// use the following buffer struct to wrap the binary buffer data
// since the buffer data could not be operated with the string API.
struct xdb_header {
    unsigned short version;
    unsigned short index_policy;
    unsigned int created_at;
    unsigned int start_index_ptr;
    unsigned int end_index_ptr;
    
    // since 3.0+ with IPv6 supporting
    unsigned short ip_version;
    unsigned short runtime_ptr_bytes;

    // the original buffer
    unsigned int length;
    char buffer[xdb_header_info_length];
};
typedef struct xdb_header xdb_header_t;

XDB_PUBLIC(xdb_header_t *) xdb_load_header(FILE *);

XDB_PUBLIC(xdb_header_t *) xdb_load_header_from_file(const char *);

XDB_PUBLIC(void) xdb_free_header(void *);


// --- vector index buffer
struct xdb_vector_index {
    unsigned int length;
    char buffer[xdb_vector_index_length];
};
typedef struct xdb_vector_index xdb_vector_index_t;

XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index(FILE *);

XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index_from_file(const char *);

XDB_PUBLIC(void) xdb_free_vector_index(void *);


// --- content buffer
struct xdb_content {
    unsigned int length;
    char *buffer;
};
typedef struct xdb_content xdb_content_t;

XDB_PUBLIC(xdb_content_t *) xdb_load_content(FILE *);

XDB_PUBLIC(xdb_content_t *) xdb_load_content_from_file(const char *);

XDB_PUBLIC(void) xdb_free_content(void *);

// --- xdb verify

// Verify if the current Searcher could be used to search the specified xdb file.
// Why do we need this check ?
// The future features of the xdb impl may cause the current searcher not able to work properly.
//
// @Note: You Just need to check this ONCE when the service starts
// Or use another process (eg, A command) to check once Just to confirm the suitability.
XDB_PUBLIC(int) xdb_verify(FILE *);

XDB_PUBLIC(int) xdb_verify_from_header(FILE *handle, xdb_header_t *);

XDB_PUBLIC(int) xdb_verify_from_file(const char *);

// --- End xdb buffer


// types type define
typedef char string_ip_t;
typedef unsigned char bytes_ip_t;

// --- ip version
#define XDB_IPv4 (xdb_version_v4())
#define XDB_IPv6 (xdb_version_v6())
typedef int (* ip_compare_fn_t) (const bytes_ip_t *, int, const char *, int);
struct xdb_ip_version_entry {
    int id;                 // version id
    char *name;             // version name
    int bytes;              // ip bytes number
    int segment_index_size; // segment index size in bytes

    // function to compare two ips
    ip_compare_fn_t ip_compare;
};
typedef struct xdb_ip_version_entry xdb_version_t;

XDB_PUBLIC(xdb_version_t *) xdb_version_v4();
XDB_PUBLIC(xdb_version_t *) xdb_version_v6();

XDB_PUBLIC(int) xdb_version_is_v4(const xdb_version_t *);
XDB_PUBLIC(int) xdb_version_is_v6(const xdb_version_t *);

XDB_PUBLIC(xdb_version_t *) xdb_version_from_name(char *);
XDB_PUBLIC(xdb_version_t *) xdb_version_from_header(xdb_header_t *);

// --- END ip version

// --- xdb util functions

// to compatiable with the windows
// returns: 0 for ok and -1 for failed
XDB_PUBLIC(int) xdb_init_winsock();
XDB_PUBLIC(void) xdb_clean_winsock();

// get the current time in microseconds
XDB_PUBLIC(long) xdb_now();

// get unsigned long (4bytes) from a specified buffer start from the specified offset with little-endian
XDB_PUBLIC(unsigned int) xdb_le_get_uint32(const char *, int);

// get unsigned short (2bytes) from a specified buffer start from the specified offset with little-endian
XDB_PUBLIC(int) xdb_le_get_uint16(const char *, int);


// parse the specified IP address to byte array.
// returns: xdb_version_t for valid ipv4 / ipv6, or NULL for failed
XDB_PUBLIC(xdb_version_t *) xdb_parse_ip(const string_ip_t *, bytes_ip_t *, size_t);

// parse the specified IPv4 address to byte array
// returns: xdb_version_t for valid ipv4, or NULL for failed
XDB_PUBLIC(xdb_version_t *) xdb_parse_v4_ip(const string_ip_t *, bytes_ip_t *, size_t);

// parse the specified IPv6 address to byte array
// returns: xdb_version_t for valid ipv6, or NULL for failed
XDB_PUBLIC(xdb_version_t *) xdb_parse_v6_ip(const string_ip_t *, bytes_ip_t *, size_t);

// convert a specified ip bytes to humen-readable string.
// returns: 0 for success or -1 for failed.
XDB_PUBLIC(int) xdb_ip_to_string(const bytes_ip_t *, int, char *, size_t);

// ipv4 bytes to string
XDB_PUBLIC(int) xdb_v4_ip_to_string(const bytes_ip_t *, char *, size_t);

// ipv6 bytes to string
XDB_PUBLIC(int) xdb_v6_ip_to_string(const bytes_ip_t *, char *, size_t);

// compare the specified ip bytes with another ip bytes in the specified buff from offset.
// ip args must be the return value from #xdb_parse_ip.
// returns: -1 if ip1 < ip2, 1 if ip1 > ip2 or 0
XDB_PUBLIC(int) xdb_ip_sub_compare(const bytes_ip_t *, int, const char *, int);

// large file seek and tell
XDB_PUBLIC(int) xdb_fseek(FILE *, long long, int);

XDB_PUBLIC(long long) xdb_ftell(FILE *);

// --- END xdb utils


// --- xdb searcher api

// xdb region info structure
#define xdb_region_buffer_wrapper 1
#define xdb_region_buffer_auto    2
struct xdb_region_buffer_entry {
    int type;           // buffer type
    char *value;        // region value
    size_t length;      // buffer length
};
typedef struct xdb_region_buffer_entry xdb_region_buffer_t;

// wrapper the region from a local stack buffer.
// returns: 0 for succeed or failed
XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *, char *, size_t);

// do the buffer alloc.
// returns: 0 for ok or failed
XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *, int);

// empty alloc - empty string
// returns:  0 - always
XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *);

XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *);

// xdb searcher structure
struct xdb_searcher_entry {
    // ip version
    xdb_version_t *version;

    // xdb file handle
    FILE *handle;

    // header info
    const char *header;
    int io_count;

    // vector index buffer cache.
    // preload the vector index will reduce the number of IO operations
    // thus speedup the search process.
    const xdb_vector_index_t *v_index;

    // content buffer.
    // cache the whole xdb content.
    const xdb_content_t *content;
};
typedef struct xdb_searcher_entry xdb_searcher_t;

// xdb searcher new api define
XDB_PUBLIC(int) xdb_new_with_file_only(xdb_version_t *, xdb_searcher_t *, const char *);

XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_version_t *, xdb_searcher_t *, const char *, const xdb_vector_index_t *);

XDB_PUBLIC(int) xdb_new_with_buffer(xdb_version_t *, xdb_searcher_t *, const xdb_content_t *);

XDB_PUBLIC(void) xdb_close(void *);

// xdb searcher search api define
XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);

XDB_PUBLIC(int) xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);

XDB_PUBLIC(xdb_version_t *) xdb_get_version(xdb_searcher_t *);

XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *);

// --- END xdb searcher api

#endif // C_IP2REGION_XDB_H


================================================
FILE: binding/c/xdb_searcher.c
================================================
// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.

// ---
// @Author Lion <chenxin619315@gmail.com>
// @Date   2022/06/27

#include "xdb_api.h"

// --- region buffer
XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *region, char *buffer, size_t length) {
    if (buffer == NULL) {
        region->type   = xdb_region_buffer_auto;
        region->length = 0;
    } else if (length <= 0) {
        return 1;
    } else {
        region->type   = xdb_region_buffer_wrapper;
        region->length = length;
        memset(buffer, 0x00, length);   // zero-fill the buffer
    }

    region->value = buffer;
    return 0;
}

XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *region, int length) {
    if (length <= 0) {
        return 1;
    }

    // no allocation supports for the buffer wapper
    if (region->type == xdb_region_buffer_wrapper) {
        if (length >= region->length) {
            return 2;
        }

        region->value[length] = '\0';
        return 0;
    }

    // ensure that the value were freed
    // by calling #xdb_region_buffer_free
    if (region->value != NULL) {
        return 3;
    }

    char *ptr = (char *) xdb_malloc(length + 1);
    if (ptr == NULL) {
        return 4;
    }

    ptr[length]    = '\0';  // NULL-end
    region->value  = ptr;
    region->length = length;
    return 0;
}

// fixed internal empty string ptr
static char * _empty_region_string = "\0";

XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *region) {
    // no allocation supports for the buffer wapper
    if (region->type == xdb_region_buffer_wrapper) {
        region->value[0] = '\0';
        return 0;
    }

    // ensure that the value were freed
    // by calling #xdb_region_buffer_free
    if (region->value != NULL) {
        return 3;
    }

    region->value  = _empty_region_string;
    region->length = 0;
    return 0;
}

XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *region) {
    if (region->type == xdb_region_buffer_auto) {
        // empty string interception
        if (region->length == 0 
            || region->value == _empty_region_string) {
            // do nothing for empty string
        } else {
            xdb_free(region->value);
        }

        // reset the value
        region->value = NULL;
    }
}

// --- END region buffer

// internal function prototype define
XDB_PRIVATE(int) read(xdb_searcher_t *, long offset, char *, size_t length);

XDB_PRIVATE(int) xdb_new_base(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index, const xdb_content_t *c_buffer) {
    memset(xdb, 0x00, sizeof(xdb_searcher_t));

    // set the version
    xdb->version = version;

    // check the content buffer first
    if (c_buffer != NULL) {
        xdb->v_index = NULL;
        xdb->content = c_buffer;
        return 0;
    }

    // open the xdb binary file
    FILE *handle = fopen(db_path, "rb");
    if (handle == NULL) {
        return 1;
    }

    xdb->handle = handle;
    xdb->v_index = v_index;

    return 0;
}

// xdb searcher new api define
XDB_PUBLIC(int) xdb_new_with_file_only(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path) {
    return xdb_new_base(version, xdb, db_path, NULL, NULL);
}

XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index) {
    return xdb_new_base(version, xdb, db_path, v_index, NULL);
}

XDB_PUBLIC(int) xdb_new_with_buffer(xdb_version_t *version, xdb_searcher_t *xdb, const xdb_content_t *c_buffer) {
    return xdb_new_base(version, xdb, NULL, NULL, c_buffer);
}

XDB_PUBLIC(void) xdb_close(void *ptr) {
    xdb_searcher_t *xdb = (xdb_searcher_t *) ptr;
    if (xdb->handle != NULL) {
        fclose(xdb->handle);
        xdb->handle = NULL;
    }
}

// --- xdb searcher search api define

XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *xdb, const string_ip_t *ip_string, xdb_region_buffer_t *region) {
    bytes_ip_t ip_bytes[16] = {'\0'};
    xdb_version_t *version = xdb_parse_ip(ip_string, ip_bytes, sizeof(ip_bytes));
    if (version == NULL) {
        return 10;
    } else {
        return xdb_search(xdb, ip_bytes, version->bytes, region);
    }
}

XDB_PUBLIC(int) xdb_search(xdb_searcher_t *xdb, const bytes_ip_t *ip_bytes, int ip_len, xdb_region_buffer_t *region) {
    int il0, il1, idx, err, bytes, d_bytes;
    register int seg_index_size, l, h, m, p;
    unsigned int s_ptr, e_ptr, data_ptr, data_len;
    char vector_buffer[xdb_vector_index_size];
    char segment_buffer[xdb_v6_index_size];

    // ip version check
    if (ip_len != xdb->version->bytes) {
        return -1;
    }

    // some resets
    err = 0;
    data_len = 0;
    bytes   = xdb->version->bytes;
    d_bytes = xdb->version->bytes << 1;
    xdb->io_count = 0;

    // locate the segment index block based on the vector index
    il0 = (int) (ip_bytes[0]);
    il1 = (int) (ip_bytes[1]);
    idx = il0 * xdb_vector_index_cols * xdb_vector_index_size + il1 * xdb_vector_index_size;
    if (xdb->v_index != NULL) {
        s_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx);
        e_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx + 4);
    } else if (xdb->content != NULL) {
        s_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx);
        e_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx + 4);
    } else {
        err = read(xdb, xdb_header_info_length + idx, vector_buffer, sizeof(vector_buffer));
        if (err != 0) {
            return 10 + err;
        }

        s_ptr = xdb_le_get_uint32(vector_buffer, 0);
        e_ptr = xdb_le_get_uint32(vector_buffer, 4);
    }

    // printf("s_ptr=%u, e_ptr=%u\n", s_ptr, e_ptr);
    // @Note: ptr validate, zero ptr means source data missing
    // so we could just stop here and return an empty string.
    if (s_ptr == 0 || e_ptr == 0) {
        xdb_region_buffer_empty(region);
        return err;
    }

    // binary search to get the final region info
    // segment_buffer = xdb_malloc(seg_index_size);
    seg_index_size = xdb->version->segment_index_size;
    data_len = 0, data_ptr = 0;
    l = 0, h = ((int) (e_ptr - s_ptr)) / seg_index_size;
    while (l <= h) {
        m = (l + h) >> 1;
        p = s_ptr + m * seg_index_size;

        // read the segment index item
        err = read(xdb, p, segment_buffer, seg_index_size);
        if (err != 0) {
            return 20 + err;
        }

        // decode the data fields as needed
        if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, 0) < 0) {
            h = m - 1;
        } else if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, bytes) > 0) {
            l = m + 1;
        } else {
            data_len = xdb_le_get_uint16(segment_buffer, d_bytes);
            data_ptr = xdb_le_get_uint32(segment_buffer, d_bytes + 2);
            break;
        }
    }

    // printf("data_len=%u, data_ptr=%u\n", data_len, data_ptr);
    if (data_len == 0) {
        // return 100;
        xdb_region_buffer_empty(region);
        return err;
    }

    // buffer alloc checking
    err = xdb_region_buffer_alloc(region, data_len);
    if (err != 0) {
        return 100 + err;
    }

    err = read(xdb, data_ptr, region->value, data_len);
    if (err != 0) {
        return 30 + err;
    }

    return err;
}

XDB_PRIVATE(int) read(xdb_searcher_t *xdb, long offset, char *buffer, size_t length) {
    // check the xdb content cache first
    if (xdb->content != NULL) {
        memcpy(buffer, xdb->content->buffer + offset, length);
        return 0;
    }

    // seek to the offset
    if (fseek(xdb->handle, offset, SEEK_SET) == -1) {
        return 1;
    }

    xdb->io_count++;
    if (fread(buffer, 1, length, xdb->handle) != length) {
        return 2;
    }

    return 0;
}

XDB_PUBLIC(xdb_version_t *) xdb_get_version(xdb_searcher_t *xdb) {
    return xdb->version;
}

XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *xdb) {
    return xdb->io_count;
}


================================================
FILE: binding/c/xdb_util.c
================================================
// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.

// ---
// @Author Lion <chenxin619315@gmail.com>
// @Date   2022/06/27

#include "xdb_api.h"
#include <ctype.h>

// for Linux
#ifdef XDB_LINUX
#include "sys/time.h"
#endif

#ifdef XDB_WINDOWS
#include <time.h>
#endif

// @Note: since 2023/10/13 to compatible with the windows system
#ifdef XDB_WINDOWS
static int winsock_initialized = 0;
XDB_PUBLIC(int) xdb_init_winsock() {
    if (winsock_initialized == 1) {
        return 0;
    }

    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
        return -1; 
    }
    winsock_initialized = 1;
    return 0;
}

XDB_PUBLIC(void) xdb_clean_winsock() {
    if (winsock_initialized == 1) {
        WSACleanup();
        winsock_initialized = 0;
    }
}

XDB_PRIVATE(int) gettimeofday(struct timeval* tp, void* tzp) {
    time_t clock;
    struct tm tm;
    SYSTEMTIME wtm;
    GetLocalTime(&wtm);
    tm.tm_year = wtm.wYear - 1900;
    tm.tm_mon = wtm.wMonth - 1;
    tm.tm_mday = wtm.wDay;
    tm.tm_hour = wtm.wHour;
    tm.tm_min = wtm.wMinute;
    tm.tm_sec = wtm.wSecond;
    tm.tm_isdst = -1;
    clock = mktime(&tm);
    tp->tv_sec = clock;
    tp->tv_usec = wtm.wMilliseconds * 1000;
    return (0);
}
#else
XDB_PUBLIC(int) xdb_init_winsock() {return 0;}
XDB_PUBLIC(void) xdb_clean_winsock() {}
#endif

// --- xdb buffer function implementations

XDB_PUBLIC(xdb_header_t *) xdb_load_header(FILE *handle) {
    xdb_header_t *header;
    unsigned int size = xdb_header_info_length;

    // entry alloc
    header = (xdb_header_t *) xdb_malloc(sizeof(xdb_header_t));
    if (header == NULL) {
        return NULL;
    }

    if (fseek(handle, 0, SEEK_SET) == -1) {
        xdb_free(header);
        return NULL;
    }

    if (fread(header->buffer, 1,size, handle) != size) {
        xdb_free(header);
        return NULL;
    }

    // fill the fields
    header->length = size;
    header->version = (unsigned short) xdb_le_get_uint16(header->buffer, 0);
    header->index_policy = (unsigned short) xdb_le_get_uint16(header->buffer, 2);
    header->created_at = xdb_le_get_uint32(header->buffer, 4);
    header->start_index_ptr = xdb_le_get_uint32(header->buffer, 8);
    header->end_index_ptr = xdb_le_get_uint32(header->buffer,12);

    // since IPv6 supporting
    header->ip_version = xdb_le_get_uint16(header->buffer, 16);
    header->runtime_ptr_bytes = xdb_le_get_uint16(header->buffer, 18);

    return header;
}

XDB_PUBLIC(xdb_header_t *) xdb_load_header_from_file(const char *db_path) {
    xdb_header_t *header;
    FILE *handle = fopen(db_path, "rb");
    if (handle == NULL) {
        return NULL;
    }

    header = xdb_load_header(handle);
    fclose(handle);
    return header;
}

XDB_PUBLIC(void) xdb_free_header(void *ptr) {
    xdb_header_t *header = (xdb_header_t *) ptr;
    if (header->length > 0) {
        header->length = 0;
        xdb_free(header);
    }
}

// --- vector index

XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index(FILE *handle) {
    xdb_vector_index_t *v_index;
    unsigned int size = xdb_vector_index_length;

    // seek to the vector index offset
    if (fseek(handle, xdb_header_info_length, SEEK_SET) == -1) {
        return NULL;
    }

    // do the buffer read
    v_index = (xdb_vector_index_t *) xdb_malloc(sizeof(xdb_vector_index_t));
    if (v_index == NULL) {
        return NULL;
    }

    v_index->length = size;
    if (fread(v_index->buffer, 1, size, handle) != size) {
        xdb_free(v_index);
        return NULL;
    }

    return v_index;
}

XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index_from_file(const char *db_path) {
    xdb_vector_index_t *v_index;
    FILE *handle = fopen(db_path, "rb");
    if (handle == NULL) {
        return NULL;
    }

    v_index = xdb_load_vector_index(handle);
    fclose(handle);
    return v_index;
}

XDB_PUBLIC(void) xdb_free_vector_index(void *ptr) {
    xdb_vector_index_t *v_index = (xdb_vector_index_t *) ptr;
    if (v_index->length > 0) {
        v_index->length = 0;
        xdb_free(v_index);
    }
}

// --- content buffer

XDB_PUBLIC(xdb_content_t *) xdb_load_content(FILE *handle) {
    unsigned int size;
    xdb_content_t *content;

    // determine the file size
    if (fseek(handle, 0, SEEK_END) == -1) {
        return NULL;
    }

    size = (unsigned int) ftell(handle);
    if (fseek(handle, 0, SEEK_SET) == -1) {
        return NULL;
    }

    // do the file read
    content = (xdb_content_t *) xdb_malloc(sizeof(xdb_content_t));
    if (content == NULL) {
        return NULL;
    }

    // do the buffer alloc
    content->buffer = (char *) xdb_malloc(size);
    if (content->buffer == NULL) {
        xdb_free(content);
        return NULL;
    }

    // read the content into the buffer
    content->length = size;
    if (fread(content->buffer, 1, size, handle) != size) {
        xdb_free(content);
        return NULL;
    }

    return content;
}

XDB_PUBLIC(xdb_content_t *) xdb_load_content_from_file(const char *db_path) {
    xdb_content_t *content;
    FILE *handle = fopen(db_path, "rb");
    if (handle == NULL) {
        return NULL;
    }

    content = xdb_load_content(handle);
    fclose(handle);
    return content;
}

XDB_PUBLIC(void) xdb_free_content(void *ptr) {
    xdb_content_t *content = (xdb_content_t *) ptr;
    if (content->length > 0) {
        content->length = 0;
        xdb_free(content->buffer);
        content->buffer = NULL;
        xdb_free(content);
    }
}

XDB_PUBLIC(int) xdb_verify_from_header(FILE *handle, xdb_header_t *header) {
    unsigned int runtime_ptr_bytes = 0;  // runtime ptr bytes
    if (header->version == xdb_structure_20) {
        runtime_ptr_bytes = 4;
    } else if (header->version == xdb_structure_30) {
        runtime_ptr_bytes = header->runtime_ptr_bytes;
    } else {
        return 2;
    }

    // 1, confirm the xdb file size.
    // to ensure that the maximum file pointer does not overflow.
    int err = fseek(handle, 0L, SEEK_END);
    if (err != 0) {
        return 3;
    }

    long long fileBytes = xdb_ftell(handle);
    long long maxFilePtr = (1LL << (runtime_ptr_bytes * 8)) - 1;
    // printf("fileBytes: %lld, maxFilePtr: %lld\n", fileBytes, maxFilePtr);
    if (fileBytes > maxFilePtr) {
        return 4;
    }

    return 0;
}

XDB_PUBLIC(int) xdb_verify(FILE *handle) {
    xdb_header_t *header = xdb_load_header(handle);
    if (header == NULL) {
        return 1;
    }

    int errcode = xdb_verify_from_header(handle, header);
    if (errcode != 0) {
        goto done;
    }

    // what next ?
done:
    xdb_free_header(header);
    return errcode;
}

XDB_PUBLIC(int) xdb_verify_from_file(const char *db_path) {
    FILE *handle = fopen(db_path, "rb");
    if (handle == NULL) {
        return -1;
    }

    int r = xdb_verify(handle);
    fclose(handle);
    return r;
}

// --- End content buffer


// --- ip version

// ip compare for IPv4
// ip1 - with Big endian byte order parsed from an input
// ip2 - with Little endian byte order read from the xdb index.
// to compatiable with the Little Endian encoded IPv4 on xdb 2.0.
XDB_PRIVATE(int) _ipv4_sub_compare(const bytes_ip_t *ip_bytes, int bytes, const char *buffer, int offset) {
    register int i0, i1;
    for (int i = 0, j = offset + bytes - 1; i < bytes; i++, j--) {
        i0 = ip_bytes[i];
        i1 = buffer[j] & 0xFF;
        if (i0 > i1) {
            return 1;
        } else if (i0 < i1) {
            return -1;
        }
    }
    return 0;
}

static xdb_version_t _ip_version_list[] = {
    // 14 = 4 + 4 + 2 + 4
    {xdb_ipv4_id, "IPv4", xdb_ipv4_bytes, xdb_v4_index_size, _ipv4_sub_compare},

    // 38 = 16 + 16 + 2 + 4
    {xdb_ipv6_id, "IPv6", xdb_ipv6_bytes, xdb_v6_index_size, xdb_ip_sub_compare},

    // END
    {0, NULL, 0, 0, NULL}
};

XDB_PUBLIC(xdb_version_t *) xdb_version_v4() {
    return &_ip_version_list[0];
}

XDB_PUBLIC(xdb_version_t *) xdb_version_v6() {
    return &_ip_version_list[1];
}

XDB_PUBLIC(int) xdb_version_is_v4(const xdb_version_t *version) {
    return version->id == xdb_ipv4_id;
}

XDB_PUBLIC(int) xdb_version_is_v6(const xdb_version_t *version) {
    return version->id == xdb_ipv6_id;
}

XDB_PUBLIC(xdb_version_t *) xdb_version_from_name(char *name) {
    // to upper case the name
    for (int i = 0; name[i] != '\0'; i++) {
        name[i] = toupper((unsigned char) name[i]);
    }

    if (strcmp(name, "V4") == 0 || strcmp(name, "IPV4") == 0) {
        return xdb_version_v4();
    } else if (strcmp(name, "V6") == 0 || strcmp(name, "IPV6") == 0) {
        return xdb_version_v6();
    } else {
        return NULL;
    }
}

XDB_PUBLIC(xdb_version_t *) xdb_version_from_header(xdb_header_t *header) {
    // Old structure with ONLY IPv4 supports
    if (header->version == xdb_structure_20) {
        return xdb_version_v4();
    }

    // structure 3.0 with IPv6 supporting
    if (header->version != xdb_structure_30) {
        return NULL;
    }

    if (header->ip_version == xdb_ipv4_id) {
        return xdb_version_v4();
    } else if (header->ip_version == xdb_ipv6_id) {
        return xdb_version_v6();
    } else {
        return NULL;
    }
}

// --- END ip version

XDB_PUBLIC(long) xdb_now() {
    struct timeval c_time;
    gettimeofday(&c_time, NULL);
    return c_time.tv_sec * (int)1e6 + c_time.tv_usec;
}

XDB_PUBLIC(unsigned int) xdb_le_get_uint32(const char *buffer, int offset) {
    return (
        ((buffer[offset  ]) & 0x000000FF) |
        ((buffer[offset+1] <<  8) & 0x0000FF00) |
        ((buffer[offset+2] << 16) & 0x00FF0000) |
        ((buffer[offset+3] << 24) & 0xFF000000)
    );
}

XDB_PUBLIC(int) xdb_le_get_uint16(const char *buffer, int offset) {
    return (
        ((buffer[offset  ]) & 0x000000FF) |
        ((buffer[offset+1] << 8) & 0x0000FF00)
    );
}

XDB_PUBLIC(xdb_version_t *) xdb_parse_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
    char *d_ptr = strchr(ip_string, '.');
    char *c_ptr = strchr(ip_string, ':');
    // version check
    if (d_ptr != NULL && c_ptr == NULL) {
        return xdb_parse_v4_ip(ip_string, buffer, length);
    } else if (c_ptr != NULL) {
        return xdb_parse_v6_ip(ip_string, buffer, length);
    }

    return NULL;
}

XDB_PUBLIC(xdb_version_t *) xdb_parse_v4_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
    struct in_addr addr;

    // buffer length checking
    if (length < xdb_ipv4_bytes) {
        return NULL;
    }

    if (inet_pton(AF_INET, ip_string, &addr) != 1) {
        return NULL;
    }

    // encode the address to buffer with big endian byte bufffer.
    buffer[0] = (addr.s_addr) & 0xFF;
    buffer[1] = (addr.s_addr >> 8) & 0xFF;
    buffer[2] = (addr.s_addr >> 16) & 0xFF;
    buffer[3] = (addr.s_addr >> 24) & 0xFF;
    return XDB_IPv4;
}

XDB_PUBLIC(xdb_version_t *) xdb_parse_v6_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
    struct  in6_addr addr;

    // buffer length checking
    if (length < xdb_ipv6_bytes) {
        return NULL;
    }

    if (inet_pton(AF_INET6, ip_string, &addr) != 1) {
        return NULL;
    }

    memcpy(buffer, addr.s6_addr, xdb_ipv6_bytes);
    return XDB_IPv6;
}

XDB_PUBLIC(int) xdb_ip_to_string(const bytes_ip_t *ip_bytes, int bytes, char *ip_string, size_t length) {
    if (bytes == xdb_ipv4_bytes) {
        return xdb_v4_ip_to_string(ip_bytes, ip_string, length);
    } else if (bytes == xdb_ipv6_bytes) {
        return xdb_v6_ip_to_string(ip_bytes, ip_string, length);
    }

    return -1;
}

XDB_PUBLIC(int) xdb_v4_ip_to_string(const bytes_ip_t *ip_bytes, char *ip_string, size_t length) {
    if (!ip_bytes || !ip_string || length == 0) {
        return -1;
    }

    // buffer length checking
    if (length < INET_ADDRSTRLEN) {
        return -1;
    }

    if (inet_ntop(AF_INET, ip_bytes, ip_string, length) == NULL) {
        return -1;
    }

    return 0;
}

XDB_PUBLIC(int) xdb_v6_ip_to_string(const bytes_ip_t *ip_bytes, char *ip_string, size_t length) {
    if (!ip_bytes || !ip_string || length == 0) {
        return -1;
    }

    if (length < INET6_ADDRSTRLEN) {
        return -1;
    }

    if (inet_ntop(AF_INET6, ip_bytes, ip_string, length) == NULL) {
        return -1;
    }

    return 0;
}

XDB_PUBLIC(int) xdb_ip_sub_compare(const bytes_ip_t *ip1, int bytes, const char *buffer, int offset) {
    register int i, i1, i2;
    for (i = 0; i < bytes; i++) {
        i1 = ip1[i];
        i2 = buffer[offset + i] & 0xFF;
        if (i1 > i2) {
            return 1;
        } else if (i1 < i2) {
            return -1;
        }
    }
    return 0;
}

XDB_PUBLIC(int) xdb_fseek(FILE *handle, long long offset, int whence) {
    // we may have to use the large file solution later
    // #if defined(XDB_LINUX)
    //     return fseeko(handle, (off_t) offset, whence);
    // #elif defined(XDB_WINDOWS)
    //     return _fseeki64(handle, (__int64) offset, whence)
    // #else
    //     return fseek(handle, (long) offset, whence);
    // #endif

    return fseek(handle, (long) offset, whence);
}

XDB_PUBLIC(long long) xdb_ftell(FILE *handle) {
    // we may have to use the large file solution later
    // #if defined(XDB_LINUX)
    //     return (long long) ftello(handle);
    // #elif defined(XDB_WINDOWS)
    //     return (long long) _ftelli64(handle);
    // #else
    //     // report error ?
    //     return (long long) ftell(handle);
    // #endif

    return (long long) ftell(handle);
}

================================================
FILE: binding/cangjie/.gitignore
================================================
# build cache
.cache/

# dependency lock
cjpm.lock


================================================
FILE: binding/cangjie/README.md
================================================
# ip2region Cangjie Query Client

This implementation supports both `IPv4` and `IPv6`, and provides a `thread-safe` service layer.

## Architecture

```
binding/cangjie/
├── src/
│   ├── xdb/              # Low-level xdb query engine
│   │   ├── searcher.cj   # Searcher (file-only / vector-index / content-buff modes)
│   │   ├── util.cj       # IP parsing/comparison, byte readers
│   │   ├── header.cj     # xdb header + version parsing
│   │   ├── version.cj    # IPv4/IPv6 version definitions
│   │   ├── *_test.cj     # Unit tests (24 cases)
│   ├── service/          # High-level thread-safe service layer
│   │   ├── config.cj     # Config (cache policy, pool size, pre-loaded data)
│   │   ├── searcher_pool.cj  # SearcherPool (Semaphore + ConcurrentLinkedQueue)
│   │   ├── ip2region.cj  # Ip2Region unified API
│   │   ├── *_test.cj     # Unit tests (17 cases)
├── example/              # Executable CLI demo (separate project)
│   ├── cjpm.toml
│   └── src/main.cj       # 5 usage demos + benchmarks
├── cjpm.toml             # Static library project
└── README.md
```

**Two API layers:**

- **`ip2region.xdb`** — Low-level Searcher. Not thread-safe (mutable `ioCount` + `File` seek/read state). Supports three cache modes.
- **`ip2region.service`** — High-level `Ip2Region` class wrapping `SearcherPool`. Thread-safe, handles v4/v6 dispatch automatically.

## Build

```bash
cd binding/cangjie
cjpm build          # static library
cd example
cjpm build          # example executable
```

## Test

41 tests across both packages (24 xdb + 17 service):

```bash
cd binding/cangjie
cjpm test
```

## Cache Policies

| Policy | Memory | Speed | Thread-safe* |
|--------|--------|-------|-------------|
| `FileOnly` (0) | ~0 MB | ~21 µs/op (v4) | Via SearcherPool |
| `VectorIndex` (1) | ~4 MB | ~20 µs/op (v4) | Via SearcherPool |
| `ContentBuff` (2) | ~full xdb | ~5 µs/op (v4) | Via SearcherPool |

\* Low-level `Searcher` is NOT thread-safe. The service layer (`SearcherPool` / `Ip2Region`) provides thread safety by pooling searchers (FileOnly/VectorIndex) or sharing a read-only buffer (ContentBuff).

## Benchmark Results

Benchmarked on Windows 11, AMD Ryzen 7, 487k IPv4 records / 638k IPv6 records:

### IPv4 (`ip2region_v4.xdb`, 487,167 records)

| Cache Policy | Total | Time | Avg |
|-------------|-------|------|-----|
| ContentBuff | 487,167 | 3,059 ms | **5 µs/op** |
| VectorIndex | 487,167 | 10,413 ms | **20 µs/op** |
| FileOnly | 487,167 | 11,271 ms | **21 µs/op** |

### IPv6 (`ip2region_v6.xdb`, 638,953 records)

| Cache Policy | Total | Time | Avg |
|-------------|-------|------|-----|
| ContentBuff | 638,953 | 37,080 ms | **56 µs/op** |
| VectorIndex | 638,953 | 38,443 ms | **58 µs/op** |
| FileOnly | 638,953 | 19,148 ms | **29 µs/op** |

Run your own:

```bash
cd example
cjpm run -- bench --db ../../../data/ip2region_v4.xdb --src ../../../data/ipv4_source.txt --cache-policy content
cjpm run -- bench --db ../../../data/ip2region_v6.xdb --src ../../../data/ipv6_source.txt --cache-policy file
```

## Usage

### Low-level API (`ip2region.xdb`)

```cangjie
import ip2region.xdb.*

// Content buffer mode (fastest)
let content = File.readFrom(Path("ip2region_v4.xdb"))
let header = newHeaderFromBytes(content)
let version = versionFromHeader(header)
let searcher = Searcher(version, content, "ip2region_v4.xdb")
let region = searcher.search(parseIP("220.181.108.183"))
searcher.close()

// Vector index mode
let vIndex = loadVectorIndex(content)
let searcher2 = Searcher(version, "ip2region_v4.xdb", vIndex)
searcher2.close()

// File only mode
let searcher3 = Searcher(version, "ip2region_v4.xdb")
searcher3.close()
```

### High-level API (`ip2region.service`)

```cangjie
import ip2region.service.*

// Via factory (VectorIndex, 20 searchers)
let region = newIp2Region("ip2region_v4.xdb", "ip2region_v6.xdb")
let result = region.search("220.181.108.183")
region.close()

// Custom config
let v4Cfg = Config(ContentBuff, IPv4, "ip2region_v4.xdb", 10)
let v6Cfg = Config(VectorIndex, IPv6, "ip2region_v6.xdb", 20)
let svc = Ip2Region(v4Cfg, v6Cfg)
let result = svc.search("2408:8266:100:1000::")
svc.close()
```

### Single-version API

```cangjie
import ip2region.service.*

// IPv4 only — Ip2Region(config, IPv4)
let v4Cfg = Config(ContentBuff, IPv4, "ip2region_v4.xdb", 10)
let v4 = Ip2Region(v4Cfg, IPv4)
let r4 = v4.search("220.181.108.183")
v4.close()

// IPv6 only — Ip2Region(config, IPv6)
let v6Cfg = Config(ContentBuff, IPv6, "ip2region_v6.xdb", 10)
let v6 = Ip2Region(v6Cfg, IPv6)
let r6 = v6.search("2408:8266:100:1000::")
v6.close()

// Factory functions
let v4Only = newIp2RegionV4(v4Cfg)
let v6Only = newIp2RegionV6(v6Cfg)
v4Only.close(); v6Only.close()
```

### CLI

```bash
cd example
cjpm run
```

## Thread Safety

- **`xdb.Searcher`** — NOT thread-safe. Keep one per thread or use a pool.
- **`service.SearcherPool`** — Thread-safe. Pre-allocates N searchers, uses `Semaphore` for backpressure and `ConcurrentLinkedQueue` for storage. `borrow()` blocks until a searcher is available.
- **`service.Ip2Region`** — Thread-safe. Delegates to pool or shared in-mem searcher based on cache policy. ContentBuff mode shares a single Searcher (Array<Byte> is read-only), FileOnly/VectorIndex use the pool.
- **`service.Config`** — Immutable after construction. Safe to share.

## Requirements

- Cangjie SDK 1.1.0+
- `cjpm` build tool


================================================
FILE: binding/cangjie/cjpm.toml
================================================
[package]
  cjc-version = "1.1.0"
  name = "ip2region"
  organization = ""
  description = "ip2region xdb searcher library for Cangjie"
  version = "1.0.0"
  target-dir = ""
  script-dir = ""
  output-type = "static"
  compile-option = "-Woff unused"
  override-compile-option = ""
  link-option = ""
  package-configuration = {}

[dependencies]


================================================
FILE: binding/cangjie/example/cjpm.toml
================================================
[package]
  cjc-version = "1.1.0"
  name = "ip2regionExample"
  version = "1.0.0"
  output-type = "executable"
  compile-option = "-Woff unused"
  description = "ip2region Cangjie CLI example"

[dependencies]
  ip2region = { path = ".." }


================================================
FILE: binding/cangjie/example/src/main.cj
================================================
package ip2regionExample

import std.fs.*
import std.time.*
import std.collection.*
import ip2region.xdb.*
import ip2region.service.*

// ip2region Cangjie 使用示例
//
// 构建: cjpm build
// 运行: cjpm run
//
// 默认从 ../../../data/ 查找 xdb 文件,
// 也可通过命令行参数指定路径:
//   cjpm run -- --v4-db <path> --v6-db <path>

func printHelp() {
    println("ip2region Cangjie 使用示例")
    println("用法: cjpm run -- [options]")
    println("选项:")
    println("  --v4-db <path>   IPv4 xdb 文件路径 (默认: ../../../data/ip2region_v4.xdb)")
    println("  --v6-db <path>   IPv6 xdb 文件路径 (默认: ../../../data/ip2region_v6.xdb)")
    println("  --help           显示此帮助")
}

// ============================================================
// 方式一: 使用底层 Searcher API (ip2region.xdb)
// 可自行选择缓存策略,单线程使用
// ============================================================
func demoXdbSearcher(v4Path: String, v6Path: String) {
    println("\n=== 方式一: 底层 Searcher API ===")

    // 1. 读取 xdb 文件到内存
    let v4Content = File.readFrom(Path(v4Path))

    // 2. 解析文件头,获取版本信息
    let v4Header = newHeaderFromBytes(v4Content)
    let v4Version = versionFromHeader(v4Header)

    // 3. 选择缓存策略创建 Searcher
    //    支持三种模式: ContentBuff(最快), VectorIndex(均衡), FileOnly(省内存)
    let vIndex = loadVectorIndex(v4Content)
    let searcher = Searcher(v4Version, v4Path, vIndex)  // VectorIndex 模式

    // 4. 查询 IP
    let ip = parseIP("220.181.108.183")
    let region = searcher.search(ip)
    println("  IP: 220.181.108.183")
    println("  结果: ${region}")

    searcher.close()
}

// ============================================================
// 方式二: 使用 Ip2Region 服务 API (ip2region.service)
// 自动管理连接池,支持多线程安全访问
// ============================================================
func demoIp2Region(v4Path: String, v6Path: String) {
    println("\n=== 方式二: Ip2Region 服务 API ===")

    // 1. 创建配置 (缓存策略 + 连接池大小)
    let v4Cfg = Config(VectorIndex, IPv4, v4Path, 10)
    let v6Cfg = Config(VectorIndex, IPv6, v6Path, 10)

    // 2. 创建服务 (同时支持 IPv4 + IPv6)
    let region = Ip2Region(v4Cfg, v6Cfg)

    // 3. 查询 IPv4
    let r1 = region.search("220.181.108.183")
    println("  IPv4: 220.181.108.183 -> ${r1}")

    // 4. 查询 IPv6
    let r2 = region.search("2408:8266:100:1000::")
    println("  IPv6: 2408:8266:100:1000:: -> ${r2}")

    region.close()
}

// ============================================================
// 方式三: 单版本服务 (仅 IPv4 或 仅 IPv6)
// 使用 init(config, ipVersion) 构造函数
// ============================================================
func demoSingleVersion(v4Path: String, v6Path: String) {
    println("\n=== 方式三: 单版本服务 ===")

    // IPv4 only
    let v4Cfg = Config(ContentBuff, IPv4, v4Path, 3)
    let v4Region = Ip2Region(v4Cfg, IPv4)
    let r4 = v4Region.search("220.181.108.183")
    println("  IPv4 only: 220.181.108.183 -> ${r4}")
    v4Region.close()

    // IPv6 only
    let v6Cfg = Config(ContentBuff, IPv6, v6Path, 3)
    let v6Region = Ip2Region(v6Cfg, IPv6)
    let r6 = v6Region.search("2408:8266:100:1000::")
    println("  IPv6 only: 2408:8266:100:1000:: -> ${r6}")
    v6Region.close()
}

// ============================================================
// 方式四: 使用简便工厂函数 (ip2region.service)
// 一行创建,默认 VectorIndex + 20 连接池
// ============================================================
func demoFactory(v4Path: String, v6Path: String) {
    println("\n=== 方式四: 简便工厂函数 ===")

    let region = newIp2Region(v4Path, v6Path)
    let result = region.search("220.181.108.183")
    println("  220.181.108.183 -> ${result}")
    region.close()
}

// ============================================================
// 性能测试: 测试三种缓存策略的查询速度
// ============================================================
func bench(v4Path: String) {
    println("\n=== 性能测试 (IPv4, 487k 条记录) ===")

    let content = File.readFrom(Path(v4Path))
    let header = newHeaderFromBytes(content)
    let version = versionFromHeader(header)
    let vIndex = loadVectorIndex(content)

    // 从数据文件读取测试 IP 列表
    let srcPath = v4Path.replace("ip2region_v4.xdb", "ipv4_source.txt")
    let srcContent = File.readFrom(Path(srcPath))
    let lines = String.fromUtf8(srcContent).split("\n")

    var ipList = ArrayList<Array<Byte>>()
    for (line in lines) {
        let trimmed = line.trimAsciiStart().trimAsciiEnd()
        if (trimmed.size == 0) { continue }
        let parts = trimmed.split("|")
        if (parts.size < 3) { continue }
        try { ipList.add(parseIP(parts[0])) } catch (_) { continue }
    }
    println("  加载了 ${ipList.size} 个 IP")

    // 测试 ContentBuff 模式
    println("\n  [ContentBuff 模式]")
    let s1 = Searcher(version, content, v4Path)
    benchSearcher(s1, ipList)

    // 测试 VectorIndex 模式
    println("  [VectorIndex 模式]")
    let s2 = Searcher(version, v4Path, vIndex)
    benchSearcher(s2, ipList)

    // 测试 FileOnly 模式
    println("  [FileOnly 模式]")
    let s3 = Searcher(version, v4Path)
    benchSearcher(s3, ipList)

    s1.close(); s2.close(); s3.close()
}

func benchSearcher(searcher: Searcher, ipList: ArrayList<Array<Byte>>) {
    let count = ipList.size
    let start = MonoTime.now()
    for (i in 0..count) {
        let _ = searcher.search(ipList[i])
    }
    let elapsed = MonoTime.now() - start
    let avg = elapsed.toMicroseconds() / count
    println("    总量: ${count}, 耗时: ${elapsed.toMilliseconds()} ms, 平均: ${avg} us/op")
}

main(args: Array<String>): Int64 {
    var v4Path = "../../../data/ip2region_v4.xdb"
    var v6Path = "../../../data/ip2region_v6.xdb"

    var i = 0
    while (i < args.size) {
        if (args[i] == "--v4-db" && i + 1 < args.size) {
            v4Path = args[i + 1]; i = i + 2
        } else if (args[i] == "--v6-db" && i + 1 < args.size) {
            v6Path = args[i + 1]; i = i + 2
        } else if (args[i] == "--help") {
            printHelp(); return 0
        } else { i = i + 1 }
    }

    println("ip2region Cangjie 使用示例")
    println("xdb 路径: ${v4Path}")

    demoXdbSearcher(v4Path, v6Path)
    demoIp2Region(v4Path, v6Path)
    demoSingleVersion(v4Path, v6Path)
    demoFactory(v4Path, v6Path)
    bench(v4Path)

    println("\n完成!")
    return 0
}


================================================
FILE: binding/cangjie/src/main.cj
================================================
package ip2region

import std.fs.*
import std.time.*
import std.env.*
import ip2region.xdb.*

func printHelp() {
    println("ip2region xdb searcher")
    println("Usage: ip2region [command] [command options]")
    println("Command:")
    println("  search    search input test")
    println("  bench     search bench test")
}

func runSearch(args: Array<String>) {
    var v4DbPath = "../../data/ip2region_v4.xdb"
    var v6DbPath = "../../data/ip2region_v6.xdb"
    var v4CachePolicy = "vectorIndex"
    var v6CachePolicy = "vectorIndex"
    var showHelp = false

    var i = 1 // skip command name
    while (i < args.size) {
        let arg = args[i]
        if (arg == "--v4-db" && i + 1 < args.size) {
            v4DbPath = args[i + 1]
            i = i + 2
        } else if (arg == "--v6-db" && i + 1 < args.size) {
            v6DbPath = args[i + 1]
            i = i + 2
        } else if (arg == "--v4-cache-policy" && i + 1 < args.size) {
            v4CachePolicy = args[i + 1]
            i = i + 2
        } else if (arg == "--v6-cache-policy" && i + 1 < args.size) {
            v6CachePolicy = args[i + 1]
            i = i + 2
        } else if (arg == "--help") {
            showHelp = true
            i = i + 1
        } else {
            i = i + 1
        }
    }

    if (showHelp) {
        println("ip2region search [command options]")
        println("options:")
        println(" --v4-db <path>            ip2region v4 binary xdb file path")
        println(" --v4-cache-policy <mode>  v4 cache policy: file/vectorIndex/content")
        println(" --v6-db <path>            ip2region v6 binary xdb file path")
        println(" --v6-cache-policy <mode>  v6 cache policy: file/vectorIndex/content")
        println(" --help                    print this help")
        return
    }

    // Load v4 xdb
    print("Loading v4 xdb ... ")
    let v4Content = File.readFrom(Path(v4DbPath))
    let v4Header = newHeaderFromBytes(v4Content)
    let v4Version = versionFromHeader(v4Header)
    let v4Searcher = createSearcher(v4Version, v4Content, v4DbPath, v4CachePolicy)
    println("done")

    // Load v6 xdb
    print("Loading v6 xdb ... ")
    let v6Content = File.readFrom(Path(v6DbPath))
    let v6Header = newHeaderFromBytes(v6Content)
    let v6Version = versionFromHeader(v6Header)
    let v6Searcher = createSearcher(v6Version, v6Content, v6DbPath, v6CachePolicy)
    println("done")

    println("ip2region search service test program")
    println("type 'quit' to exit")

    let reader = getStdIn()
    while (true) {
        print("ip2region>> ")
        let lineOpt = reader.readln()
        if (lineOpt == None) {
            break
        }
        let line = lineOpt.getOrThrow()
        if (line == "quit" || line == "exit") {
            break
        }
        if (line.size == 0) {
            continue
        }

        let start = MonoTime.now()
        let region = searchIP(line, v4Searcher, v6Searcher)
        let elapsed = MonoTime.now() - start
        println("${line} -> ${region}  (took: ${elapsed.toMicroseconds()} us)")
    }

    v4Searcher.close()
    v6Searcher.close()
}

func runBench(args: Array<String>) {
    var dbFile = ""
    var srcFile = ""
    var cachePolicy = "content"

    var i = 1 // skip command name
    while (i < args.size) {
        let arg = args[i]
        if (arg == "--db" && i + 1 < args.size) {
            dbFile = args[i + 1]
            i = i + 2
        } else if (arg == "--src" && i + 1 < args.size) {
            srcFile = args[i + 1]
            i = i + 2
        } else if (arg == "--cache-policy" && i + 1 < args.size) {
            cachePolicy = args[i + 1]
            i = i + 2
        } else {
            i = i + 1
        }
    }

    if (dbFile == "" || srcFile == "") {
        println("ip2region bench [command options]")
        println("options:")
        println(" --db <path>              ip2region binary xdb file path")
        println(" --src <path>             source ip text file path")
        println(" --cache-policy <mode>    cache policy: file/vectorIndex/content")
        return
    }

    println("Loading xdb from: ${dbFile}")
    let content = File.readFrom(Path(dbFile))
    let header = newHeaderFromBytes(content)
    let version = versionFromHeader(header)
    let searcher = createSearcher(version, content, dbFile, cachePolicy)

    println("Loading source data from: ${srcFile}")
    let srcContent = File.readFrom(Path(srcFile))
    let srcStr = String.fromUtf8(srcContent)
    let lines = srcStr.split("\n")

    var count: Int64 = 0
    var totalCost: Int64 = 0
    let tStart = MonoTime.now()

    for (line in lines) {
        let trimmed = line.trimAsciiStart().trimAsciiEnd()
        if (trimmed.size == 0) {
            continue
        }

        let parts = trimmed.split("|")
        if (parts.size < 3) {
            continue
        }

        let ipBytes = parseIP(parts[0])
        let t0 = MonoTime.now()
        let _ = searcher.search(ipBytes)
        let t1 = MonoTime.now()
        totalCost = totalCost + (t1 - t0).toNanoseconds()
        count = count + 1

        if (count % 10000 == 0) {
            print(".")
        }
    }

    let tEnd = MonoTime.now()
    let totalTime = tEnd - tStart

    println("")
    println("Bench finished:")
    println("  cachePolicy: ${cachePolicy}")
    println("  total: ${count}")
    println("  took: ${totalTime.toMilliseconds()} ms")
    if (count > 0) {
        println("  avg: ${totalCost / count / 1000} us/op")
    }

    searcher.close()
}

func createSearcher(version: Version, content: Array<Byte>, dbPath: String, cachePolicy: String): Searcher {
    if (cachePolicy == "content") {
        println("Using content buffer mode")
        return Searcher(version, content, dbPath)
    } else if (cachePolicy == "vectorIndex") {
        println("Using vector index mode")
        let vIndex = loadVectorIndex(content)
        return Searcher(version, dbPath, vIndex)
    } else {
        println("Using file only mode")
        return Searcher(version, dbPath)
    }
}

func searchIP(ipStr: String, v4Searcher: Searcher, v6Searcher: Searcher): String {
    let ipBytes = parseIP(ipStr)
    if (ipBytes.size == 4) {
        return v4Searcher.search(ipBytes)
    } else if (ipBytes.size == 16) {
        return v6Searcher.search(ipBytes)
    }
    return "Invalid IP"
}

main(args: Array<String>): Int64 {
    if (args.size == 0) {
        printHelp()
        return 0
    }

    let cmd = args[0]
    if (cmd == "search") {
        runSearch(args)
    } else if (cmd == "bench") {
        runBench(args)
    } else {
        printHelp()
    }

    return 0
}


================================================
FILE: binding/cangjie/src/service/config.cj
================================================
package ip2region.service

import std.fs.*
import ip2region.xdb.*

// Cache policy constants - match Searcher modes
public let FileOnly: Int64 = 0
public let VectorIndex: Int64 = 1
public let ContentBuff: Int64 = 2

// Config holds xdb file configuration and pre-loaded data for creating Searcher instances
public class Config {
    public let cachePolicy: Int64
    public let ipVersion: Version
    public let xdbPath: String
    public let header: Header
    public let vIndex: Array<Byte>
    public let cBuffer: Array<Byte>
    public let searchers: Int64

    public init(cachePolicy: Int64, ipVersion: Version, xdbPath: String, searchers: Int64) {
        if (searchers <= 0) {
            throw Exception("searchers must be > 0")
        }

        this.cachePolicy = cachePolicy
        this.ipVersion = ipVersion
        this.xdbPath = xdbPath
        this.searchers = searchers

        let content = File.readFrom(Path(xdbPath))
        this.header = newHeaderFromBytes(content)

        // Verify IP version matches
        let detectedVersion = versionFromHeader(this.header)
        if (detectedVersion.id != ipVersion.id) {
            throw Exception("xdb file IP version mismatch: expected ${ipVersion.name}, got ${detectedVersion.name}")
        }

        if (cachePolicy == VectorIndex) {
            this.vIndex = loadVectorIndex(content)
            this.cBuffer = Array<Byte>(0, repeat: 0)
        } else if (cachePolicy == ContentBuff) {
            this.vIndex = Array<Byte>(0, repeat: 0)
            this.cBuffer = content
        } else {
            this.vIndex = Array<Byte>(0, repeat: 0)
            this.cBuffer = Array<Byte>(0, repeat: 0)
        }
    }

    public init(cachePolicy: Int64, ipVersion: Version, xdbPath: String) {
        this(cachePolicy, ipVersion, xdbPath, 20)
    }
}

// Static factories for IPv4 and IPv6
public func newV4Config(cachePolicy: Int64, xdbPath: String, searchers: Int64): Config {
    return Config(cachePolicy, IPv4, xdbPath, searchers)
}

public func newV6Config(cachePolicy: Int64, xdbPath: String, searchers: Int64): Config {
    return Config(cachePolicy, IPv6, xdbPath, searchers)
}

public func newV4Config(cachePolicy: Int64, xdbPath: String): Config {
    return Config(cachePolicy, IPv4, xdbPath, 20)
}

public func newV6Config(cachePolicy: Int64, xdbPath: String): Config {
    return Config(cachePolicy, IPv6, xdbPath, 20)
}


================================================
FILE: binding/cangjie/src/service/ip2region.cj
================================================
package ip2region.service

import ip2region.xdb.*

// Ip2Region is the high-level API for searching IP addresses.
// It supports both IPv4 and IPv6 with thread-safe access via SearcherPool.
public class Ip2Region {
    let v4Pool: SearcherPool
    let v6Pool: SearcherPool
    let v4InMemSearcher: Searcher
    let v6InMemSearcher: Searcher
    let hasV4: Bool
    let hasV6: Bool
    let v4IsInMem: Bool
    let v6IsInMem: Bool

    // Create Ip2Region with both v4 and v6 configurations.
    public init(v4Config: Config, v6Config: Config) {
        // v4 setup
        if (v4Config.cachePolicy == ContentBuff) {
            this.hasV4 = true
            this.v4IsInMem = true
            this.v4InMemSearcher = Searcher(v4Config.ipVersion, v4Config.cBuffer, v4Config.xdbPath)
            this.v4Pool = SearcherPool(v4Config)
        } else {
            this.hasV4 = true
            this.v4IsInMem = false
            this.v4InMemSearcher = Searcher(v4Config.ipVersion, Array<Byte>(0, repeat: 0), v4Config.xdbPath)
            this.v4Pool = SearcherPool(v4Config)
        }

        // v6 setup
        if (v6Config.cachePolicy == ContentBuff) {
            this.hasV6 = true
            this.v6IsInMem = true
            this.v6InMemSearcher = Searcher(v6Config.ipVersion, v6Config.cBuffer, v6Config.xdbPath)
            this.v6Pool = SearcherPool(v6Config)
        } else {
            this.hasV6 = true
            this.v6IsInMem = false
            this.v6InMemSearcher = Searcher(v6Config.ipVersion, Array<Byte>(0, repeat: 0), v6Config.xdbPath)
            this.v6Pool = SearcherPool(v6Config)
        }
    }

    // Single-version: provide Config and IP version.
    // Examples:
    //   Ip2Region(v4Config, IPv4)  — IPv4 only
    //   Ip2Region(v6Config, IPv6)  — IPv6 only
    public init(config: Config, ipVersion: Version) {
        if (ipVersion.id == IPv4VersionNo) {
            this.hasV4 = true
            this.v4IsInMem = (config.cachePolicy == ContentBuff)
            if (config.cachePolicy == ContentBuff) {
                this.v4InMemSearcher = Searcher(config.ipVersion, config.cBuffer, config.xdbPath)
            } else {
                this.v4InMemSearcher = Searcher(config.ipVersion, Array<Byte>(0, repeat: 0), config.xdbPath)
            }
            this.v4Pool = SearcherPool(config)
            // IPv6 disabled
            this.hasV6 = false
            this.v6IsInMem = false
            this.v6InMemSearcher = Searcher(IPv4, Array<Byte>(0, repeat: 0), config.xdbPath)
            this.v6Pool = SearcherPool(Config(ContentBuff, IPv4, config.xdbPath, 1))
        } else {
            this.hasV6 = true
            this.v6IsInMem = (config.cachePolicy == ContentBuff)
            if (config.cachePolicy == ContentBuff) {
                this.v6InMemSearcher = Searcher(config.ipVersion, config.cBuffer, config.xdbPath)
            } else {
                this.v6InMemSearcher = Searcher(config.ipVersion, Array<Byte>(0, repeat: 0), config.xdbPath)
            }
            this.v6Pool = SearcherPool(config)
            // IPv4 disabled
            this.hasV4 = false
            this.v4IsInMem = false
            this.v4InMemSearcher = Searcher(IPv6, Array<Byte>(0, repeat: 0), config.xdbPath)
            this.v4Pool = SearcherPool(Config(ContentBuff, IPv6, config.xdbPath, 1))
        }
    }

    // Search an IP string and return the region info.
    public func search(ipStr: String): String {
        try {
            let ipBytes = parseIP(ipStr)

            if (ipBytes.size == 4) {
                return this.searchV4(ipBytes)
            } else if (ipBytes.size == 16) {
                return this.searchV6(ipBytes)
            }
        } catch (_) {
            return "Invalid IP"
        }
        return "Invalid IP"
    }

    func searchV4(ip: Array<Byte>): String {
        if (!this.hasV4) {
            return ""
        }
        if (this.v4IsInMem) {
            return this.v4InMemSearcher.search(ip)
        }
        let searcher = this.v4Pool.borrow()
        let region = searcher.search(ip)
        this.v4Pool.return_(searcher)
        return region
    }

    func searchV6(ip: Array<Byte>): String {
        if (!this.hasV6) {
            return ""
        }
        if (this.v6IsInMem) {
            return this.v6InMemSearcher.search(ip)
        }
        let searcher = this.v6Pool.borrow()
        let region = searcher.search(ip)
        this.v6Pool.return_(searcher)
        return region
    }

    // close releases all resources.
    public func close() {
        if (this.hasV4) {
            this.v4Pool.close()
        }
        if (this.hasV6) {
            this.v6Pool.close()
        }
    }

    // closeTimeout releases all resources with the specified timeout.
    public func closeTimeout(timeout: Duration) {
        if (this.hasV4) {
            this.v4Pool.closeTimeout(timeout)
        }
        if (this.hasV6) {
            this.v6Pool.closeTimeout(timeout)
        }
    }
}

// Create an Ip2Region instance using xdb file paths.
// Uses VectorIndex cache policy with 20 searchers by default.
public func newIp2Region(v4XdbPath: String, v6XdbPath: String): Ip2Region {
    let v4Config = Config(VectorIndex, IPv4, v4XdbPath, 20)
    let v6Config = Config(VectorIndex, IPv6, v6XdbPath, 20)
    return Ip2Region(v4Config, v6Config)
}

// Create an IPv4-only Ip2Region service.
public func newIp2RegionV4(config: Config): Ip2Region {
    return Ip2Region(config, IPv4)
}

// Create an IPv6-only Ip2Region service.
public func newIp2RegionV6(config: Config): Ip2Region {
    return Ip2Region(config, IPv6)
}


================================================
FILE: binding/cangjie/src/service/searcher_pool.cj
================================================
package ip2region.service

import std.sync.*
import std.core.*
import std.time.*
import std.collection.concurrent.*
import ip2region.xdb.*

// SearcherPool is a thread-safe pool of xdb.Searcher instances.
// Uses Semaphore for backpressure and ConcurrentLinkedQueue for storage.
public class SearcherPool {
    let config: Config
    let queue: ConcurrentLinkedQueue<Searcher>
    let semaphore: Semaphore
    let closing: AtomicBool
    let loanCnt: AtomicInt64

    public init(config: Config) {
        this.config = config
        this.queue = ConcurrentLinkedQueue<Searcher>()
        this.semaphore = Semaphore(config.searchers)
        this.closing = AtomicBool(false)
        this.loanCnt = AtomicInt64(0)

        let poolSize = config.searchers
        for (_ in 0..poolSize) {
            let searcher = createPoolSearcher(config)
            this.queue.add(searcher)
            this.semaphore.release(amount: 1)
        }
    }

    // borrow takes a Searcher from the pool, blocking until one is available.
    // Throws if the pool is closing or closed.
    public func borrow(): Searcher {
        while (!this.closing.load()) {
            if (this.semaphore.tryAcquire(amount: 1)) {
                let opt = this.queue.remove()
                if (opt.isNone()) {
                    this.semaphore.release(amount: 1)
                    continue
                }
                let s = opt.getOrThrow()
                this.loanCnt.fetchAdd(1)
                return s
            }
            sleep(Duration.millisecond * 5)
        }
        throw Exception("SearcherPool is closing")
    }

    // return_ returns a Searcher to the pool, or closes it if the pool is shutting down.
    public func return_(searcher: Searcher) {
        if (this.closing.load()) {
            searcher.close()
        } else {
            this.queue.add(searcher)
            this.semaphore.release(amount: 1)
        }
        this.loanCnt.fetchSub(1)
    }

    // close closes the pool with a default 10 second timeout.
    public func close() {
        this.closeTimeout(Duration.second * 10)
    }

    // closeTimeout closes the pool with the specified timeout.
    public func closeTimeout(timeout: Duration) {
        this.closing.store(true)

        // Drain the queue: acquire all permits and close searchers
        while (true) {
            if (!this.semaphore.tryAcquire(amount: 1)) {
                break
            }
            try {
                let opt = this.queue.remove()
                if (opt.isSome()) {
                    opt.getOrThrow().close()
                }
            } catch (_) {
                break
            }
        }

        // Wait for outstanding loans to return (with timeout)
        let deadline = MonoTime.now() + timeout
        while (this.loanCnt.load() > 0) {
            if (MonoTime.now() >= deadline) {
                break
            }
            sleep(Duration.millisecond * 10)
        }
    }

    public func getLoanCount(): Int64 {
        return this.loanCnt.load()
    }
}

// Helper to create a Searcher based on Config's cache policy
func createPoolSearcher(config: Config): Searcher {
    if (config.cachePolicy == ContentBuff) {
        return Searcher(config.ipVersion, config.cBuffer, config.xdbPath)
    } else if (config.cachePolicy == VectorIndex) {
        return Searcher(config.ipVersion, config.xdbPath, config.vIndex)
    } else {
        return Searcher(config.ipVersion, config.xdbPath)
    }
}


================================================
FILE: binding/cangjie/src/tests/service/config_test.cj
================================================
package ip2region.tests.service

import ip2region.xdb.*
import ip2region.service.*

@Test
class ConfigV4Tests {
    @TestCase
    func testCreateFileOnly() {
        let cfg = Config(FileOnly, IPv4, "../../data/ip2region_v4.xdb", 5)
        if (cfg.cachePolicy != FileOnly) { throw Exception("wrong cache policy") }
        if (cfg.searchers != 5) { throw Exception("wrong searchers count") }
        if (cfg.ipVersion.id != IPv4VersionNo) { throw Exception("wrong version") }
    }

    @TestCase
    func testCreateVectorIndex() {
        let cfg = Config(VectorIndex, IPv4, "../../data/ip2region_v4.xdb", 10)
        if (cfg.vIndex.size == 0) { throw Exception("vIndex should be loaded") }
        let expectedSize = VectorIndexRows * VectorIndexCols * VectorIndexSize
        if (cfg.vIndex.size != expectedSize) {
            throw Exception("vIndex size mismatch: ${cfg.vIndex.size} vs ${expectedSize}")
        }
    }

    @TestCase
    func testCreateContentBuff() {
        let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 3)
        if (cfg.cBuffer.size == 0) { throw Exception("cBuffer should be loaded") }
    }

    @TestCase
    func testFactoryV4() {
        let cfg = newV4Config(VectorIndex, "../../data/ip2region_v4.xdb", 8)
        if (cfg.ipVersion.id != IPv4VersionNo) { throw Exception("should be IPv4") }
        if (cfg.searchers != 8) { throw Exception("wrong searchers") }
    }

    @TestCase
    func testFactoryV6() {
        let cfg = newV6Config(VectorIndex, "../../data/ip2region_v6.xdb", 8)
        if (cfg.ipVersion.id != IPv6VersionNo) { throw Exception("should be IPv6") }
    }

    @TestCase
    func testFactoryDefaultSearchers() {
        let cfg = newV4Config(ContentBuff, "../../data/ip2region_v4.xdb")
        if (cfg.searchers != 20) { throw Exception("default searchers should be 20") }
    }
}


================================================
FILE: binding/cangjie/src/tests/service/ip2region_test.cj
================================================
package ip2region.tests.service

import ip2region.xdb.*
import ip2region.service.*

@Test
class Ip2RegionBasicTests {
    @TestCase
    func testSearchWithContentBuff() {
        let v4Cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 1)
        let v6Cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 1)
        let region = Ip2Region(v4Cfg, v6Cfg)
        let r1 = region.search("220.181.108.183")
        if (r1.size == 0) {
            throw Exception("v4 search should return result")
        }
        let r2 = region.search("2408:8266:100:1000::")
        if (r2.size == 0) {
            throw Exception("v6 search should return result")
        }
        region.close()
    }

    @TestCase
    func testSearchWithVectorIndex() {
        let v4Cfg = Config(VectorIndex, IPv4, "../../data/ip2region_v4.xdb", 5)
        let v6Cfg = Config(VectorIndex, IPv6, "../../data/ip2region_v6.xdb", 5)
        let region = Ip2Region(v4Cfg, v6Cfg)
        let r = region.search("220.181.108.183")
        if (r.size == 0) {
            throw Exception("v4 search should return result")
        }
        region.close()
    }

    @TestCase
    func testInvalidIP() {
        let v4Cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 1)
        let v6Cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 1)
        let region = Ip2Region(v4Cfg, v6Cfg)
        let r = region.search("invalid")
        if (r != "Invalid IP") {
            throw Exception("expected 'Invalid IP', got '${r}'")
        }
        region.close()
    }

    @TestCase
    func testClose() {
        let v4Cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 1)
        let v6Cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 1)
        let region = Ip2Region(v4Cfg, v6Cfg)
        region.search("220.181.108.183")
        region.close()
    }

    @TestCase
    func testV4OnlyInit() {
        let cfg = Config(VectorIndex, IPv4, "../../data/ip2region_v4.xdb", 3)
        let region = Ip2Region(cfg, IPv4)
        let r = region.search("220.181.108.183")
        if (r.size == 0) {
            throw Exception("v4-only region should find v4 IP")
        }
        let r6 = region.search("2408:8266:100:1000::")
        if (r6.size != 0) {
            throw Exception("v4-only region should not find v6 IP")
        }
        region.close()
    }

    @TestCase
    func testV6OnlyInit() {
        let cfg = Config(VectorIndex, IPv6, "../../data/ip2region_v6.xdb", 3)
        let region = Ip2Region(cfg, IPv6)
        let r = region.search("2408:8266:100:1000::")
        if (r.size == 0) {
            throw Exception("v6-only region should find v6 IP")
        }
        let r4 = region.search("220.181.108.183")
        if (r4.size != 0) {
            throw Exception("v6-only region should not find v4 IP")
        }
        region.close()
    }

    @TestCase
    func testNewV4Factory() {
        let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 2)
        let region = newIp2RegionV4(cfg)
        let r = region.search("220.181.108.183")
        if (r.size == 0) {
            throw Exception("newIp2RegionV4 should work")
        }
        region.close()
    }

    @TestCase
    func testNewV6Factory() {
        let cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 2)
        let region = newIp2RegionV6(cfg)
        let r = region.search("2408:8266:100:1000::")
        if (r.size == 0) {
            throw Exception("newIp2RegionV6 should work")
        }
        region.close()
    }
}


================================================
FILE: binding/cangjie/src/tests/service/searcher_pool_test.cj
================================================
package ip2region.tests.service

import ip2region.xdb.*
import ip2region.service.*

@Test
class SearcherPoolTests {
    @TestCase
    func testBorrowAndReturn() {
        let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 3)
        let pool = SearcherPool(cfg)
        let s = pool.borrow()
        if (pool.getLoanCount() != 1) {
            throw Exception("loan count should be 1, got ${pool.getLoanCount()}")
        }
        pool.return_(s)
        if (pool.getLoanCount() != 0) {
            throw Exception("loan count should be 0 after return")
        }
        pool.close()
    }

    @TestCase
    func testBorrowAll() {
        let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 5)
        let pool = SearcherPool(cfg)
        let s1 = pool.borrow()
        let s2 = pool.borrow()
        let s3 = pool.borrow()
        let s4 = pool.borrow()
        let s5 = pool.borrow()
        if (pool.getLoanCount() != 5) {
            throw Exception("should have 5 loans, got ${pool.getLoanCount()}")
        }
        pool.return_(s5)
        pool.return_(s4)
        pool.return_(s3)
        pool.return_(s2)
        pool.return_(s1)
        if (pool.getLoanCount() != 0) {
            throw Exception("all should be returned, got ${pool.getLoanCount()}")
        }
        pool.close()
    }

    @TestCase
    func testClosePool() {
        let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 2)
        let pool = SearcherPool(cfg)
        let s = pool.borrow()
        pool.return_(s)
        pool.close()
        pool.close()
    }
}


================================================
FILE: binding/cangjie/src/tests/tests.cj
================================================
package ip2region.tests


================================================
FILE: binding/cangjie/src/tests/xdb/header_test.cj
================================================
package ip2region.tests.xdb

import ip2region.xdb.*

@Test
class HeaderTests {
    func makeHeaderBytes(): Array<Byte> {
        let buf = Array<Byte>(256, repeat: 0)
        var v: Byte
        v = 3; buf[0] = v; v = 0; buf[1] = v
        v = 1; buf[2] = v; v = 0; buf[3] = v
        v = 0xA0; buf[4] = v; v = 0x86; buf[5] = v; v = 0x01; buf[6] = v; v = 0x00; buf[7] = v
        v = 0xE8; buf[8] = v; v = 0x03; buf[9] = v; v = 0x00; buf[10] = v; v = 0x00; buf[11] = v
        v = 0xD0; buf[12] = v; v = 0x07; buf[13] = v; v = 0x00; buf[14] = v; v = 0x00; buf[15] = v
        v = 4; buf[16] = v; v = 0; buf[17] = v
        v = 0; buf[18] = v; v = 0; buf[19] = v
        return buf
    }

    @TestCase
    func testNewHeaderFromBytes() {
        let buf = makeHeaderBytes()
        let h = newHeaderFromBytes(buf)
        if (h.version != UInt16(3)) {
            throw Exception("expected version 3, got ${h.version}")
        }
        if (h.ipVersion != UInt16(4)) {
            throw Exception("expected ipVersion 4, got ${h.ipVersion}")
        }
    }

    @TestCase
    func testHeaderToString() {
        let buf = makeHeaderBytes()
        let h = newHeaderFromBytes(buf)
        let s = h.toString()
        if (s.size == 0) {
            throw Exception("toString should not be empty")
        }
    }

    @TestCase
    func testVersionFromHeaderV4() {
        let buf = makeHeaderBytes()
        let h = newHeaderFromBytes(buf)
        let v = versionFromHeader(h)
        if (v.id != IPv4VersionNo) {
            throw Exception("expected IPv4, got ${v.name}")
        }
    }

    @TestCase
    func testVersionFromHeaderV6() {
        let buf = makeHeaderBytes()
        var v: Byte = 6; buf[16] = v
        let h = newHeaderFromBytes(buf)
        let ver = versionFromHeader(h)
        if (ver.id != IPv6VersionNo) {
            throw Exception("expected IPv6, got ${ver.name}")
        }
    }

    @TestCase
    func testVersionFromHeader20() {
        let buf = makeHeaderBytes()
        var v: Byte = 2; buf[0] = v
        v = 0; buf[16] = v
        let h = newHeaderFromBytes(buf)
        let ver = versionFromHeader(h)
        if (ver.id != IPv4VersionNo) {
            throw Exception("expected IPv4 for v2.0")
        }
    }

    @TestCase
    func testHeaderConstants() {
        if (HeaderInfoLength != 256) { throw Exception("expected 256") }
        if (VectorIndexRows != 256) { throw Exception("expected 256") }
        if (VectorIndexSize != 8) { throw Exception("expected 8") }
        if (IPv4SegmentIndexSize != 14) { throw Exception("expected 14") }
        if (IPv6SegmentIndexSize != 38) { throw Exception("expected 38") }
    }
}


================================================
FILE: binding/cangjie/src/tests/xdb/searcher_test.cj
================================================
package ip2region.tests.xdb

import std.fs.*
import ip2region.xdb.*

@Test
class SearcherV4Tests {
    var v4Content: Array<Byte> = Array<Byte>(0, repeat: 0)
    var initialized: Bool = false

    @BeforeAll
    func setup() {
        this.v4Content = File.readFrom(Path("../../data/ip2region_v4.xdb"))
        this.initialized = true
    }

    @TestCase
    func testSearchContentMode() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v4Content)
        let version = versionFromHeader(header)
        let searcher = Searcher(version, this.v4Content, "../../data/ip2region_v4.xdb")
        let ip = parseIP("220.181.108.183")
        let region = searcher.search(ip)
        if (region.size == 0) {
            throw Exception("should find region for known IP")
        }
        searcher.close()
    }

    @TestCase
    func testSearchVectorIndexMode() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v4Content)
        let version = versionFromHeader(header)
        let vIndex = loadVectorIndex(this.v4Content)
        let searcher = Searcher(version, "../../data/ip2region_v4.xdb", vIndex)
        let ip = parseIP("220.181.108.183")
        let region = searcher.search(ip)
        if (region.size == 0) {
            throw Exception("should find region for known IP")
        }
        searcher.close()
    }

    @TestCase
    func testSearchFileMode() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v4Content)
        let version = versionFromHeader(header)
        let searcher = Searcher(version, "../../data/ip2region_v4.xdb")
        let ip = parseIP("220.181.108.183")
        let region = searcher.search(ip)
        if (region.size == 0) {
            throw Exception("should find region for known IP")
        }
        searcher.close()
    }

    @TestCase
    func testAllModesSameResult() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v4Content)
        let version = versionFromHeader(header)
        let vIndex = loadVectorIndex(this.v4Content)
        let s1 = Searcher(version, this.v4Content, "../../data/ip2region_v4.xdb")
        let s2 = Searcher(version, "../../data/ip2region_v4.xdb", vIndex)
        let s3 = Searcher(version, "../../data/ip2region_v4.xdb")
        let ip = parseIP("1.2.3.4")
        let r1 = s1.search(ip)
        let r2 = s2.search(ip)
        let r3 = s3.search(ip)
        s1.close(); s2.close(); s3.close()
        if (r1 != r2 || r2 != r3) {
            throw Exception("all modes should return same result")
        }
    }

    @TestCase
    func testSearchByString() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v4Content)
        let version = versionFromHeader(header)
        let searcher = Searcher(version, this.v4Content, "../../data/ip2region_v4.xdb")
        let region = searcher.searchByString("220.181.108.183")
        if (region.size == 0) {
            throw Exception("searchByString should find region")
        }
        searcher.close()
    }
}

@Test
class SearcherV6Tests {
    var v6Content: Array<Byte> = Array<Byte>(0, repeat: 0)
    var initialized: Bool = false

    @BeforeAll
    func setup() {
        this.v6Content = File.readFrom(Path("../../data/ip2region_v6.xdb"))
        this.initialized = true
    }

    @TestCase
    func testSearchV6ContentMode() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v6Content)
        let version = versionFromHeader(header)
        let searcher = Searcher(version, this.v6Content, "../../data/ip2region_v6.xdb")
        let ip = parseIP("2408:8266:100:1000::")
        let region = searcher.search(ip)
        if (region.size == 0) {
            throw Exception("should find region for known IPv6")
        }
        searcher.close()
    }

    @TestCase
    func testSearchV6VectorIndexMode() {
        if (!this.initialized) { return }
        let header = newHeaderFromBytes(this.v6Content)
        let version = versionFromHeader(header)
        let vIndex = loadVectorIndex(this.v6Content)
        let searcher = Searcher(version, "../../data/ip2region_v6.xdb", vIndex)
        let ip = parseIP("2408:8266:100:1000::")
        let region = searcher.search(ip)
        if (region.size == 0) {
            throw Exception("should find region for known IPv6")
        }
        searcher.close()
    }
}


================================================
FILE: binding/cangjie/src/tests/xdb/util_test.cj
================================================
package ip2region.tests.xdb

import ip2region.xdb.*

@Test
class UtilTests {
    @TestCase
    func testParseIPv4() {
        let ip = parseIP("1.2.3.4")
        if (ip.size != 4) {
            throw Exception("expected 4 bytes, got ${ip.size}")
        }
        if (Int64(ip[0]) != 1 || Int64(ip[1]) != 2 || Int64(ip[2]) != 3 || Int64(ip[3]) != 4) {
            throw Exception("IPv4 parse mismatch")
        }
    }

    @TestCase
    func testParseIPv6() {
        let ip = parseIP("::1")
        if (ip.size != 16) {
            throw Exception("expected 16 bytes for IPv6, got ${ip.size}")
        }
    }

    @TestCase
    func testIPCompareEqual() {
        let a = Array<Byte>(4, repeat: 0)
        var v: Byte = 1; a[0] = v; v = 2; a[1] = v; v = 3; a[2] = v; v = 4; a[3] = v
        let b = Array<Byte>(4, repeat: 0)
        v = 1; b[0] = v; v = 2; b[1] = v; v = 3; b[2] = v; v = 4; b[3] = v
        let r = ipCompare(a, b)
        if (r != 0) {
            throw Exception("expected 0, got ${r}")
        }
    }

    @TestCase
    func testIPCompareLess() {
        let a = Array<Byte>(4, repeat: 0)
        var v: Byte = 1; a[0] = v; v = 2; a[1] = v; v = 3; a[2] = v; v = 4; a[3] = v
        let b = Array<Byte>(4, repeat: 0)
        v = 1; b[0] = v; v = 2; b[1] = v; v = 3; b[2] = v; v = 5; b[3] = v
        let r = ipCompare(a, b)
        if (r != -1) {
            throw Exception("expected -1, got ${r}")
        }
    }

    @TestCase
    func testIPCompareGreater() {
        let a = Array<Byte>(4, repeat: 0)
        var v: Byte = 1; a[0] = v; v = 2; a[1] = v; v = 3; a[2] = v; v = 5; a[3] = v
        let b = Array<Byte>(4, repeat: 0)
        v = 1; b[0] = v; v = 2; b[1] = v; v = 3; b[2] = v; v = 4; b[3] = v
        let r = ipCompare(a, b)
        if (r != 1) {
            throw Exception("expected 1, got ${r}")
        }
    }

    @TestCase
    func testReadLEUint32() {
        let buf = Array<Byte>(4, repeat: 0)
        var v: Byte = 0x78; buf[0] = v; v = 0x56; buf[1] = v; v = 0x34; buf[2] = v; v = 0x12; buf[3] = v
        let val = readLEUint32(buf, 0)
        let expected: Int64 = 0x12345678
        if (val != expected) {
            throw Exception("expected 0x12345678, got ${val}")
        }
    }

    @TestCase
    func testReadLEUint16() {
        let buf = Array<Byte>(2, repeat: 0)
        var v: Byte = 0x34; buf[0] = v; v = 0x12; buf[1] = v
        let val = readLEUint16(buf, 0)
        if (val != 0x1234) {
            throw Exception("expected 0x1234, got ${val}")
        }
    }

    @TestCase
    func testIpToString() {
        let ip = Array<Byte>(4, repeat: 0)
        var v: Byte = 127; ip[0] = v; v = 0; ip[1] = v; v = 0; ip[2] = v; v = 1; ip[3] = v
        let s = ipToString(ip)
        if (s != "127.0.0.1") {
            throw Exception("expected 127.0.0.1, got ${s}")
        }
    }

    @TestCase
    func testCompareIPv4() {
        let ip = Array<Byte>(4, repeat: 0)
        var v: Byte = 1; ip[0] = v; v = 2; ip[1] = v; v = 3; ip[2] = v; v = 4; ip[3] = v
        let entry = Array<Byte>(8, repeat: 0)
        v = 4; entry[0] = v; v = 3; entry[1] = v; v = 2; entry[2] = v; v = 1; entry[3] = v
        v = 8; entry[4] = v; v = 7; entry[5] = v; v = 6; entry[6] = v; v = 5; entry[7] = v
        let r = compareIPv4(ip, entry, 0)
        if (r != 0) {
            throw Exception("expected equal, got ${r}")
        }
    }

    @TestCase
    func testCompareIPv6() {
        let ip = Array<Byte>(16, repeat: 0)
        var v: Byte = 0x20; ip[0] = v; v = 0x01; ip[1] = v; v = 0x0d; ip[2] = v; v = 0xb8; ip[3] = v
        let entry = Array<Byte>(16, repeat: 0)
        v = 0x20; entry[0] = v; v = 0x01; entry[1] = v; v = 0x0d; entry[2] = v; v = 0xb8; entry[3] = v
        let r = compareIPv6(ip, entry, 0)
        if (r != 0) {
            throw Exception("expected equal, got ${r}")
        }
    }

    @TestCase
    func testLoadVectorIndex() {
        let viSize = VectorIndexRows * VectorIndexCols * VectorIndexSize
        let cBuff = Array<Byte>(HeaderInfoLength + viSize, repeat: 0)
        var v: Byte = 0x42
        cBuff[HeaderInfoLength] = v
        let vi = loadVectorIndex(cBuff)
        if (vi.size != viSize) {
            throw Exception("expected size ${viSize}, got ${vi.size}")
        }
        let firstVal = Int64(vi[0])
        if (firstVal != 0x42) {
            throw Exception("expected first byte 0x42, got ${firstVal}")
        }
    }
}


================================================
FILE: binding/cangjie/src/xdb/header.cj
================================================
package ip2region.xdb

// xdb file format constants
public let HeaderInfoLength: Int64 = 256
public let VectorIndexRows: Int64 = 256
public let VectorIndexCols: Int64 = 256
public let VectorIndexSize: Int64 = 8
public let IPv4SegmentIndexSize: Int64 = 14  // 4 + 4 + 2 + 4
public let IPv6SegmentIndexSize: Int64 = 38  // 16 + 16 + 2 + 4

public let Structure20: Int64 = 2
public let Structure30: Int64 = 3

// xdb header parsed from the first 256 bytes of the xdb file
public class Header {
    public let version: UInt16
    public let indexPolicy: UInt16
    public let createdAt: UInt32
    public let startIndexPtr: UInt32
    public let endIndexPtr: UInt32
    public let ipVersion: UInt16
    public let runtimePtrBytes: UInt16

    public init(
        ver: UInt16,
        idxPolicy: UInt16,
        createAt: UInt32,
        sPtr: UInt32,
        ePtr: UInt32,
        ipVer: UInt16,
        rtPtrBytes: UInt16
    ) {
        this.version = ver
        this.indexPolicy = idxPolicy
        this.createdAt = createAt
        this.startIndexPtr = sPtr
        this.endIndexPtr = ePtr
        this.ipVersion = ipVer
        this.runtimePtrBytes = rtPtrBytes
    }

    public func toString(): String {
        return "{version: ${this.version}, index_policy: ${this.indexPolicy}, " +
            "created_at: ${this.createdAt}, start_index_ptr: ${this.startIndexPtr}, " +
            "end_index_ptr: ${this.endIndexPtr}, ip_version: ${this.ipVersion}, " +
            "runtime_ptr_bytes: ${this.runtimePtrBytes}}"
    }
}

// Read a little-endian UInt16 from byte array at offset
func readHeaderUInt16(buf: Array<Byte>, offset: Int64): UInt16 {
    let b0 = UInt16(buf[offset])
    let b1 = UInt16(buf[offset + 1]) << 8
    return b0 | b1
}

// Read a little-endian UInt32 from byte array at offset
func readHeaderUInt32(buf: Array<Byte>, offset: Int64): UInt32 {
    let b0 = UInt32(buf[offset])
    let b1 = UInt32(buf[offset + 1]) << 8
    let b2 = UInt32(buf[offset + 2]) << 16
    let b3 = UInt32(buf[offset + 3]) << 24
    return b0 | b1 | b2 | b3
}

// Create a Header from raw byte buffer (first 256 bytes of xdb file)
public func newHeaderFromBytes(buf: Array<Byte>): Header {
    return Header(
        readHeaderUInt16(buf, 0),
        readHeaderUInt16(buf, 2),
        readHeaderUInt32(buf, 4),
        readHeaderUInt32(buf, 8),
        readHeaderUInt32(buf, 12),
        readHeaderUInt16(buf, 16),
        readHeaderUInt16(buf, 18)
    )
}


================================================
FILE: binding/cangjie/src/xdb/searcher.cj
================================================
package ip2region.xdb

import std.fs.*
import std.io.SeekPosition

// Searcher for ip2region xdb database
public class Searcher {
    let version: Version
    let handle: File
    var ioCount: Int64
    let vectorIndex: Array<Byte>
    let contentBuff: Array<Byte>
    let mode: Int64

    public let FileOnlyMode: Int64 = 0
    public let VectorIndexMode: Int64 = 1
    public let ContentBuffMode: Int64 = 2

    public init(ver: Version, dbFile: String) {
        this.version = ver
        this.handle = File(dbFile, Read)
        this.vectorIndex = Array<Byte>(0, repeat: 0)
        this.contentBuff = Array<Byte>(0, repeat: 0)
        this.mode = FileOnlyMode
        this.ioCount = 0
    }

    public init(ver: Version, dbFile: String, vIndex: Array<Byte>) {
        this.version = ver
        this.handle = File(dbFile, Read)
        this.vectorIndex = vIndex
        this.contentBuff = Array<Byte>(0, repeat: 0)
        this.mode = VectorIndexMode
        this.ioCount = 0
    }

    public init(ver: Version, cBuff: Array<Byte>, dbPath: String) {
        this.version = ver
        this.handle = File(dbPath, Read)
        this.vectorIndex = Array<Byte>(0, repeat: 0)
        this.contentBuff = cBuff
        this.mode = ContentBuffMode
        this.ioCount = 0
    }

    public func close() {
        this.handle.close()
    }

    public func getIOCount(): Int64 {
        return this.ioCount
    }

    public func searchByString(ipStr: String): String {
        let ipBytes = parseIP(ipStr)
        return this.search(ipBytes)
    }

    public func search(ip: Array<Byte>): String {
        this.ioCount = 0

        let il0 = Int64(ip[0])
        let il1 = Int64(ip[1])
        let idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize

        var sPtr: Int64 = 0
        var ePtr: Int64 = 0

        if (this.mode == VectorIndexMode) {
            sPtr = readLEUint32(this.vectorIndex, idx)
            ePtr = readLEUint32(this.vectorIndex, idx + 4)
        } else if (this.mode == ContentBuffMode) {
            sPtr = readLEUint32(this.contentBuff, HeaderInfoLength + idx)
            ePtr = readLEUint32(this.contentBuff, HeaderInfoLength + idx + 4)
        } else {
            let buff = Array<Byte>(VectorIndexSize, repeat: 0)
            this.readFromFile(HeaderInfoLength + idx, buff)
            sPtr = readLEUint32(buff, 0)
            ePtr = readLEUint32(buff, 4)
        }

        if (sPtr == 0 || ePtr == 0) {
            return ""
        }

        let segIndexSize = this.version.segmentIndexSize
        let bytes = this.version.bytes
        let dBytes = bytes * 2
        var dataLen: Int64 = 0
        var dataPtr: Int64 = 0

        var l: Int64 = 0
        var h: Int64 = (ePtr - sPtr) / segIndexSize
        let buff = Array<Byte>(segIndexSize, repeat: 0)

        while (l <= h) {
            let m = (l + h) / 2
            let p = sPtr + m * segIndexSize

            this.read(p, buff)
            this.ioCount = this.ioCount + 1

            if (this.compareIP(ip, buff, 0) < 0) {
                h = m - 1
            } else if (this.compareIP(ip, buff, bytes) > 0) {
                l = m + 1
            } else {
                dataLen = readLEUint16(buff, dBytes)
                dataPtr = readLEUint32(buff, dBytes + 2)
                break
            }
        }

        if (dataLen == 0) {
            return ""
        }

        let regionBuff = Array<Byte>(dataLen, repeat: 0)
        this.read(dataPtr, regionBuff)
        return String.fromUtf8(regionBuff)
    }

    func read(offset: Int64, buff: Array<Byte>) {
        if (this.mode == ContentBuffMode) {
            for (i in 0..buff.size) {
                buff[i] = this.contentBuff[offset + i]
            }
        } else {
            this.readFromFile(offset, buff)
            ()
        }
    }

    func readFromFile(offset: Int64, buff: Array<Byte>) {
        this.handle.seek(SeekPosition.Begin(offset))
        this.handle.read(buff)
        ()
    }

    func compareIP(ip: Array<Byte>, entry: Array<Byte>, offset: Int64): Int64 {
        if (this.version.id == IPv4VersionNo) {
            return compareIPv4(ip, entry, offset)
        }
        return compareIPv6(ip, entry, offset)
    }
}


================================================
FILE: binding/cangjie/src/xdb/util.cj
================================================
package ip2region.xdb

import std.net.IPAddress

// --- IP address parsing and formatting

// Parse IP string to byte array
// For IPv4, returns 4 bytes in big-endian order (network byte order)
// For IPv6, returns 16 bytes in big-endian order
public func parseIP(ip: String): Array<Byte> {
    let addr = IPAddress.parse(ip)
    return addr.getAddressBytes()
}

// Convert IP byte array back to string
public func ipToString(ip: Array<Byte>): String {
    if (ip.size == 4) {
        return "${Int64(ip[0])}.${Int64(ip[1])}.${Int64(ip[2])}.${Int64(ip[3])}"
    }
    // IPv6 simplified format
    var result = ""
    for (i in 0..ip.size) {
        if (i > 0 && i % 2 == 0) {
            result = result + ":"
        }
        result = result + "${Int64(ip[i])}"
    }
    return result
}

// Compare two IP byte arrays.
// Returns: -1 if ip1 < ip2, 0 if equal, 1 if ip1 > ip2
public func ipCompare(ip1: Array<Byte>, ip2: Array<Byte>): Int64 {
    var len = ip1.size
    if (ip2.size < len) {
        len = ip2.size
    }
    for (i in 0..len) {
        if (ip1[i] < ip2[i]) {
            return -1
        } else if (ip1[i] > ip2[i]) {
            return 1
        }
    }
    if (ip1.size < ip2.size) {
        return -1
    } else if (ip1.size > ip2.size) {
        return 1
    }
    return 0
}

// --- Binary data reading helpers for xdb format

// Read a little-endian UInt32 from a byte buffer at the given offset
public func readLEUint32(buf: Array<Byte>, offset: Int64): Int64 {
    let b0 = Int64(buf[offset])
    let b1 = Int64(buf[offset + 1]) << 8
    let b2 = Int64(buf[offset + 2]) << 16
    let b3 = Int64(buf[offset + 3]) << 24
    return b0 | b1 | b2 | b3
}

// Read a little-endian UInt16 from a byte buffer at the given offset
public func readLEUint16(buf: Array<Byte>, offset: Int64): Int64 {
    let b0 = Int64(buf[offset])
    let b1 = Int64(buf[offset + 1]) << 8
    return b0 | b1
}

// Compare IPv4 address (big-endian) with xdb stored IPv4 (little-endian)
// xdb stores IPv4 in little-endian, so we compare ip[i] with xdbEntry[3-i]
public func compareIPv4(ip: Array<Byte>, xdbEntry: Array<Byte>, entryOffset: Int64): Int64 {
    for (i in 0..4) {
        let ipByte = Int64(ip[i])
        let xdbByte = Int64(xdbEntry[entryOffset + 3 - i])
        if (ipByte < xdbByte) {
            return -1
        } else if (ipByte > xdbByte) {
            return 1
        }
    }
    return 0
}

// Load the vector index from a content buffer
// The vector index starts at HeaderInfoLength and has VectorIndexRows * VectorIndexCols * VectorIndexSize bytes
public func loadVectorIndex(cBuff: Array<Byte>): Array<Byte> {
    let start = HeaderInfoLength
    let len = VectorIndexRows * VectorIndexCols * VectorIndexSize
    let vi = Array<Byte>(len, repeat: 0)
    for (i in 0..len) {
        vi[i] = cBuff[start + i]
    }
    return vi
}

// Compare IPv6 address (big-endian) with xdb stored IPv6 (big-endian)
public func compareIPv6(ip: Array<Byte>, xdbEntry: Array<Byte>, entryOffset: Int64): Int64 {
    for (i in 0..16) {
        if (ip[i] < xdbEntry[entryOffset + i]) {
            return -1
        } else if (ip[i] > xdbEntry[entryOffset + i]) {
            return 1
        }
    }
    return 0
}


================================================
FILE: binding/cangjie/src/xdb/version.cj
================================================
package ip2region.xdb

// IP version definitions for ip2region xdb searcher
public class Version {
    public let id: Int64
    public let name: String
    public let bytes: Int64
    public let segmentIndexSize: Int64

    public init(id: Int64, name: String, bytes: Int64, segmentIndexSize: Int64) {
        this.id = id
        this.name = name
        this.bytes = bytes
        this.segmentIndexSize = segmentIndexSize
    }

    public func toString(): String {
        return "{id: ${this.id}, name: ${this.name}, bytes: ${this.bytes}, " +
            "segment_index_size: ${this.segmentIndexSize}}"
    }
}

public let IPv4VersionNo: Int64 = 4
public let IPv6VersionNo: Int64 = 6

// Pre-defined IP versions
public let IPv4 = Version(4, "IPv4", 4, 14)
public let IPv6 = Version(6, "IPv6", 16, 38)

// Determine IP version from header info
public func versionFromHeader(header: Header): Version {
    // Old structure (2.0) with IPv4 only
    if (Int64(header.version) == Structure20) {
        return IPv4
    }
    // Structure 3.0+
    if (Int64(header.ipVersion) == IPv4VersionNo) {
        return IPv4
    }
    return IPv6
}


================================================
FILE: binding/cpp/.gitignore
================================================
bin/


================================================
FILE: binding/cpp/Makefile
================================================

all: bin header search bench make edit

FILES=$(wildcard src/*.cc)

bin:
	mkdir -p bin

header: $(FILES) test/header.cc
	g++ -std=c++11 -O2 $^ -o bin/$@

search: $(FILES) test/search.cc
	g++ -std=c++11 -O2 $^ -o bin/$@

bench: $(FILES) test/bench.cc
	g++ -std=c++11 -O2 $^ -o bin/$@

make: $(FILES) test/make.cc
	g++ -std=c++11 -O2 $^ -o bin/$@

edit: $(FILES)
	g++ -std=c++11 -O2 $^ test/edit_v4.cc -o bin/edit_v4
	g++ -std=c++11 -O2 $^ test/edit_v6.cc -o bin/edit_v6

clean:
	rm -rf bin



================================================
FILE: binding/cpp/README.md
================================================
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)

# ip2region C++ query client

## 0. File Description

```
Makefile --------- Build

src ------------------ Source directory
src/base.* ----------- Constants and utility functions
src/ip.* ------------- IP processing implementation
src/header.* --------- xdb header parsing implementation
src/search.* --------- xdb search implementation
src/bench.* ---------- Search benchmarking implementation
src/make.* ----------- xdb file generation implementation
src/edit.* ----------- Raw data editing implementation

test ---------------- Test directory
test/header.cc ------ Test header
test/search.cc ------ Test search
test/bench.cc ------- Benchmarking
test/make.cc -------- Generate xdb file
test/edit_v4.cc ----- Test raw data editing (ipv4)
test/edit_v6.cc ----- Test raw data editing (ipv6)


bin --------------- Executable directory (generated via make)
bin/header -------- Test header
bin/search -------- Test search
bin/bench --------- Benchmarking
bin/make ---------- Generate xdb file
bin/edit_v4 ------- Test raw data editing (ipv4)
bin/edit_v6 ------- Test raw data editing (ipv6)

readme.md --------- readme

```

## 1. Compilation

```
$ make
```

## 2. Search

### 2.1 Example

```cpp
#include "src/search.h"

// IP Version: xdb::ipv4 xdb::ipv6
//    Policy: xdb::policy_file xdb::policy_vector xdb::policy_content
//               No cache           Partial cache        Full cache
int main() {
    std::string xdb_name = "../../data/ip2region_v6.xdb";
    int         version  = xdb::ipv6;
    int         policy   = xdb::policy_content;
    std::string ip       = "2001:200:124::";

    xdb::search_t s(xdb_name, version, policy);
    std::cout << s.search(ip) << std::endl;
    return 0;
}

// $ g++ src/*.cc 1.cc --- Compile
// $ ./a.out ------------- Test
// Japan|Tokyo|Asagaya-minami|WIDE Project|JP
```

### 2.2 Test xdb Header

```
$ ./bin/header
Test IPv4
Version: 3
Cache Policy: 1
File Generation Time: 2025-09-06 02:24:16
Index Start Address: 955933
Index End Address: 11042415
IP Version: 4
Pointer Bytes: 4

Test IPv6
Version: 3
Cache Policy: 1
File Generation Time: 2025-10-17 04:41:04
Index Start Address: 3094259
Index End Address: 36258303
IP Version: 6
Pointer Bytes: 4
```

### 2.3 Test Search

```
$ ./bin/search
Test IPv4   No cache: Success
Test IPv4 Partial cache: Success
Test IPv4 Full cache: Success
Test IPv6   No cache: Success
Test IPv6 Partial cache: Success
Test IPv6 Full cache: Success
```

## 3. Benchmarking and Correctness Verification

```
./bin/bench
Test IPv4,   No cache, total: 3910284, took:    27.60s, cost:   6.59μs/op, io count: 28227147
Test IPv4, Partial cache, total: 3910284, took:    21.85s, cost:   5.15μs/op, io count: 24316863
Test IPv4, Full cache, total: 3910284, took:     2.26s, cost:   0.25μs/op, io count: 0
Test IPv6,   No cache, total: 4792520, took:   100.40s, cost:  20.22μs/op, io count: 80758866
Test IPv6, Partial cache, total: 4792520, took:    93.06s, cost:  18.71μs/op, io count: 75966346
Test IPv6, Full cache, total: 4792520, took:     6.24s, cost:   0.81μs/op, io count: 0
```

## 4. Generate xdb File

### 4.1 Generate xdb File

```
$ ./bin/make
Generate ipv4 xdb file, took: 0.57s
Generate ipv6 xdb file, took: 1.24s
```

## 5. Raw Data Editing

### 5.1. Instructions for Use

* New IP attribution files can contain empty lines
* New IP attribution files can be out of order; the program will automatically sort them
* New IP attribution files can overlap; as long as there is no ambiguity, the program will automatically merge them
* The final result will automatically merge adjacent lines with the same attribution
* For the following tests, the original file uses the data file provided in the repository, and the new file uses 1.txt in the current directory


================================================
FILE: binding/cpp/README_zh.md
================================================
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)

# ip2region C++ 查询客户端

## 0. 文件说明
```
Makefile --------- 构建

src ------------------ 源文件目录
src/base.* ----------- 常量及工具函数
src/ip.* ------------- 实现 IP 处理
src/header.* --------- 实现 xdb 头部解析
src/search.* --------- 实现 xdb 查找
src/bench.* ---------- 实现 查找 测速
src/make.* ----------- 实现 生成 xdb 文件
src/edit.* ----------- 实现 原始数据编辑

test ---------------- 测试目录
test/header.cc ------ 测试 头部
test/search.cc ------ 测试 查找
test/bench.cc ------- 测速
test/make.cc -------- 生成 xdb 文件
test/edit_v4.cc ----- 测试 原始数据编辑(ipv4)
test/edit_v6.cc ----- 测试 原始数据编辑(ipv6)


bin --------------- 可执行文件目录(通过 make 生成)
bin/header -------- 测试 头部
bin/search -------- 测试 查找
bin/bench --------- 测速
bin/make ---------- 生成 xdb 文件
bin/edit_v4 ------- 测试 原始数据编辑(ipv4)
bin/edit_v6 ------- 测试 原始数据编辑(ipv6)

readme.md --------- readme
```

## 1. 编译
```
$ make
```

## 2. 查找
### 2.1 示例
```cpp
#include "src/search.h"

// IP 版本: xdb::ipv4 xdb::ipv6
//    策略: xdb::policy_file xdb::policy_vector xdb::policy_content
//               不缓存           部分缓存           全部缓存
int main() {
    std::string xdb_name = "../../data/ip2region_v6.xdb";
    int         version  = xdb::ipv6;
    int         policy   = xdb::policy_content;
    std::string ip       = "2001:200:124::";

    xdb::search_t s(xdb_name, version, policy);
    std::cout << s.search(ip) << std::endl;
    return 0;
}

// $ g++ src/*.cc 1.cc --- 编译
// $ ./a.out ------------- 测试
// Japan|Tokyo|Asagaya-minami|WIDE Project|JP
```

### 2.2 测试 xdb 头部
```
$ ./bin/header
测试 IPv4
版本号: 3
缓存策略: 1
文件生成时间: 2025-09-06 02:24:16
索引起始地址: 955933
索引结束地址: 11042415
IP版本: 4
指针字节数: 4

测试 IPv6
版本号: 3
缓存策略: 1
文件生成时间: 2025-10-17 04:41:04
索引起始地址: 3094259
索引结束地址: 36258303
IP版本: 6
指针字节数: 4
```

### 2.3 测试查找
```
$ ./bin/search
测试 IPv4   不缓存: 成功
测试 IPv4 部分缓存: 成功
测试 IPv4 全部缓存: 成功
测试 IPv6   不缓存: 成功
测试 IPv6 部分缓存: 成功
测试 IPv6 全部缓存: 成功
```

## 3. 测速以及检验正确性
```
./bin/bench
测试 IPv4,   不缓存, total: 3910284, took:    27.60s, cost:   6.59μs/op, io count: 28227147
测试 IPv4, 部分缓存, total: 3910284, took:    21.85s, cost:   5.15μs/op, io count: 24316863
测试 IPv4, 全部缓存, total: 3910284, took:     2.26s, cost:   0.25μs/op, io count: 0
测试 IPv6,   不缓存, total: 4792520, took:   100.40s, cost:  20.22μs/op, io count: 80758866
测试 IPv6, 部分缓存, total: 4792520, took:    93.06s, cost:  18.71μs/op, io count: 75966346
测试 IPv6, 全部缓存, total: 4792520, took:     6.24s, cost:   0.81μs/op, io count: 0
```

## 4. 生成 xdb 文件
### 4.1 生成 xdb 文件
```
$ ./bin/make
生成 ipv4 的 xdb 文件, took: 0.57s
生成 ipv6 的 xdb 文件, took: 1.24s
```

## 5. 原始数据编辑
### 5.1. 使用说明
* 新的IP归属地文件可以包含空行
* 新的IP归属地文件顺序可以乱序, 程序会自动排序
* 新的IP归属地文件顺序可以重叠, 只要无二义性, 程序会自动合并
* 最终的结果会将相邻的且归属地相同的行自动合并
* 以下测试, 原文件使用仓库自带的数据文件, 新文件使用当前目录下的 1.txt


================================================
FILE: binding/cpp/src/base.cc
================================================

#include "base.h"

namespace xdb {

int ip_version;  // ip 版本
int ip_size;     // ip 占的字节数
int content_size;

void init_xdb(int version) {
    ip_version   = version;
    ip_size      = version == ipv4 ? 4 : 16;
    content_size = ip_size * 2 + 2 + 4;
}

void log_exit(const string &msg) {
    std::cout << msg << std::endl;
    exit(-1);
}

void read_bin(int index, char *buf, size_t len, FILE *db) {
    fseek(db, index, SEEK_SET);
    if (fread(buf, 1, len, db) != len)
        log_exit(__func__);
}

unsigned to_uint(const char *buf) {
    return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00) |
           ((buf[2] << 16) & 0x00FF0000) | ((buf[3] << 24) & 0xFF000000);
}

unsigned to_ushort(const char *buf) {
    return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00);
}

unsigned to_int(const char *buf, int n) {
    return n == 2 ? to_ushort(buf) : to_uint(buf);
}

void write_uint(unsigned data, char buf[]) {
    buf[0] = (data >> 0) & 0xFF;
    buf[1] = (data >> 8) & 0xFF;
    buf[2] = (data >> 16) & 0xFF;
    buf[3] = (data >> 24) & 0xFF;
}

void write_uint(unsigned data, FILE *dst) {
    char buf[4];
    write_uint(data, buf);
    fwrite(buf, 1, sizeof(buf), dst);
}

void write_ushort(unsigned data, char buf[]) {
    buf[0] = (data >> 0) & 0xFF;
    buf[1] = (data >> 8) & 0xFF;
}

void write_ushort(unsigned data, FILE *dst) {
    char buf[2];
    write_ushort(data, buf);
    fwrite(buf, 1, sizeof(buf), dst);
}

void write_string(const char *buf, unsigned len, FILE *dst) {
    fwrite(buf, 1, len, dst);
}

unsigned long long get_time() {
    struct timeval tv1;
    gettimeofday(&tv1, NULL);
    return (unsigned long long)tv1.tv_sec * 1000 * 1000 + tv1.tv_usec;
}

}  // namespace xdb


================================================
FILE: binding/cpp/src/base.h
================================================
#ifndef BASE_H
#define BASE_H

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include <algorithm>
#include <iostream>
#include <list>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>

namespace xdb {

using std::string;

constexpr int ipv4 = 4;
constexpr int ipv6 = 6;

constexpr int policy_file    = 0;
constexpr int policy_vector  = 1;
constexpr int policy_content = 2;

constexpr int length_header = 256;
constexpr int length_vector = 256 * 256 * 8;

extern int ip_version;  // ip 版本
extern int ip_size;     // ip 占的字节数
extern int content_size;

void init_xdb(int version);

void log_exit(const string &msg);

void read_bin(int index, char *buf, size_t len, FILE *db);

unsigned to_uint(const char *buf);
unsigned to_ushort(const char *buf);
unsigned to_int(const char *buf, int n);

void write_uint(unsigned data, char buf[]);
void write_uint(unsigned data, FILE *dst);

void write_ushort(unsigned data, char buf[]);
void write_ushort(unsigned data, FILE *dst);

void write_string(const char *buf, unsigned len, FILE *dst);

unsigned long long get_time();

}  // namespace xdb

#endif


================================================
FILE: binding/cpp/src/bench.cc
================================================

#include "bench.h"

namespace xdb {

bench_t::bench_t(const std::string &file_name, int version, int policy)
    : search(file_name, version, policy) {
}

void bench_t::test_one(const ip_t &ip, const string region) {
    if (search.search(ip.to_string()) != region)
        xdb::log_exit("failed: " + ip.to_string() + " " + region);
    sum_io_count += search.get_io_count();
    sum_cost_time += search.get_cost_time();
    sum_count++;
}

void bench_t::test_line(char *buf) {
    size_t buf_len = strlen(buf);
    if (buf_len == 0)
        return;
    buf[buf_len - 1] = '\0';  // 去掉换行符

    node_t node(buf);

    // 只测五个
    for (int i = 0; i < 5 && node.ip1 < node.ip2; ++i) {
        test_one(node.ip1, node.region);
        node.ip1 = node.ip1 + 1;
    }
    test_one(node.ip2, node.region);
}

void bench_t::test_file(const std::string &file_name) {
    FILE *f = fopen(file_name.data(), "r");
    if (f == NULL)
        xdb::log_exit("can't open " + file_name);
    char buf[1024];
    while (fgets(buf, sizeof(buf), f) != NULL)
        test_line(buf);
}

void bench_t::test(const string &file_name) {
    sum_io_count  = 0;
    sum_cost_time = 0;
    sum_count     = 0;

    unsigned long long tv1 = xdb::get_time();
    test_file(file_name);
    unsigned long long tv2 = xdb::get_time();

    double took = (tv2 - tv1) * 1.0 / 1000 / 1000;
    double cost = sum_cost_time * 1.0 / sum_count;

    printf(
        "total: %llu, took: %8.2fs, cost: %6.2fμs/op, io "
        "count: "
        "%llu\n",
        sum_count,
        took,
        cost,
        sum_io_count);
}

}  // namespace xdb


================================================
FILE: binding/cpp/src/bench.h
================================================
#ifndef BENCH_H
#define BENCH_H

#include "search.h"

namespace xdb {

class bench_t {
public:
    bench_t(const string &file_name, int version, int policy);

    void test(const string &file_name);

private:
    void test_one(const ip_t &ip, const string region);
    void test_line(char *buf);
    void test_file(const std::string &file_name);

    search_t search;

    unsigned long long sum_io_count;
    unsigned long long sum_cost_time;
    unsigned long long sum_count;
};

}  // namespace xdb
#endif


================================================
FILE: binding/cpp/src/edit.cc
================================================

#include "edit.h"

namespace xdb {

void handle_ip_txt(const string& name, std::list<node_t>& regions) {
    FILE* f = fopen(name.data(), "r");
    if (f == NULL)
        log_exit("can't open " + name);

    char buf[1024];
    while (fgets(buf, sizeof(buf), f) != NULL) {
        unsigned int buf_len = strlen(buf);
        // 去掉多余的空
        while (buf_len > 0 && isspace(buf[buf_len - 1]))
            --buf_len;
        if (buf_len == 0)
            continue;
        buf[buf_len] = '\0';
        regions.push_back(node_t(buf));
    }

    fclose(f);
}

void edit_t::handle_new_file(const std::string& file_name) {
    handle_ip_txt(file_name, new_regions);  // 输入
    new_regions.sort();                     // 排序

    // 检验及其去重
    auto it = new_regions.begin();

    for (;;) {
        if (it == new_regions.end())
            break;
        auto next = it;
        ++next;
        if (next == new_regions.end())
            break;
        if (it->ip1 > it->ip2)
            it = new_regions.erase(it);  // 非法, 直接跳过
        else if (it->ip1 == next->ip1 || next->ip1 <= it->ip2) {
            // 数据重叠
            if (it->region != next->region)
                log_exit("数据有二义性: " + it->to_string() + ", " +
                         next->to_string());
            it->ip2 = std::max(it->ip2, next->ip2);
            new_regions.erase(next);
        } else if (it->ip2 + 1 == next->ip1 && it->region == next->region) {
            // 数据连接
            it->ip2 = next->ip2;
            new_regions.erase(next);
        } else {
            ++it;
        }
    }
}

void edit_t::handle_old_file(const std::string& file_name) {
    handle_ip_txt(file_name, old_regions);
}

void edit_t::merge() {
    auto it1 = old_regions.begin();
    auto it2 = new_regions.begin();

    for (;;) {
        if (it2 == new_regions.end())
            break;
        if (it2->ip1 > it2->ip2) {
            ++it2;
            continue;
        }
        // it1->ip1 it1->ip2 it2->ip1 it2->ip2
        while (it1->ip2 < it2->ip1)
            ++it1;
        if (it1->ip2 <= it2->ip2) {
            // it1->ip1 it2->ip1 it1->ip2 it2->ip2
            node_t node;
            node.ip1    = it2->ip1;
            node.ip2    = it1->ip2;
            node.region = it2->region;

            it1->ip2 = node.ip1 - 1;
            it2->ip1 = node.ip2 + 1;

            ++it1;
            it1 = old_regions.insert(it1, node);
            ++it1;
        } else {
            // it1->ip1 it2->ip1 it2->ip2 it1->ip2
            node_t node;
            node.ip1    = it2->ip2 + 1;
            node.ip2    = it1->ip2;
            node.region = it1->region;

            it1->ip2 = it2->ip1 - 1;

            ++it1;
            it1 = old_regions.insert(it1, *it2);

            ++it1;
            it1 = old_regions.insert(it1, node);

            ++it2;
        }
    }
}

void edit_t::write_old_file(const std::string& file_name) {
    FILE* f = fopen(file_name.data(), "w");
    if (f == NULL)
        log_exit("can't open " + file_name);

    auto it = old_regions.begin();

    // 删除非法的数据
    for (;;) {
        if (it == old_regions.end())
            break;
        if (it->ip1 > it->ip2)
            it = old_regions.erase(it);
        else
            ++it;
    }

    // 合并数据域相同的相邻数据
    it = old_regions.begin();
    for (;;) {
        if (it == old_regions.end())
            break;
        auto next = it;
        ++next;
        if (next == old_regions.end())
            break;
        if (it->region == next->region) {
            it->ip2 = next->ip2;
            old_regions.erase(next);
        } else {
            ++it;
        }
    }

    for (auto& d : old_regions) {
        string res =
            d.ip1.to_string() + "|" + d.ip2.to_string() + "|" + d.region + "\n";
        fputs(res.data(), f);
    }

    fclose(f);
}

edit_t::edit_t(const string& name_old, const string& name_new, int version) {
    unsigned long long tv1 = get_time();

    init_xdb(version);

    handle_new_file(name_new);
    handle_old_file(name_old);
    merge();
    write_old_file(name_old);

    unsigned long long tv2 = get_time();

    double took = (tv2 - tv1) * 1.0 / 1000 
gitextract_jvnkjpne/

├── .gitattributes
├── .gitignore
├── LICENSE.md
├── README.md
├── README_zh.md
├── binding/
│   ├── c/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── main.c
│   │   ├── test_util.c
│   │   ├── xdb_api.h
│   │   ├── xdb_searcher.c
│   │   └── xdb_util.c
│   ├── cangjie/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── cjpm.toml
│   │   ├── example/
│   │   │   ├── cjpm.toml
│   │   │   └── src/
│   │   │       └── main.cj
│   │   └── src/
│   │       ├── main.cj
│   │       ├── service/
│   │       │   ├── config.cj
│   │       │   ├── ip2region.cj
│   │       │   └── searcher_pool.cj
│   │       ├── tests/
│   │       │   ├── service/
│   │       │   │   ├── config_test.cj
│   │       │   │   ├── ip2region_test.cj
│   │       │   │   └── searcher_pool_test.cj
│   │       │   ├── tests.cj
│   │       │   └── xdb/
│   │       │       ├── header_test.cj
│   │       │       ├── searcher_test.cj
│   │       │       └── util_test.cj
│   │       └── xdb/
│   │           ├── header.cj
│   │           ├── searcher.cj
│   │           ├── util.cj
│   │           └── version.cj
│   ├── cpp/
│   │   ├── .gitignore
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── src/
│   │   │   ├── base.cc
│   │   │   ├── base.h
│   │   │   ├── bench.cc
│   │   │   ├── bench.h
│   │   │   ├── edit.cc
│   │   │   ├── edit.h
│   │   │   ├── header.cc
│   │   │   ├── header.h
│   │   │   ├── ip.cc
│   │   │   ├── ip.h
│   │   │   ├── make.cc
│   │   │   ├── make.h
│   │   │   ├── search.cc
│   │   │   └── search.h
│   │   └── test/
│   │       ├── bench.cc
│   │       ├── edit_v4.cc
│   │       ├── edit_v6.cc
│   │       ├── header.cc
│   │       ├── make.cc
│   │       └── search.cc
│   ├── csharp/
│   │   ├── .editorconfig
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── IP2Region.Net/
│   │   │   ├── Abstractions/
│   │   │   │   └── ISearcher.cs
│   │   │   ├── Extensions/
│   │   │   │   └── ServiceCollectionExtensions.cs
│   │   │   ├── IP2Region.Net.csproj
│   │   │   ├── Internal/
│   │   │   │   ├── CacheStrategyFactory.cs
│   │   │   │   ├── ContentCacheStrategy.cs
│   │   │   │   ├── FileCacheStrategy.cs
│   │   │   │   ├── ICacheStrategy.cs
│   │   │   │   └── VectorIndexCacheStrategy.cs
│   │   │   └── XDB/
│   │   │       ├── CachePolicy.cs
│   │   │       ├── Searcher.cs
│   │   │       ├── Util.cs
│   │   │       └── XdbVersion.cs
│   │   ├── IP2Region.Net.BenchMark/
│   │   │   ├── Benmarks.cs
│   │   │   ├── IP2Region.Net.BenchMark.csproj
│   │   │   └── Program.cs
│   │   ├── IP2Region.Net.Test/
│   │   │   ├── IP2Region.Net.Test.csproj
│   │   │   ├── SearcherTest.cs
│   │   │   ├── UtilTest.cs
│   │   │   └── XdbTest.cs
│   │   ├── IP2Region.Net.slnx
│   │   └── README.md
│   ├── erlang/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── benchmarks/
│   │   │   └── xdb-benchmark.sh
│   │   ├── include/
│   │   │   └── ip2region.hrl
│   │   ├── priv/
│   │   │   └── dummy
│   │   ├── rebar.config
│   │   ├── src/
│   │   │   ├── ip2region.app.src
│   │   │   ├── ip2region_app.erl
│   │   │   ├── ip2region_sup.erl
│   │   │   ├── ip2region_util.erl
│   │   │   ├── ip2region_worker.erl
│   │   │   ├── ip2region_xdb.erl
│   │   │   ├── xdb.erl
│   │   │   └── xdb_benchmark.erl
│   │   └── test/
│   │       ├── ip2region_sup_test.erl
│   │       ├── ip2region_test_util.erl
│   │       ├── ip2region_util_test.erl
│   │       ├── ip2region_worker_test.erl
│   │       ├── ip2region_xdb_test.erl
│   │       └── xdb_test.erl
│   ├── golang/
│   │   ├── LICENSE.md
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── main.go
│   │   ├── service/
│   │   │   ├── config.go
│   │   │   ├── config_test.go
│   │   │   ├── ip2region.go
│   │   │   ├── ip2region_test.go
│   │   │   ├── searcher_pool.go
│   │   │   └── searcher_pool_test.go
│   │   └── xdb/
│   │       ├── header.go
│   │       ├── searcher.go
│   │       ├── util.go
│   │       ├── util_test.go
│   │       └── version.go
│   ├── java/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── pom.xml
│   │   └── src/
│   │       ├── main/
│   │       │   └── java/
│   │       │       └── org/
│   │       │           └── lionsoul/
│   │       │               └── ip2region/
│   │       │                   ├── SearcherTest.java
│   │       │                   ├── service/
│   │       │                   │   ├── Config.java
│   │       │                   │   ├── ConfigBuilder.java
│   │       │                   │   ├── InvalidConfigException.java
│   │       │                   │   ├── Ip2Region.java
│   │       │                   │   └── SearcherPool.java
│   │       │                   └── xdb/
│   │       │                       ├── Header.java
│   │       │                       ├── IPv4.java
│   │       │                       ├── IPv6.java
│   │       │                       ├── InetAddressException.java
│   │       │                       ├── LittleEndian.java
│   │       │                       ├── Log.java
│   │       │                       ├── LongByteArray.java
│   │       │                       ├── Searcher.java
│   │       │                       ├── Util.java
│   │       │                       ├── Version.java
│   │       │                       └── XdbException.java
│   │       └── test/
│   │           └── java/
│   │               └── org/
│   │                   └── lionsoul/
│   │                       └── ip2region/
│   │                           ├── service/
│   │                           │   ├── ConfigTest.java
│   │                           │   ├── Ip2RegionTest.java
│   │                           │   └── SearcherPoolTest.java
│   │                           └── xdb/
│   │                               ├── BufferTest.java
│   │                               ├── IPv4Test.java
│   │                               ├── LittleEndianTest.java
│   │                               ├── UtilTest.java
│   │                               └── VersionTest.java
│   ├── javascript/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── index.d.ts
│   │   ├── index.js
│   │   ├── package.json
│   │   ├── searcher.js
│   │   ├── tests/
│   │   │   ├── bench.app.js
│   │   │   ├── search.app.js
│   │   │   ├── searcher.test.js
│   │   │   └── util.test.js
│   │   ├── tsconfig.json
│   │   └── util.js
│   ├── lua/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── bench_test.lua
│   │   ├── search_test.lua
│   │   ├── util_test.lua
│   │   └── xdb_searcher.lua
│   ├── lua_c/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── bench_test.lua
│   │   ├── search_test.lua
│   │   ├── util_test.lua
│   │   └── xdb_searcher.c
│   ├── nginx/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── config
│   │   ├── src/
│   │   │   ├── ngx_http_ip2region_module.c
│   │   │   └── ngx_http_ip2region_module.h
│   │   └── t/
│   │       └── http_ip2region.t
│   ├── nodejs/
│   │   └── README.md
│   ├── php/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── batch_test.php
│   │   ├── bench_test.php
│   │   ├── search_test.php
│   │   └── xdb/
│   │       ├── Searcher.class.php
│   │       └── util_test.php
│   ├── python/
│   │   ├── .gitignore
│   │   ├── LICENSE
│   │   ├── MANIFEST.in
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── bench_test.py
│   │   ├── ip2region/
│   │   │   ├── __init__.py
│   │   │   ├── searcher.py
│   │   │   └── util.py
│   │   ├── search_test.py
│   │   ├── setup.py
│   │   └── util_test.py
│   ├── rust/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── example/
│   │   │   ├── Cargo.toml
│   │   │   └── src/
│   │   │       ├── cmd.rs
│   │   │       └── main.rs
│   │   └── ip2region/
│   │       ├── Cargo.toml
│   │       ├── benches/
│   │       │   └── search.rs
│   │       └── src/
│   │           ├── error.rs
│   │           ├── ip_value.rs
│   │           ├── lib.rs
│   │           └── searcher.rs
│   └── typescript/
│       └── README.md
├── data/
│   ├── ip2region_v4.xdb
│   ├── ip2region_v6.xdb
│   ├── ipv4_source.txt
│   ├── ipv6_source.txt
│   └── sample/
│       ├── github-issue-196.fix
│       ├── github-issue-200.fix
│       ├── github-issue-243.fix
│       ├── github-issue-287.bug
│       ├── ip.test.txt
│       ├── segments.tests
│       └── segments.tests.mixed
└── maker/
    ├── c/
    │   └── ReadMe.md
    ├── cpp/
    │   └── README.md
    ├── csharp/
    │   ├── .gitignore
    │   ├── IP2RegionMaker/
    │   │   ├── IP2RegionMaker.csproj
    │   │   ├── Program.cs
    │   │   ├── Properties/
    │   │   │   └── PublishProfiles/
    │   │   │       └── FolderProfile.pubxml
    │   │   └── XDB/
    │   │       ├── IndexPolicy.cs
    │   │       ├── Maker.cs
    │   │       ├── Segment.cs
    │   │       └── Util.cs
    │   ├── IP2RegionMaker.Test/
    │   │   ├── IP2RegionMaker.Test.csproj
    │   │   ├── Usings.cs
    │   │   └── UtilTest.cs
    │   ├── README.md
    │   └── README_zh.md
    ├── golang/
    │   ├── Dockerfile
    │   ├── LICENSE.md
    │   ├── Makefile
    │   ├── README.md
    │   ├── README_zh.md
    │   ├── cmd/
    │   │   ├── bench.go
    │   │   ├── edit.go
    │   │   ├── generate.go
    │   │   ├── process.go
    │   │   ├── search.go
    │   │   └── util.go
    │   ├── go.mod
    │   ├── go.sum
    │   ├── main.go
    │   ├── make.bat
    │   └── xdb/
    │       ├── editor.go
    │       ├── index.go
    │       ├── maker.go
    │       ├── processor.go
    │       ├── region.go
    │       ├── searcher.go
    │       ├── segment.go
    │       ├── util.go
    │       ├── util_test.go
    │       └── version.go
    ├── java/
    │   ├── README.md
    │   ├── README_zh.md
    │   ├── pom.xml
    │   └── src/
    │       ├── main/
    │       │   └── java/
    │       │       └── org/
    │       │           └── lionsoul/
    │       │               └── ip2region/
    │       │                   ├── MakerApp.java
    │       │                   └── xdb/
    │       │                       ├── IPv4.java
    │       │                       ├── IPv6.java
    │       │                       ├── IndexPolicy.java
    │       │                       ├── InvalidInetAddressException.java
    │       │                       ├── LittleEndian.java
    │       │                       ├── Log.java
    │       │                       ├── Maker.java
    │       │                       ├── Segment.java
    │       │                       ├── Util.java
    │       │                       └── Version.java
    │       └── test/
    │           └── java/
    │               └── org/
    │                   └── lionsoul/
    │                       └── ip2region/
    │                           └── xdb/
    │                               ├── IndexPolicyTest.java
    │                               ├── LittleEndianTest.java
    │                               ├── MakerTest.java
    │                               ├── SegmentTest.java
    │                               ├── UtilTest.java
    │                               └── VersionTest.java
    ├── python/
    │   ├── README.md
    │   ├── README_zh.md
    │   ├── main.py
    │   └── xdb/
    │       ├── __init__.py
    │       ├── index.py
    │       ├── maker.py
    │       ├── segment.py
    │       └── util.py
    └── rust/
        ├── README.md
        ├── README_zh.md
        └── maker/
            ├── Cargo.toml
            └── src/
                ├── command.rs
                ├── error.rs
                ├── header.rs
                ├── lib.rs
                ├── main.rs
                ├── maker.rs
                └── segment.rs
Condensed preview — 298 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,150K chars).
[
  {
    "path": ".gitattributes",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".gitignore",
    "chars": 1383,
    "preview": "*.class\r\n*.out\r\n*.o\r\n*.pyc\r\n*~\r\n*.log\r\n*.la\r\n*.so\r\n*.iml\r\nMETA-INF/\r\n.DS_Store\r\n\r\n# Binary Files #\r\n*.jar\r\n!dbMaker-*.ja..."
  },
  {
    "path": "LICENSE.md",
    "chars": 12976,
    "preview": "ip2region is dual-licensed under the Apache License 2.0 and the MIT License.\n\nYou may use, distribute, and modify this s..."
  },
  {
    "path": "README.md",
    "chars": 9523,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region\n\n[ip2region](https://ip2region.net) - is..."
  },
  {
    "path": "README_zh.md",
    "chars": 6004,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\r\n\r\n# ip2region\r\n\r\n[ip2region](https://ip2region.net)..."
  },
  {
    "path": "binding/c/Makefile",
    "chars": 747,
    "preview": "all: xdb_searcher test_util\n\nxdb_searcher: xdb_api.h xdb_util.c xdb_searcher.c main.c\n\tgcc -std=c99 -Wall -O2 -I./ xdb_u..."
  },
  {
    "path": "binding/c/README.md",
    "chars": 14933,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region c Query Client\n\n# Usage\n\n### About Query..."
  },
  {
    "path": "binding/c/README_zh.md",
    "chars": 10965,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region c 查询客户端\n\n\n# 使用方式\n\n### 关于查询 API\n查询 API 的原..."
  },
  {
    "path": "binding/c/main.c",
    "chars": 12869,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/c/test_util.c",
    "chars": 5949,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/c/xdb_api.h",
    "chars": 9035,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/c/xdb_searcher.c",
    "chars": 8173,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/c/xdb_util.c",
    "chars": 13570,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/cangjie/.gitignore",
    "chars": 51,
    "preview": "# build cache\n.cache/\n\n# dependency lock\ncjpm.lock\n"
  },
  {
    "path": "binding/cangjie/README.md",
    "chars": 5385,
    "preview": "# ip2region Cangjie Query Client\n\nThis implementation supports both `IPv4` and `IPv6`, and provides a `thread-safe` serv..."
  },
  {
    "path": "binding/cangjie/cjpm.toml",
    "chars": 346,
    "preview": "[package]\n  cjc-version = \"1.1.0\"\n  name = \"ip2region\"\n  organization = \"\"\n  description = \"ip2region xdb searcher libra..."
  },
  {
    "path": "binding/cangjie/example/cjpm.toml",
    "chars": 239,
    "preview": "[package]\n  cjc-version = \"1.1.0\"\n  name = \"ip2regionExample\"\n  version = \"1.0.0\"\n  output-type = \"executable\"\n  compile..."
  },
  {
    "path": "binding/cangjie/example/src/main.cj",
    "chars": 6073,
    "preview": "package ip2regionExample\n\nimport std.fs.*\nimport std.time.*\nimport std.collection.*\nimport ip2region.xdb.*\nimport ip2reg..."
  },
  {
    "path": "binding/cangjie/src/main.cj",
    "chars": 6662,
    "preview": "package ip2region\n\nimport std.fs.*\nimport std.time.*\nimport std.env.*\nimport ip2region.xdb.*\n\nfunc printHelp() {\n    pri..."
  },
  {
    "path": "binding/cangjie/src/service/config.cj",
    "chars": 2406,
    "preview": "package ip2region.service\n\nimport std.fs.*\nimport ip2region.xdb.*\n\n// Cache policy constants - match Searcher modes\npubl..."
  },
  {
    "path": "binding/cangjie/src/service/ip2region.cj",
    "chars": 5608,
    "preview": "package ip2region.service\n\nimport ip2region.xdb.*\n\n// Ip2Region is the high-level API for searching IP addresses.\n// It..."
  },
  {
    "path": "binding/cangjie/src/service/searcher_pool.cj",
    "chars": 3497,
    "preview": "package ip2region.service\n\nimport std.sync.*\nimport std.core.*\nimport std.time.*\nimport std.collection.concurrent.*\nimpo..."
  },
  {
    "path": "binding/cangjie/src/tests/service/config_test.cj",
    "chars": 1859,
    "preview": "package ip2region.tests.service\n\nimport ip2region.xdb.*\nimport ip2region.service.*\n\n@Test\nclass ConfigV4Tests {\n    @Tes..."
  },
  {
    "path": "binding/cangjie/src/tests/service/ip2region_test.cj",
    "chars": 3571,
    "preview": "package ip2region.tests.service\n\nimport ip2region.xdb.*\nimport ip2region.service.*\n\n@Test\nclass Ip2RegionBasicTests {..."
  },
  {
    "path": "binding/cangjie/src/tests/service/searcher_pool_test.cj",
    "chars": 1594,
    "preview": "package ip2region.tests.service\n\nimport ip2region.xdb.*\nimport ip2region.service.*\n\n@Test\nclass SearcherPoolTests {..."
  },
  {
    "path": "binding/cangjie/src/tests/tests.cj",
    "chars": 24,
    "preview": "package ip2region.tests\n"
  },
  {
    "path": "binding/cangjie/src/tests/xdb/header_test.cj",
    "chars": 2670,
    "preview": "package ip2region.tests.xdb\n\nimport ip2region.xdb.*\n\n@Test\nclass HeaderTests {\n    func makeHeaderBytes(): Array<Byte> {..."
  },
  {
    "path": "binding/cangjie/src/tests/xdb/searcher_test.cj",
    "chars": 4486,
    "preview": "package ip2region.tests.xdb\n\nimport std.fs.*\nimport ip2region.xdb.*\n\n@Test\nclass SearcherV4Tests {\n    var v4Content: Ar..."
  },
  {
    "path": "binding/cangjie/src/tests/xdb/util_test.cj",
    "chars": 4429,
    "preview": "package ip2region.tests.xdb\n\nimport ip2region.xdb.*\n\n@Test\nclass UtilTests {\n    @TestCase\n    func testParseIPv4() {..."
  },
  {
    "path": "binding/cangjie/src/xdb/header.cj",
    "chars": 2460,
    "preview": "package ip2region.xdb\n\n// xdb file format constants\npublic let HeaderInfoLength: Int64 = 256\npublic let VectorIndexRows:..."
  },
  {
    "path": "binding/cangjie/src/xdb/searcher.cj",
    "chars": 4240,
    "preview": "package ip2region.xdb\n\nimport std.fs.*\nimport std.io.SeekPosition\n\n// Searcher for ip2region xdb database\npublic class S..."
  },
  {
    "path": "binding/cangjie/src/xdb/util.cj",
    "chars": 3224,
    "preview": "package ip2region.xdb\n\nimport std.net.IPAddress\n\n// --- IP address parsing and formatting\n\n// Parse IP string to byte ar..."
  },
  {
    "path": "binding/cangjie/src/xdb/version.cj",
    "chars": 1138,
    "preview": "package ip2region.xdb\n\n// IP version definitions for ip2region xdb searcher\npublic class Version {\n    public let id: In..."
  },
  {
    "path": "binding/cpp/.gitignore",
    "chars": 5,
    "preview": "bin/\n"
  },
  {
    "path": "binding/cpp/Makefile",
    "chars": 491,
    "preview": "\nall: bin header search bench make edit\n\nFILES=$(wildcard src/*.cc)\n\nbin:\n\tmkdir -p bin\n\nheader: $(FILES) test/header.cc..."
  },
  {
    "path": "binding/cpp/README.md",
    "chars": 3818,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region C++ query client\n\n## 0. File Description..."
  },
  {
    "path": "binding/cpp/README_zh.md",
    "chars": 2722,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region C++ 查询客户端\n\n## 0. 文件说明\n```\nMakefile -----..."
  },
  {
    "path": "binding/cpp/src/base.cc",
    "chars": 1725,
    "preview": "\n#include \"base.h\"\n\nnamespace xdb {\n\nint ip_version;  // ip 版本\nint ip_size;     // ip 占的字节数\nint content_size;\n\nvoid init..."
  },
  {
    "path": "binding/cpp/src/base.h",
    "chars": 1175,
    "preview": "#ifndef BASE_H\n#define BASE_H\n\n#include <arpa/inet.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#includ..."
  },
  {
    "path": "binding/cpp/src/bench.cc",
    "chars": 1604,
    "preview": "\n#include \"bench.h\"\n\nnamespace xdb {\n\nbench_t::bench_t(const std::string &file_name, int version, int policy)\n    : sear..."
  },
  {
    "path": "binding/cpp/src/bench.h",
    "chars": 509,
    "preview": "#ifndef BENCH_H\n#define BENCH_H\n\n#include \"search.h\"\n\nnamespace xdb {\n\nclass bench_t {\npublic:\n    bench_t(const string..."
  },
  {
    "path": "binding/cpp/src/edit.cc",
    "chars": 4218,
    "preview": "\n#include \"edit.h\"\n\nnamespace xdb {\n\nvoid handle_ip_txt(const string& name, std::list<node_t>& regions) {\n    FILE* f =..."
  },
  {
    "path": "binding/cpp/src/edit.h",
    "chars": 444,
    "preview": "#ifndef EDIT_H\n#define EDIT_H\n\n#include \"ip.h\"\n\nnamespace xdb {\n\nclass edit_t {\npublic:\n    edit_t(const string& old_nam..."
  },
  {
    "path": "binding/cpp/src/header.cc",
    "chars": 708,
    "preview": "\n#include \"header.h\"\n\nnamespace xdb {\n\nheader_t::header_t(FILE* db) {\n    read_bin(0, header, sizeof(header), db);\n}\n\nhe..."
  },
  {
    "path": "binding/cpp/src/header.h",
    "chars": 460,
    "preview": "#ifndef HEADER_H\n#define HEADER_H\n\n#include \"base.h\"\n\nnamespace xdb {\n\nclass header_t {\npublic:\n    header_t(FILE* db);..."
  },
  {
    "path": "binding/cpp/src/ip.cc",
    "chars": 3256,
    "preview": "\n#include \"ip.h\"\n\nnamespace xdb {\n\nip_t::ip_t() {\n    memset(p, '\\0', sizeof(p));\n}\n\nip_t::ip_t(const ip_t& rhs, int val..."
  },
  {
    "path": "binding/cpp/src/ip.h",
    "chars": 1026,
    "preview": "#ifndef IP_H\n#define IP_H\n\n#include \"base.h\"\n\nnamespace xdb {\n\nstruct ip_t {\n    unsigned char p[16];\n\n    ip_t();\n    i..."
  },
  {
    "path": "binding/cpp/src/make.cc",
    "chars": 4461,
    "preview": "\n#include \"make.h\"\n\nnamespace xdb {\n\nvoid make_t::vector_index_push_back(int row, int col, const node_t &node) {\n    vec..."
  },
  {
    "path": "binding/cpp/src/make.h",
    "chars": 706,
    "preview": "#ifndef MAKE_H\n#define MAKE_H\n\n#include \"ip.h\"\n\nnamespace xdb {\n\nclass make_t {\npublic:\n    make_t(const string &src, co..."
  },
  {
    "path": "binding/cpp/src/search.cc",
    "chars": 3600,
    "preview": "\n#include \"search.h\"\n\nnamespace xdb {\n\nsearch_t::search_t(const string &file, int version, int p)\n    : db(fopen(file.da..."
  },
  {
    "path": "binding/cpp/src/search.h",
    "chars": 923,
    "preview": "#ifndef SEARCH_H\n#define SEARCH_H\n\n#include \"header.h\"\n#include \"ip.h\"\n\nnamespace xdb {\n\nclass search_t {\nprotected:..."
  },
  {
    "path": "binding/cpp/test/bench.cc",
    "chars": 820,
    "preview": "\n#include \"../src/bench.h\"\n\nstd::map<int, std::string> prompt;\n\nvoid test_ipv4(int policy) {\n    std::cout << \"测试 IPv4,..."
  },
  {
    "path": "binding/cpp/test/edit_v4.cc",
    "chars": 223,
    "preview": "\n#include \"../src/edit.h\"\n\nint main() {\n    std::string file_name_old = \"../../data/ipv4_source.txt\";\n    std::string fi..."
  },
  {
    "path": "binding/cpp/test/edit_v6.cc",
    "chars": 223,
    "preview": "\n#include \"../src/edit.h\"\n\nint main() {\n    std::string file_name_old = \"../../data/ipv6_source.txt\";\n    std::string fi..."
  },
  {
    "path": "binding/cpp/test/header.cc",
    "chars": 933,
    "preview": "\n#include \"../src/header.h\"\n\nvoid test(const std::string& prompt, const std::string& file_name) {\n    std::cout << promp..."
  },
  {
    "path": "binding/cpp/test/make.cc",
    "chars": 549,
    "preview": "\n#include \"../src/make.h\"\n\nvoid test(const std::string& prompt,\n          const std::string& filename_xdb,\n          con..."
  },
  {
    "path": "binding/cpp/test/search.cc",
    "chars": 1364,
    "preview": "\n#include \"../src/search.h\"\n\nstd::map<int, std::string> prompt;\n\nvoid test(xdb::search_t& s, const std::string& ip, cons..."
  },
  {
    "path": "binding/csharp/.editorconfig",
    "chars": 759,
    "preview": "root = true\n\n# To learn more about .editorconfig see https://aka.ms/editorconfigdocs\n[*]\ncharset = utf-8\nindent_style =..."
  },
  {
    "path": "binding/csharp/.gitignore",
    "chars": 5691,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G..."
  },
  {
    "path": "binding/csharp/CHANGELOG.md",
    "chars": 533,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [3.0.0] - 2025-11-22\n- 支持 .NET 10...."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Abstractions/ISearcher.cs",
    "chars": 1045,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Extensions/ServiceCollectionExtensions.cs",
    "chars": 1339,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/IP2Region.Net.csproj",
    "chars": 2892,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <id>IP2Region.Net</id>\n        <version>3.0.2</version>..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Internal/CacheStrategyFactory.cs",
    "chars": 699,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Internal/ContentCacheStrategy.cs",
    "chars": 1047,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Internal/FileCacheStrategy.cs",
    "chars": 2270,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Internal/ICacheStrategy.cs",
    "chars": 538,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/Internal/VectorIndexCacheStrategy.cs",
    "chars": 824,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/XDB/CachePolicy.cs",
    "chars": 670,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/XDB/Searcher.cs",
    "chars": 4639,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/XDB/Util.cs",
    "chars": 2824,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net/XDB/XdbVersion.cs",
    "chars": 1090,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs",
    "chars": 2482,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.BenchMark/IP2Region.Net.BenchMark.csproj",
    "chars": 952,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <OutputType>Exe</OutputType>\n        <TargetFramework>net..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.BenchMark/Program.cs",
    "chars": 384,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.Test/IP2Region.Net.Test.csproj",
    "chars": 1777,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net10.0</TargetFramework>\n\t\t<ImplicitUsings>enab..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.Test/SearcherTest.cs",
    "chars": 6202,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.Test/UtilTest.cs",
    "chars": 674,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.Test/XdbTest.cs",
    "chars": 1880,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/csharp/IP2Region.Net.slnx",
    "chars": 332,
    "preview": "<Solution>\n  <Folder Name=\"/Solution Items/\">\n    <File Path=\".editorconfig\" />\n    <File Path=\"README.md\" />\n  </Folder..."
  },
  {
    "path": "binding/csharp/README.md",
    "chars": 3854,
    "preview": "# IP2Region.Net\n\n.NET client library for IP2Region\n\n## Installation\n\nInstall the package with [NuGet](https://www.nuget...."
  },
  {
    "path": "binding/erlang/Makefile",
    "chars": 228,
    "preview": ".PHONY: all compile test clean bench-v4 bench-v6\n\nall: compile\n\ncompile:\n\trebar3 compile\n\ntest:\n\trebar3 eunit\n\nclean:\n\tr..."
  },
  {
    "path": "binding/erlang/README.md",
    "chars": 4492,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region Erlang query client\n\n### Introduction\n\nT..."
  },
  {
    "path": "binding/erlang/README_zh.md",
    "chars": 3614,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region Erlang 查询客户端\n\n### 简介\n\n该 bingding 以 `Erla..."
  },
  {
    "path": "binding/erlang/benchmarks/xdb-benchmark.sh",
    "chars": 1029,
    "preview": "#!/bin/bash\nset -e\n\n# Run from binding/erlang so paths and rebar3 artifacts are correct.\ncd \"$(dirname \"$0\")/..\"\n\nVERSIO..."
  },
  {
    "path": "binding/erlang/include/ip2region.hrl",
    "chars": 1330,
    "preview": "-ifndef(IP2REGION_HRL).\n-define(IP2REGION_HRL, true).\n\n-define(NONE, none).\n-define(APP_NAME, ip2region).\n\n-define(XDB_H..."
  },
  {
    "path": "binding/erlang/priv/dummy",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "binding/erlang/rebar.config",
    "chars": 396,
    "preview": "{erl_opts, [\n    debug_info,\n    export_all,\n    nowarn_export_all\n]}.\n\n{plugins, [rebar3_hex, rebar3_ex_doc]}.\n\n{deps,..."
  },
  {
    "path": "binding/erlang/src/ip2region.app.src",
    "chars": 490,
    "preview": "{application, ip2region,\n [{description, \"ip2region xdb client application\"},\n  {vsn, \"0.1.0\"},\n  {registered, []},\n  {m..."
  },
  {
    "path": "binding/erlang/src/ip2region_app.erl",
    "chars": 529,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/src/ip2region_sup.erl",
    "chars": 2722,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/src/ip2region_util.erl",
    "chars": 3012,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/src/ip2region_worker.erl",
    "chars": 8795,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/src/ip2region_xdb.erl",
    "chars": 2413,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/src/xdb.erl",
    "chars": 1682,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/src/xdb_benchmark.erl",
    "chars": 4279,
    "preview": "%%%-------------------------------------------------------------------\n%% Copyright 2022 The Ip2Region Authors. All righ..."
  },
  {
    "path": "binding/erlang/test/ip2region_sup_test.erl",
    "chars": 269,
    "preview": "-module(ip2region_sup_test).\n-include_lib(\"eunit/include/eunit.hrl\").\n-include(\"ip2region.hrl\").\n\npools_started_test() -..."
  },
  {
    "path": "binding/erlang/test/ip2region_test_util.erl",
    "chars": 1068,
    "preview": "-module(ip2region_test_util).\n\n-export([\n    repo_root/0,\n    v6_xdb_path/0,\n    default_db_config/0,\n    reset_app/0,..."
  },
  {
    "path": "binding/erlang/test/ip2region_util_test.erl",
    "chars": 3667,
    "preview": "-module(ip2region_util_test).\n-include_lib(\"eunit/include/eunit.hrl\").\n\nipv4_inputs_test_() ->\n    [\n        ?_assertEqu..."
  },
  {
    "path": "binding/erlang/test/ip2region_worker_test.erl",
    "chars": 400,
    "preview": "-module(ip2region_worker_test).\n-include_lib(\"eunit/include/eunit.hrl\").\n-include(\"ip2region.hrl\").\n\nworker_search_v4_bi..."
  },
  {
    "path": "binding/erlang/test/ip2region_xdb_test.erl",
    "chars": 1953,
    "preview": "-module(ip2region_xdb_test).\n\n-include_lib(\"eunit/include/eunit.hrl\").\n-include(\"ip2region.hrl\").\n\nvalid_v4_header_test(..."
  },
  {
    "path": "binding/erlang/test/xdb_test.erl",
    "chars": 1808,
    "preview": "-module(xdb_test).\n\n-include_lib(\"eunit/include/eunit.hrl\").\n\n-define(IPV6_RESULT, \"United States|Florida|Miami|Google L..."
  },
  {
    "path": "binding/golang/LICENSE.md",
    "chars": 12640,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004..."
  },
  {
    "path": "binding/golang/Makefile",
    "chars": 165,
    "preview": "# ip2region golang binding makefile\nall: build\n.PHONY: all\n\nbuild:\n\tgo build -o xdb_searcher\ntest:\n\tgo test -v ./...\ncle..."
  },
  {
    "path": "binding/golang/README.md",
    "chars": 10995,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region golang query client\n\n# Usage\n\n### packag..."
  },
  {
    "path": "binding/golang/README_zh.md",
    "chars": 7349,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region golang 查询客户端\n\n# 使用方式\n\n### package 获取\n```..."
  },
  {
    "path": "binding/golang/go.mod",
    "chars": 113,
    "preview": "module github.com/lionsoul2014/ip2region/binding/golang\n\ngo 1.18\n\nrequire github.com/mitchellh/go-homedir v1.1.0\n"
  },
  {
    "path": "binding/golang/go.sum",
    "chars": 181,
    "preview": "github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v..."
  },
  {
    "path": "binding/golang/main.go",
    "chars": 9606,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/service/config.go",
    "chars": 3429,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/service/config_test.go",
    "chars": 901,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/service/ip2region.go",
    "chars": 4704,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/service/ip2region_test.go",
    "chars": 6835,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/service/searcher_pool.go",
    "chars": 2292,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/service/searcher_pool_test.go",
    "chars": 1808,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/xdb/header.go",
    "chars": 1958,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/xdb/searcher.go",
    "chars": 5951,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/xdb/util.go",
    "chars": 7440,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/xdb/util_test.go",
    "chars": 4654,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/golang/xdb/version.go",
    "chars": 2252,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/README.md",
    "chars": 17271,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region java Query Client\n\n# Usage\n\n### Maven Re..."
  },
  {
    "path": "binding/java/README_zh.md",
    "chars": 12180,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region java 查询客户端\n\n# 使用方式\n\n### maven 仓库:\n```xml..."
  },
  {
    "path": "binding/java/pom.xml",
    "chars": 7897,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/SearcherTest.java",
    "chars": 12184,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/service/Config.java",
    "chars": 3694,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/service/ConfigBuilder.java",
    "chars": 4749,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/service/InvalidConfigException.java",
    "chars": 173,
    "preview": "package org.lionsoul.ip2region.service;\n\npublic class InvalidConfigException extends Exception {\n    public InvalidConfi..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/service/Ip2Region.java",
    "chars": 5768,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/service/SearcherPool.java",
    "chars": 3640,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java",
    "chars": 1513,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv4.java",
    "chars": 1427,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv6.java",
    "chars": 755,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/InetAddressException.java",
    "chars": 340,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/LittleEndian.java",
    "chars": 2080,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/Log.java",
    "chars": 3189,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/LongByteArray.java",
    "chars": 5466,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java",
    "chars": 14444,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java",
    "chars": 3471,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/Version.java",
    "chars": 2748,
    "preview": "// Copyright 2025 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/main/java/org/lionsoul/ip2region/xdb/XdbException.java",
    "chars": 324,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/service/ConfigTest.java",
    "chars": 4230,
    "preview": "package org.lionsoul.ip2region.service;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/service/Ip2RegionTest.java",
    "chars": 7299,
    "preview": "package org.lionsoul.ip2region.service;\n\nimport static org.junit.Assert.assertEquals;\n\nimport java.io.IOException;\nimpor..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/service/SearcherPoolTest.java",
    "chars": 2946,
    "preview": "package org.lionsoul.ip2region.service;\n\nimport org.junit.Test;\nimport org.lionsoul.ip2region.xdb.Log;\nimport org.lionso..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/xdb/BufferTest.java",
    "chars": 3395,
    "preview": "package org.lionsoul.ip2region.xdb;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nim..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/xdb/IPv4Test.java",
    "chars": 1437,
    "preview": "package org.lionsoul.ip2region.xdb;\n\nimport org.junit.Test;\n\npublic class IPv4Test {\n\n    private static final Log log =..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/xdb/LittleEndianTest.java",
    "chars": 1126,
    "preview": "package org.lionsoul.ip2region.xdb;\n\nimport static org.junit.Assert.assertEquals;\n\nimport org.junit.Test;\n\npublic class..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/xdb/UtilTest.java",
    "chars": 2033,
    "preview": "package org.lionsoul.ip2region.xdb;\n\nimport org.junit.Test;\n\npublic class UtilTest {\n\n    private static final Log log =..."
  },
  {
    "path": "binding/java/src/test/java/org/lionsoul/ip2region/xdb/VersionTest.java",
    "chars": 1326,
    "preview": "package org.lionsoul.ip2region.xdb;\n\nimport static org.junit.Assert.assertEquals;\n\nimport org.junit.Test;\n\npublic class..."
  },
  {
    "path": "binding/javascript/README.md",
    "chars": 9413,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region JavaScript Query Client\n\n# Usage\n\n### In..."
  },
  {
    "path": "binding/javascript/README_zh.md",
    "chars": 6867,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region javascript 查询客户端\n\n# 使用方式\n\n### 安装 `ip2reg..."
  },
  {
    "path": "binding/javascript/index.d.ts",
    "chars": 3128,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/index.js",
    "chars": 908,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/package.json",
    "chars": 1271,
    "preview": "{\n  \"name\": \"ip2region.js\",\n  \"version\": \"3.1.8\",\n  \"description\": \"official javascript binding for ip2region with both..."
  },
  {
    "path": "binding/javascript/searcher.js",
    "chars": 4914,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/tests/bench.app.js",
    "chars": 3876,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/tests/search.app.js",
    "chars": 3462,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/tests/searcher.test.js",
    "chars": 4528,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/tests/util.test.js",
    "chars": 3839,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/javascript/tsconfig.json",
    "chars": 509,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"moduleResolution\": \"node\",\n    \"allowJs\":..."
  },
  {
    "path": "binding/javascript/util.js",
    "chars": 11234,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua/README.md",
    "chars": 9727,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region lua query client\n\n#### Note: Please prio..."
  },
  {
    "path": "binding/lua/README_zh.md",
    "chars": 6818,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region lua 查询客户端\n\n#### 备注:请优先使用 lua_c 扩展 xdb 查询..."
  },
  {
    "path": "binding/lua/bench_test.lua",
    "chars": 5598,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua/search_test.lua",
    "chars": 4465,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua/util_test.lua",
    "chars": 5340,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua/xdb_searcher.lua",
    "chars": 19003,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua_c/Makefile",
    "chars": 510,
    "preview": "LuaVersion ?= 5.4\nLIB_DIR = /usr/local/share/lua/$(LuaVersion)\n\nall: ../c/xdb_api.h ../c/xdb_util.c ../c/xdb_searcher.c..."
  },
  {
    "path": "binding/lua_c/README.md",
    "chars": 10565,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region lua c extension query client\n\n# Version..."
  },
  {
    "path": "binding/lua_c/README_zh.md",
    "chars": 7387,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region lua c 扩展查询客户端\n\n# 版本兼容\n该实现兼容 lua `5.1`,`5..."
  },
  {
    "path": "binding/lua_c/bench_test.lua",
    "chars": 5653,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua_c/search_test.lua",
    "chars": 4615,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua_c/util_test.lua",
    "chars": 4807,
    "preview": "-- Copyright 2022 The Ip2Region Authors. All rights reserved.\n-- Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/lua_c/xdb_searcher.c",
    "chars": 22728,
    "preview": "// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2.0-styl..."
  },
  {
    "path": "binding/nginx/Dockerfile",
    "chars": 1287,
    "preview": "ARG NGINX_VERSION=1.29.6\nFROM nginx:${NGINX_VERSION} AS build\nARG NGINX_VERSION\n\n# prepare the build environment\nRUN apt..."
  },
  {
    "path": "binding/nginx/README.md",
    "chars": 2654,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# nginx-ip2region\n\n## build\n\n```shell\n$ mkdir -p wor..."
  },
  {
    "path": "binding/nginx/README_zh.md",
    "chars": 2504,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# nginx-ip2region\n\n## build\n\n```shell\n$ mkdir -p wor..."
  },
  {
    "path": "binding/nginx/config",
    "chars": 756,
    "preview": "ngx_addon_name=ngx_http_ip2region_module\n\nNGX_HTTP_IP2REGION_SRCS=\"..."
  },
  {
    "path": "binding/nginx/src/ngx_http_ip2region_module.c",
    "chars": 19361,
    "preview": "/*\n * Created by Wu Jian Ping on - 2023/03/30.\n */\n\n#include \"ngx_http_ip2region_module.h\"\n\nstatic ngx_int_t ngx_http_ip..."
  },
  {
    "path": "binding/nginx/src/ngx_http_ip2region_module.h",
    "chars": 690,
    "preview": "/*\n * Created by Wu Jian Ping on - 2023/03/30.\n */\n\n#ifndef __NGX_HTTP_IP2REGION_MODULE_H_INCLUDED__\n#define __NGX_HTTP_..."
  },
  {
    "path": "binding/nginx/t/http_ip2region.t",
    "chars": 375,
    "preview": "use lib 'lib';\nuse Test::Nginx::Socket; # 'no_plan';\n\nrepeat_each(2);\n\nplan tests => repeat_each() * 124;\n\nno_long_strin..."
  },
  {
    "path": "binding/nodejs/README.md",
    "chars": 282,
    "preview": "# :cn: [中文简体]\n\n# ip2region nodejs 查询客户端\n\n请使用最新的 IPv6 兼容的 javascript binding:[javascript binding](../javascript/)\n\n---\n\n#..."
  },
  {
    "path": "binding/php/README.md",
    "chars": 9313,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region PHP Query Client\n\n# Usage\n\n### About Que..."
  },
  {
    "path": "binding/php/README_zh.md",
    "chars": 6821,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region php 查询客户端\n\n# 使用方式\n\n### 关于查询 API\n查询 API 的..."
  },
  {
    "path": "binding/php/batch_test.php",
    "chars": 4318,
    "preview": "<?php\n// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2...."
  },
  {
    "path": "binding/php/bench_test.php",
    "chars": 5715,
    "preview": "<?php\n// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2...."
  },
  {
    "path": "binding/php/search_test.php",
    "chars": 4216,
    "preview": "<?php\n// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2...."
  },
  {
    "path": "binding/php/xdb/Searcher.class.php",
    "chars": 17205,
    "preview": "<?php\n// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2...."
  },
  {
    "path": "binding/php/xdb/util_test.php",
    "chars": 3409,
    "preview": "<?php\n// Copyright 2022 The Ip2Region Authors. All rights reserved.\n// Use of this source code is governed by a Apache2...."
  },
  {
    "path": "binding/python/.gitignore",
    "chars": 332,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# Distribution / packaging\n.Python\nbuild/\ndev..."
  },
  {
    "path": "binding/python/LICENSE",
    "chars": 12640,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004..."
  },
  {
    "path": "binding/python/MANIFEST.in",
    "chars": 103,
    "preview": "include LICENSE\ninclude README.md\ninclude search_test.py\ninclude bench_test.py\nrecursive-include tests\n"
  },
  {
    "path": "binding/python/README.md",
    "chars": 9113,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region python query client\n\n# Version Compatibi..."
  },
  {
    "path": "binding/python/README_zh.md",
    "chars": 6606,
    "preview": ":globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)\n\n# ip2region python 查询客户端\n\n# 版本兼容\n该实现兼容 Python `>=` *..."
  },
  {
    "path": "binding/python/bench_test.py",
    "chars": 4129,
    "preview": "# Copyright 2022 The Ip2Region Authors. All rights reserved.\n# Use of this source code is governed by a Apache2.0-style..."
  },
  {
    "path": "binding/python/ip2region/__init__.py",
    "chars": 184,
    "preview": "# Copyright 2022 The Ip2Region Authors. All rights reserved.\n# Use of this source code is governed by a Apache2.0-style..."
  },
  {
    "path": "binding/python/ip2region/searcher.py",
    "chars": 4981,
    "preview": "# Copyright 2022 The Ip2Region Authors. All rights reserved.\n# Use of this source code is governed by a Apache2.0-style..."
  },
  {
    "path": "binding/python/ip2region/util.py",
    "chars": 7260,
    "preview": "# Copyright 2022 The Ip2Region Authors. All rights reserved.\n# Use of this source code is governed by a Apache2.0-style..."
  },
  {
    "path": "binding/python/search_test.py",
    "chars": 3387,
    "preview": "# Copyright 2022 The Ip2Region Authors. All rights reserved.\n# Use of this source code is governed by a Apache2.0-style..."
  },
  {
    "path": "binding/python/setup.py",
    "chars": 1049,
    "preview": "import setuptools\n\nsetuptools.setup(\n    name=\"py-ip2region\",\n    version=\"3.0.4\",\n    description=\"ip2region official..."
  },
  {
    "path": "binding/python/util_test.py",
    "chars": 6057,
    "preview": "# Copyright 2022 The Ip2Region Authors. All rights reserved.\n# Use of this source code is governed by a Apache2.0-style..."
  }
]

// ... and 98 more files (download for full content)

About this extraction

This page contains the full source code of the lionsoul2014/ip2region GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 302 files (155.6 MB), approximately 317.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!