diff --git a/binding/csharp/IP2Region/xdb/Searcher.cs b/binding/csharp/IP2Region/xdb/Searcher.cs
index c04c166fa5819bc0eb82c58de4655a8d51c37b48..210b6072ff079ebf886c350b22e955f2e5b0aca7 100644
--- a/binding/csharp/IP2Region/xdb/Searcher.cs
+++ b/binding/csharp/IP2Region/xdb/Searcher.cs
@@ -247,8 +247,7 @@ namespace IP2Region.xdb
/* check the specified ip address */
public static long checkIP(String ip)
{
- String[]
- ps = ip.Split('.');
+ String[] ps = ip.Split('.');
if (ps.Length != 4) throw new Exception("invalid ip address `" + ip + "`");
long ipDst = 0;
diff --git a/maker/csharp/.gitignore b/maker/csharp/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2ecf253d4958e8d401904f375178c7de7b51be35
--- /dev/null
+++ b/maker/csharp/.gitignore
@@ -0,0 +1,337 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
\ No newline at end of file
diff --git a/maker/csharp/IP2Region.Maker.Test/IP2Region.Maker.Test.csproj b/maker/csharp/IP2Region.Maker.Test/IP2Region.Maker.Test.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..86432940130e1dffbb7ade6be61f82b12f84fcdd
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker.Test/IP2Region.Maker.Test.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ ip2region-maker-dotnet
+
+
+
+
+
+
+
diff --git a/maker/csharp/IP2Region.Maker.Test/Program.cs b/maker/csharp/IP2Region.Maker.Test/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a367dd7b56988ef08ccf04ccf224aaca950128c
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker.Test/Program.cs
@@ -0,0 +1,99 @@
+
+using IP2Region.Maker;
+
+public static class Program
+{
+ public static Log log = Log.getLogger(typeof(Program));
+
+ public static void printHelp(String[] args)
+ {
+ Console.WriteLine("ip2region xdb maker");
+ Console.WriteLine("ip2region-maker-dotnet [command options]");
+ Console.WriteLine("options:");
+ Console.WriteLine(" --src string source ip text file path");
+ Console.WriteLine(" --dst string destination binary xdb file path");
+ }
+
+ public static void genDb(String[] args)
+ {
+ String srcFile = "", dstFile = "";
+ int indexPolicy = IndexPolicy.Vector;
+ foreach (String r in args)
+ {
+ if (r.Length < 5)
+ {
+ continue;
+ }
+
+ if (r.IndexOf("--") != 0)
+ {
+ continue;
+ }
+
+ int sIdx = r.IndexOf('=');
+ if (sIdx < 0)
+ {
+ Console.WriteLine("missing = for args pair `{0}`", r);
+ return;
+ }
+
+ String key = r.Substring(2, sIdx - 2);
+ String val = r.Substring(sIdx + 1);
+ // System.out.printf("key=%s, val=%s", key, val);
+ if ("src" == (key))
+ {
+ srcFile = val;
+ }
+ else if ("dst" == (key))
+ {
+ dstFile = val;
+ }
+ else if ("index" == (key))
+ {
+ try
+ {
+ indexPolicy = IndexPolicy.Parse(val);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("parse policy " + e);
+ }
+ }
+ else
+ {
+ Console.WriteLine("undefined option `{0}`", r);
+ return;
+ }
+ }
+
+ if (srcFile.Length < 1 || dstFile.Length < 1)
+ {
+ printHelp(args);
+ return;
+ }
+
+ long tStart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ Maker maker = new Maker(indexPolicy, srcFile, dstFile);
+ maker.init();
+ maker.start();
+ maker.end();
+
+ log.infof("Done, elapsed: {0} s", (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - tStart) / 1000);
+ }
+ public static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ printHelp(args);
+ return;
+ }
+ try
+ {
+ genDb(args);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("failed running genDb: {0}", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/maker/csharp/IP2Region.Maker.sln b/maker/csharp/IP2Region.Maker.sln
new file mode 100644
index 0000000000000000000000000000000000000000..36c904f3822b63b7f5a935ca9395a8b58524b764
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32228.430
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IP2Region.Maker", "IP2Region.Maker\IP2Region.Maker.csproj", "{B6522718-0981-43F1-AF6E-DC26FF28E115}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IP2Region.Maker.Test", "IP2Region.Maker.Test\IP2Region.Maker.Test.csproj", "{49D73A7F-D0D0-4BC6-B640-7D294B246FC0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B6522718-0981-43F1-AF6E-DC26FF28E115}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B6522718-0981-43F1-AF6E-DC26FF28E115}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B6522718-0981-43F1-AF6E-DC26FF28E115}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B6522718-0981-43F1-AF6E-DC26FF28E115}.Release|Any CPU.Build.0 = Release|Any CPU
+ {49D73A7F-D0D0-4BC6-B640-7D294B246FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {49D73A7F-D0D0-4BC6-B640-7D294B246FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {49D73A7F-D0D0-4BC6-B640-7D294B246FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {49D73A7F-D0D0-4BC6-B640-7D294B246FC0}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {6AEA440B-0D90-4F6E-BA9A-D42B27A78D7B}
+ EndGlobalSection
+EndGlobal
diff --git a/maker/csharp/IP2Region.Maker/IP2Region.Maker.csproj b/maker/csharp/IP2Region.Maker/IP2Region.Maker.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..9f5c4f4abb611103e8d62ab979dda9bfcb5a7fc2
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker/IP2Region.Maker.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/maker/csharp/IP2Region.Maker/IndexPolicy.cs b/maker/csharp/IP2Region.Maker/IndexPolicy.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3c79e80b9757cc55636b0c2f302e28fa37183af4
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker/IndexPolicy.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace IP2Region.Maker
+{
+ public class IndexPolicy
+ {
+ public static int Vector = 1;
+ public static int BTree = 2;
+
+ // parser the index policy from string
+ public static int Parse(String policy)
+ {
+ String v = policy.ToLowerInvariant();
+ if ("vector" == v)
+ {
+ return Vector;
+ }
+ else if ("btree" == v)
+ {
+ return BTree;
+ }
+ else
+ {
+ throw new Exception("unknown index policy `" + policy + "`");
+ }
+ }
+ }
+}
diff --git a/maker/csharp/IP2Region.Maker/Log.cs b/maker/csharp/IP2Region.Maker/Log.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1f262e4d46dd754521a3a209880cfa498a41851a
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker/Log.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace IP2Region.Maker
+{
+
+ // simple log implementation
+ public class Log
+ {
+
+ /* Log level constants define */
+ public static int DEBUG = 0;
+ public static int INFO = 1;
+ public static int WARN = 2;
+ public static int ERROR = 3;
+
+ // level name
+ public static String[] level_string = new String[] {
+ "DEBUG",
+ "INFO",
+ "WARN",
+ "ERROR"
+ };
+
+ public Type baseClass;
+ private static int level;
+
+ public Log(Type baseClass)
+ {
+ this.baseClass = baseClass;
+ }
+
+ public static Log getLogger(Type baseClass)
+ {
+ return new Log(baseClass);
+ }
+
+ public String format(int level, String format, params Object[] args)
+ {
+ // append the datetime
+ StringBuilder sb = new StringBuilder();
+ //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ //sb.append(String.format("%s %-5s ", sdf.format(new Date()), level_string[level]));
+ sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss} {1} ", DateTime.Now, level_string[level]);
+
+ // append the class name
+ sb.Append(baseClass.Name).Append(' ');
+ sb.Append(String.Format(format, args));
+ return sb.ToString();
+ }
+
+ public void printf(int level, String format, params Object[] args)
+ {
+ if (level < DEBUG || level > ERROR)
+ {
+ throw new ArgumentOutOfRangeException("invalid level index " + level);
+ }
+
+ // level filter
+ if (level < Log.level)
+ {
+ return;
+ }
+
+ Console.WriteLine(this.format(level, format, args));
+ }
+
+ public String getDebugf(String format, params Object[] args)
+ {
+ return this.format(DEBUG, format, args);
+ }
+
+ public void debugf(String format, params Object[] args)
+ {
+ printf(DEBUG, format, args);
+ }
+
+ public String getInfof(String format, params Object[] args)
+ {
+ return this.format(INFO, format, args);
+ }
+
+ public void infof(String format, params Object[] args)
+ {
+ printf(INFO, format, args);
+ }
+
+ public String getWarnf(String format, params Object[] args)
+ {
+ return this.format(WARN, format, args);
+ }
+
+ public void warnf(String format, params Object[] args)
+ {
+ printf(WARN, format, args);
+ }
+
+ public String getErrorf(String format, params Object[] args)
+ {
+ return this.format(ERROR, format, args);
+ }
+
+ public void errorf(String format, params Object[] args)
+ {
+ printf(ERROR, format, args);
+ }
+
+ public static void setLevel(int level)
+ {
+ Log.level = level;
+ }
+
+ public static void setLevel(String level)
+ {
+ String v = level.ToLowerInvariant();
+ if ("debug" == v)
+ {
+ Log.level = DEBUG;
+ }
+ else if ("info" == v)
+ {
+ Log.level = INFO;
+ }
+ else if ("warn" == v)
+ {
+ Log.level = WARN;
+ }
+ else if ("error" == v)
+ {
+ Log.level = ERROR;
+ }
+ }
+
+ }
+}
diff --git a/maker/csharp/IP2Region.Maker/Maker.cs b/maker/csharp/IP2Region.Maker/Maker.cs
new file mode 100644
index 0000000000000000000000000000000000000000..530d8693889f73084f8b298e578e8c9f0fa97f6d
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker/Maker.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace IP2Region.Maker
+{
+ public class Maker
+ {
+ // constants define
+ public static int VersionNo = 2;
+ public static int HeaderInfoLength = 256;
+ public static int VectorIndexRows = 256;
+ public static int VectorIndexCols = 256;
+ public static int VectorIndexSize = 8;
+ public static int SegmentIndexSize = 14;
+ public static int VectorIndexLength = VectorIndexRows * VectorIndexCols * VectorIndexSize;
+
+ private static Log log = Log.getLogger(typeof(Maker));
+
+
+ // source text file handle
+ private FileInfo srcFile;
+ private List segments;
+ private Encoding bytesCharset;
+
+ // destination binary file handle
+ private FileStream dstHandle;
+
+ // index policy
+ private int indexPolicy;
+
+ // region pool
+ private Dictionary regionPool;
+
+ // vector index raw bytes
+ private byte[] vectorIndex;
+
+ public Maker(int policy, String srcFile, String dstFile)
+ {
+ this.srcFile = new FileInfo(srcFile);
+ if (!this.srcFile.Exists)
+ {
+ throw new FileNotFoundException("source text file `" + srcFile + "` not found");
+ }
+
+ this.bytesCharset = Encoding.UTF8;
+ this.segments = new List();
+ this.dstHandle = File.OpenWrite(dstFile);
+ this.indexPolicy = policy;
+ this.regionPool = new Dictionary();
+ this.vectorIndex = new byte[VectorIndexLength]; // all filled with 0
+ }
+
+ // init the header of the target xdb binary file
+ private void initHeader()
+ {
+ log.infof("try to init the db header ... ");
+ dstHandle.Seek(0, SeekOrigin.Begin);
+
+ // make and write the header space
+ byte[]
+ header = new byte[HeaderInfoLength];
+
+ // encode the data
+ Util.write(header, 0, VersionNo, 2);
+ Util.write(header, 2, indexPolicy, 2);
+ Util.write(header, 4, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000, 4);
+ Util.write(header, 8, 0, 4); // start index ptr
+ Util.write(header, 12, 0, 4); // end index ptr
+
+ dstHandle.Write(header, 0, header.Length);
+ }
+
+ // load all the segments
+ private void loadSegments()
+ {
+ log.infof("try to load the segments ... ");
+ long tStart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ Segment last = null;
+
+ //FileInputStream fis = new FileInputStream(srcFile);
+ //BufferedReader br = new BufferedReader(new InputStreamReader(fis, bytesCharset));
+
+ // while ((line = br.readLine()) != null)
+ foreach (var line in File.ReadLines(srcFile.FullName, this.bytesCharset))
+ {
+ log.infof("load segment `{0}`", line);
+ String[] ps = line.Split(new char[] { '|' }, 3);
+ if (ps.Length != 3)
+ {
+ throw new Exception("invalid ip segment line `" + ps[0] + "`");
+ }
+
+ long sip = Util.checkIP(ps[0]);
+ long eip = Util.checkIP(ps[1]);
+ if (sip > eip)
+ {
+ throw new Exception("start ip(" + ps[0] + ") should not be greater than end ip(" + ps[1] + ")");
+ }
+
+ if (ps[2].Length < 1)
+ {
+ throw new Exception("empty region info in segment line `" + ps[2] + "`");
+ }
+
+ // check the continuity of the data segment
+ if (last != null)
+ {
+ if (last.endIP + 1 != sip)
+ {
+ throw new Exception("discontinuous data segment: last.eip+1(" + sip + ") != seg.sip(" + eip + ", " + ps[0] + ")");
+ }
+ }
+
+ Segment seg = new Segment(sip, eip, ps[2]);
+ segments.Add(seg);
+ last = seg;
+ }
+ log.infof("all segments loaded, length: {0}, elapsed: {1} ms", segments.Count, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - tStart);
+ }
+
+ // init the maker
+ public void init()
+ {
+ // init the db header
+ initHeader();
+
+ // load all the segments
+ loadSegments();
+ }
+
+ // set the vector index info of the specified ip
+ private void setVectorIndex(long ip, long ptr)
+ {
+ int il0 = (int)((ip >> 24) & 0xFF);
+ int il1 = (int)((ip >> 16) & 0xFF);
+ int idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
+ long sPtr = Util.getIntLong(vectorIndex, idx);
+ if (sPtr == 0)
+ {
+ Util.write(vectorIndex, idx, ptr, 4);
+ Util.write(vectorIndex, idx + 4, ptr + SegmentIndexSize, 4);
+ }
+ else
+ {
+ Util.write(vectorIndex, idx + 4, ptr + SegmentIndexSize, 4);
+ }
+ }
+
+ // start to make the binary file
+ public void start()
+ {
+ if (segments.Count == 0)
+ {
+ throw new Exception("empty segment list");
+ }
+
+ // 1, write all the region/data to the binary file
+ dstHandle.Seek(HeaderInfoLength + VectorIndexLength, SeekOrigin.Begin);
+
+ log.infof("try to write the data block ... ");
+ foreach (var seg in segments)
+ {
+ log.infof("try to write region `{0}` ... ", seg.region);
+ //DataEntry e = regionPool[seg.region];
+ if (regionPool.TryGetValue(seg.region, out var e))
+ {
+ log.infof(" --[Cached] with ptr={0}", e.ptr);
+ continue;
+ }
+
+ // get the utf-8 bytes of the region info
+ byte[] regionBuff = bytesCharset.GetBytes(seg.region);
+ if (regionBuff.Length < 1)
+ {
+ throw new Exception("empty region info for segment `" + seg + "`");
+ }
+ else if (regionBuff.Length > 0xFFFF)
+ {
+ throw new Exception("too long region info `" + seg.region + "`: should be less than 65535 bytes");
+ }
+
+ // record the current ptr
+ long pos = dstHandle.Position;
+ dstHandle.Write(regionBuff, 0, regionBuff.Length);
+
+ // record the mapping
+ regionPool.Add(seg.region, new DataEntry(regionBuff.Length, pos));
+ log.infof(" --[Added] with ptr={0}", pos);
+ }
+
+ // 2, write the index block cache the super index block
+ log.infof("try to write the segment index block ... ");
+ int counter = 0;
+ long startIndexPtr = -1, endIndexPtr = -1;
+ byte[]
+ indexBuff = new byte[SegmentIndexSize]; // 4 + 4 + 2 + 4
+ foreach (Segment seg in segments)
+ {
+ // we need the region ptr
+ DataEntry e = regionPool[seg.region];
+ if (e == null)
+ {
+ throw new Exception("missing ptr cache for region `" + seg.region + "`");
+ }
+
+ List segList = seg.split();
+ log.infof("try to index segment({0} splits) {1} ... ", segList.Count, seg);
+ foreach (Segment s in segList)
+ {
+ long pos = dstHandle.Position;
+
+ // encode the segment index info
+ Util.write(indexBuff, 0, s.startIP, 4);
+ Util.write(indexBuff, 4, s.endIP, 4);
+ Util.write(indexBuff, 8, e.length, 2);
+ Util.write(indexBuff, 10, e.ptr, 4);
+ dstHandle.Write(indexBuff, 0, indexBuff.Length);
+
+ log.infof("|-segment index: {0}, ptr: {1}, segment: {2}", counter, pos, s);
+ setVectorIndex(s.startIP, pos);
+ counter++;
+
+ // check and record the start index ptr
+ if (startIndexPtr == -1)
+ {
+ startIndexPtr = pos;
+ }
+
+ endIndexPtr = pos;
+ }
+ }
+
+ // 3, synchronize the vector index block
+ log.infof("try to write the vector index block ... ");
+ dstHandle.Seek(HeaderInfoLength, SeekOrigin.Begin);
+ dstHandle.Write(vectorIndex, 0, vectorIndex.Length);
+
+ // 4, synchronize the segment index info
+ log.infof("try to write the segment index ptr ... ");
+ Util.write(indexBuff, 0, startIndexPtr, 4);
+ Util.write(indexBuff, 4, endIndexPtr, 4);
+ dstHandle.Seek(8, SeekOrigin.Begin);
+ dstHandle.Write(indexBuff, 0, 8);
+
+ log.infof("write done, dataBlocks: {0}, indexBlocks: ({1}, {2}), indexPtr: ({3}, {4})", regionPool.Count, segments.Count, counter, startIndexPtr, endIndexPtr);
+ }
+
+ // end the make, do the resource clean up
+ public void end()
+ {
+ this.dstHandle.Close();
+ }
+
+ private class DataEntry
+ {
+ public long ptr;
+ public int length; // in bytes
+
+ public DataEntry(int length, long ptr)
+ {
+ this.length = length;
+ this.ptr = ptr;
+ }
+ }
+ }
+}
diff --git a/maker/csharp/IP2Region.Maker/Segment.cs b/maker/csharp/IP2Region.Maker/Segment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2d049595432b431d3304d6d1c6c224f04c91cc82
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker/Segment.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+
+namespace IP2Region.Maker
+{
+ public class Segment
+ {
+ public long startIP;
+ public long endIP;
+ public String region;
+
+ // parser the Segment from an input string
+ public static Segment parse(String input)
+ {
+ String[] ps = input.Split(new char[] { '|' }, 3);
+ if (ps.Length != 3)
+ {
+ throw new Exception("invalid ip segment `" + input + "`");
+ }
+
+ long sip = Util.checkIP(ps[0]);
+ long eip = Util.checkIP(ps[1]);
+ if (sip > eip)
+ {
+ throw new Exception("start ip `" + ps[0] + "` should not be greater than end ip `" + ps[1] + "`");
+ }
+
+ return new Segment(sip, eip, ps[2]);
+ }
+
+ public Segment(long startIP, long endIP, String region)
+ {
+ this.startIP = startIP;
+ this.endIP = endIP;
+ this.region = region;
+ }
+
+ // split the current segment for vector index
+ public List split()
+ {
+ long sByte1 = (int)((startIP >> 24) & 0xFF);
+ long eByte1 = (int)((endIP >> 24) & 0xFF);
+ long nSip = startIP;
+ List tList = new List();
+ for (long i = sByte1; i <= eByte1; i++)
+ {
+ long sip = (i << 24) | (nSip & 0xFFFFFF);
+ long eip = (i << 24) | 0xFFFFFF;
+ if (eip < endIP)
+ {
+ nSip = (i + 1) << 24;
+ }
+ else
+ {
+ eip = endIP;
+ }
+
+ // append the new segment
+ tList.Add(new Segment(sip, eip, null));
+ }
+
+ // 2, split the segments with the second byte
+ List segList = new List();
+ foreach (Segment seg in tList)
+ {
+ long _base = seg.startIP & 0xFF000000;
+ long tSip = seg.startIP;
+ long sb2 = (seg.startIP >> 16) & 0xFF;
+ long eb2 = (seg.endIP >> 16) & 0xFF;
+ for (long i = sb2; i <= eb2; i++)
+ {
+ long sip = _base | (i << 16) | (tSip & 0xFFFF);
+ long eip = _base | (i << 16) | 0xFFFF;
+ if (eip < seg.endIP)
+ {
+ tSip = 0;
+ }
+ else
+ {
+ eip = seg.endIP;
+ }
+
+ segList.Add(new Segment(sip, eip, region));
+ }
+ }
+
+ return segList;
+ }
+ public override string ToString()
+ {
+ return Util.long2ip(startIP) + "|" + Util.long2ip(endIP) + "|" + region;
+ }
+ }
+}
diff --git a/maker/csharp/IP2Region.Maker/Util.cs b/maker/csharp/IP2Region.Maker/Util.cs
new file mode 100644
index 0000000000000000000000000000000000000000..110bda84d61de2701f49b5f262ac82ebc63a2e04
--- /dev/null
+++ b/maker/csharp/IP2Region.Maker/Util.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace IP2Region.Maker
+{
+ public static class Util
+ {
+ // write specified bytes into a byte array start from offset
+ public static void write(byte[] b, int offset, long v, int bytes)
+ {
+ for (int i = 0; i < bytes; i++)
+ {
+ b[offset++] = (byte)((v >> (8 * i)) & 0xFF);
+ }
+ }
+
+ // write a int to a byte array
+ public static void writeIntLong(byte[] b, int offset, long v)
+ {
+ b[offset++] = (byte)((v) & 0xFF);
+ b[offset++] = (byte)((v >> 8) & 0xFF);
+ b[offset++] = (byte)((v >> 16) & 0xFF);
+ b[offset] = (byte)((v >> 24) & 0xFF);
+ }
+
+ // get an int from a byte array start from the specified offset
+ public static long getIntLong(byte[] b, int offset)
+ {
+ return (
+ ((b[offset++] & 0x000000FFL)) |
+ ((b[offset++] << 8) & 0x0000FF00L) |
+ ((b[offset++] << 16) & 0x00FF0000L) |
+ ((b[offset] << 24) & 0xFF000000L)
+ );
+ }
+
+ public static int getInt2(byte[] b, int offset)
+ {
+ return (
+ (b[offset++] & 0x000000FF) |
+ (b[offset] & 0x0000FF00)
+ );
+ }
+
+ /* long int to ip string */
+ public static String long2ip(long ip)
+ {
+ return string.Join(".", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip) & 0xFF);
+ }
+
+ public static byte[] shiftIndex = { 24, 16, 8, 0 };
+
+ /* check the specified ip address */
+ public static long checkIP(String ip)
+ {
+ String[] ps = ip.Split('.');
+ if (ps.Length != 4) throw new Exception("invalid ip address `" + ip + "`");
+
+ long ipDst = 0;
+ for (int i = 0; i < ps.Length; i++)
+ {
+ int val = Convert.ToInt32(ps[i]);
+ if (val > 255)
+ {
+ throw new Exception("ip part `" + ps[i] + "` should be less then 256");
+ }
+ ipDst |= ((long)val << shiftIndex[i]);
+ }
+
+ return ipDst & 0xFFFFFFFFL;
+ }
+ }
+}