diff --git a/binding/java/pom.xml b/binding/java/pom.xml
index d531daf..b9027b3 100644
--- a/binding/java/pom.xml
+++ b/binding/java/pom.xml
@@ -4,7 +4,7 @@
org.lionsoul
ip2region
- 2.8.1
+ 3.1.0
jar
ip2region
@@ -41,8 +41,8 @@
UTF-8
UTF-8
- 1.6
- 1.6
+ 1.8
+ 1.8
@@ -105,7 +105,7 @@
- org.lionsoul.ip2region.SearchTest
+ org.lionsoul.ip2region.SearchApp
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearchTest.java b/binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java
similarity index 82%
rename from binding/java/src/main/java/org/lionsoul/ip2region/SearchTest.java
rename to binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java
index bc6b53f..68a9f93 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/SearchTest.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java
@@ -6,15 +6,18 @@
package org.lionsoul.ip2region;
+import org.lionsoul.ip2region.xdb.InetAddressException;
+import org.lionsoul.ip2region.xdb.XdbException;
import org.lionsoul.ip2region.xdb.LongByteArray;
import org.lionsoul.ip2region.xdb.Searcher;
+import org.lionsoul.ip2region.xdb.Util;
+import org.lionsoul.ip2region.xdb.Version;
import java.io.*;
import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
-public class SearchTest {
+public class SearchApp {
public static void printHelp(String[] args) {
System.out.print("ip2region xdb searcher\n");
@@ -24,21 +27,33 @@ public class SearchTest {
System.out.print(" bench search bench test\n");
}
- public static Searcher createSearcher(String dbPath, String cachePolicy) throws IOException {
+ public static Searcher createSearcher(String dbPath, String cachePolicy) throws IOException, XdbException {
+ final RandomAccessFile handle = new RandomAccessFile(dbPath, "r");
+
+ // verify the xdb file
+ // @Note: do NOT call it every time you create a searcher since this will slow
+ // down the search response.
+ // @see the util.Verify function for details.
+ Searcher.verify(handle);
+
+ // get the ip version from header
+ final Version version = Version.fromHeader(Searcher.loadHeader(handle));
+
+ // create the final searcher
if ("file".equals(cachePolicy)) {
- return Searcher.newWithFileOnly(dbPath);
+ return Searcher.newWithFileOnly(version, dbPath);
} else if ("vectorIndex".equals(cachePolicy)) {
byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
- return Searcher.newWithVectorIndex(dbPath, vIndex);
+ return Searcher.newWithVectorIndex(version, dbPath, vIndex);
} else if ("content".equals(cachePolicy)) {
LongByteArray cBuff = Searcher.loadContentFromFile(dbPath);
- return Searcher.newWithBuffer(cBuff);
+ return Searcher.newWithBuffer(version, cBuff);
} else {
throw new IOException("invalid cache policy `" + cachePolicy + "`, options: file/vectorIndex/content");
}
}
- public static void searchTest(String[] args) throws IOException {
+ public static void searchTest(String[] args) throws IOException, XdbException {
String dbPath = "", cachePolicy = "vectorIndex";
for (final String r : args) {
if (r.length() < 5) {
@@ -105,7 +120,7 @@ public class SearchTest {
System.out.println("searcher test program exited, thanks for trying");
}
- public static void benchTest(String[] args) throws IOException {
+ public static void benchTest(String[] args) throws IOException, XdbException, InetAddressException {
String dbPath = "", srcPath = "", cachePolicy = "vectorIndex";
for (final String r : args) {
if (r.length() < 5) {
@@ -155,40 +170,44 @@ public class SearchTest {
String l = line.trim();
String[] ps = l.split("\\|", 3);
if (ps.length != 3) {
+ reader.close();
System.out.printf("invalid ip segment `%s`\n", l);
return;
}
- long sip;
+ byte[] sip;
try {
- sip = Searcher.checkIP(ps[0]);
+ sip = Util.parseIP(ps[0]);
} catch (Exception e) {
+ reader.close();
System.out.printf("check start ip `%s`: %s\n", ps[0], e);
return;
}
- long eip;
+ byte[] eip;
try {
- eip = Searcher.checkIP(ps[1]);
+ eip = Util.parseIP(ps[1]);
} catch (Exception e) {
+ reader.close();
System.out.printf("check end ip `%s`: %s\n", ps[1], e);
return;
}
- if (sip > eip) {
+ if (Util.ipCompare(sip, eip) > 0) {
+ reader.close();
System.out.printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1]);
return;
}
- long mip = (sip + eip) >> 1;
- for (final long ip : new long[]{sip, (sip + mip) >> 1, mip, (mip + eip) >> 1, eip}) {
+ for (final byte[] ip : new byte[][]{sip, eip}) {
long sTime = System.nanoTime();
String region = searcher.search(ip);
costs += System.nanoTime() - sTime;
// check the region info
if (!ps[2].equals(region)) {
- System.out.printf("failed search(%s) with (%s != %s)\n", Searcher.long2ip(ip), region, ps[2]);
+ System.out.printf("failed search(%s) with (%s != %s)\n", Util.ipToString(ip), region, ps[2]);
+ reader.close();
return;
}
@@ -213,14 +232,14 @@ public class SearchTest {
if ("search".equals(args[0])) {
try {
searchTest(args);
- } catch (IOException e) {
+ } catch (Exception e) {
System.out.printf("failed running search test: %s\n", e);
}
} else if ("bench".equals(args[0])) {
try {
benchTest(args);
- } catch (IOException e) {
- System.out.printf("failed running bench test: %s\n", e);
+ } catch (Exception e) {
+ System.out.printf("fwailed running bench test: %s\n", e);
}
} else {
printHelp(args);
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/UtilTest.java b/binding/java/src/main/java/org/lionsoul/ip2region/UtilTest.java
deleted file mode 100644
index db5cbbb..0000000
--- a/binding/java/src/main/java/org/lionsoul/ip2region/UtilTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// 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
-// @Date 2022/06/23
-
-package org.lionsoul.ip2region;
-
-import org.lionsoul.ip2region.xdb.LongByteArray;
-import org.lionsoul.ip2region.xdb.Searcher;
-
-import java.util.Arrays;
-
-public class UtilTest {
-
- public static void testIP2Long() {
- String ip = "1.2.3.4";
- long ipAddr = 0;
- try {
- ipAddr = Searcher.checkIP(ip);
- } catch (Exception e) {
- System.out.printf("failed to check ip: %s\n", e);
- return;
- }
-
- if (ipAddr != 16909060) {
- System.out.print("failed ip2long\n");
- return;
- }
-
- String ip2 = Searcher.long2ip(ipAddr);
- if (!ip.equals(ip2)) {
- System.out.print("failed long2ip\n");
- return;
- }
-
- System.out.printf("passed: ip=%s, ipAddr=%d, ip2=%s\n", ip, ipAddr, ip2);
- }
-
- public static void testLongByteArray() {
- final LongByteArray byteArray = new LongByteArray();
- byteArray.append(new byte[]{0,0,0,0,0});
- byteArray.append(new byte[]{1,1,1,1,1});
- int counter = 2;
- for (int i = 0; i < 100; i++) {
- final byte[] buff = new byte[10];
- Arrays.fill(buff, (byte) counter);
- byteArray.append(buff);
- counter++;
- }
-
- System.out.printf("1, byteArray.length: %d\n", byteArray.length());
- System.out.println("2, length copy test...");
- int[] length = new int[]{5, 10, 15, 20, 21, 22, 23, 25, 28, 29, 30, 40, 42, 44, 50, 60};
- for (int j : length) {
- final byte[] destBuff = new byte[j];
- byteArray.copy(0, destBuff, 0, destBuff.length);
- System.out.printf("copy(0,%d): \n", destBuff.length);
- for (byte b : destBuff) {
- System.out.print(b + " ");
- }
- System.out.println();
- }
-
- System.out.println("3, offset copy test...");
- int[] offset = new int[]{0, 5, 10, 15, 20, 21, 22, 23, 25, 28, 29, 30, 40, 42, 44, 50, 60};
- for (int j : offset) {
- final byte[] destBuff = new byte[11];
- byteArray.copy(j, destBuff, 0, destBuff.length);
- System.out.printf("copy(%d,%d): \n", j, destBuff.length);
- for (byte b : destBuff) {
- System.out.print(b + " ");
- }
- System.out.println();
- }
- }
-
- public static void main(String[] args) {
- System.out.print("testing IP2Long ... \n");
- testIP2Long();
- System.out.print("testing LongByteArray ... \n");
- testLongByteArray();
- }
-
-}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java
index a55cedc..a6305f1 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java
@@ -9,18 +9,25 @@ package org.lionsoul.ip2region.xdb;
public class Header {
public final int version;
public final int indexPolicy;
- public final int createdAt;
- public final int startIndexPtr;
- public final int endIndexPtr;
+ public final long createdAt;
+ public final long startIndexPtr;
+ public final long endIndexPtr;
+
+ // since xdb 3.0 with IPv6 supporting
+ public final int ipVersion;
+ public final int runtimePtrBytes;
+
public final byte[] buffer;
public Header(byte[] buff) {
assert buff.length >= 16;
- version = Searcher.getInt2(buff, 0);
- indexPolicy = Searcher.getInt2(buff, 2);
- createdAt = Searcher.getInt(buff, 4);
- startIndexPtr = Searcher.getInt(buff, 8);
- endIndexPtr = Searcher.getInt(buff, 12);
+ version = LittleEndian.getInt2(buff, 0);
+ indexPolicy = LittleEndian.getInt2(buff, 2);
+ createdAt = LittleEndian.getUint32(buff, 4);
+ startIndexPtr = LittleEndian.getUint32(buff, 8);
+ endIndexPtr = LittleEndian.getUint32(buff, 12);
+ ipVersion = LittleEndian.getInt2(buff, 16);
+ runtimePtrBytes = LittleEndian.getInt2(buff, 18);
buffer = buff;
}
@@ -30,7 +37,9 @@ public class Header {
"IndexPolicy: " + indexPolicy + ',' +
"CreatedAt: " + createdAt + ',' +
"StartIndexPtr: " + startIndexPtr + ',' +
- "EndIndexPtr: " + endIndexPtr +
+ "EndIndexPtr: " + endIndexPtr + ',' +
+ "IPVersion: " + ipVersion + ',' +
+ "RuntimePtrBytes: " + runtimePtrBytes +
'}';
}
}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv4.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv4.java
new file mode 100644
index 0000000..bd07013
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv4.java
@@ -0,0 +1,47 @@
+// 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.
+
+package org.lionsoul.ip2region.xdb;
+
+// IPv4 version implementation
+// @Author Lion
+// @Date 2025/09/10
+
+public class IPv4 extends Version {
+ public IPv4() {
+ // segmentIndex: 4 + 4 + 2 + 4
+ super(4, "IPv4", 4, 14);
+ }
+
+ @Override
+ public int putBytes(byte[] buff, int offset, byte[] ip) {
+ // use the Little endian byte order to compatible with the old searcher implementation
+ buff[offset++] = ip[3];
+ buff[offset++] = ip[2];
+ buff[offset++] = ip[1];
+ buff[offset ] = ip[0];
+ return ip.length;
+ }
+
+ @Override
+ public int ipSubCompare(byte[] ip1, byte[] buff, int offset) {
+ // ip1: Big endian byte order parsed from input
+ // ip2: Little endian byte order read from xdb index.
+ // @Note: to compatible with the old Litten endian index encode implementation.
+ int j = offset + ip1.length - 1;
+ for (int i = 0; i < ip1.length; i++, j--) {
+ final int i1 = (int) (ip1[i] & 0xFF);
+ final int i2 = (int) (buff[j] & 0xFF);
+ if (i1 < i2) {
+ return -1;
+ }
+
+ if (i1 > i2) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv6.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv6.java
new file mode 100644
index 0000000..af7349d
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/IPv6.java
@@ -0,0 +1,29 @@
+// 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.
+
+package org.lionsoul.ip2region.xdb;
+
+// IPv4 version implementation
+// @Author Lion
+// @Date 2025/09/10
+
+public class IPv6 extends Version {
+
+ public IPv6() {
+ // segmentIndex: 16 + 16 + 2 + 4
+ super(6, "IPv6", 16, 38);
+ }
+
+ @Override
+ public int putBytes(byte[] buff, int offset, byte[] ip) {
+ System.arraycopy(ip, 0, buff, offset, ip.length);
+ return ip.length;
+ }
+
+ @Override
+ public int ipSubCompare(byte[] ip1, byte[] buff, int offset) {
+ return Util.ipSubCompare(ip1, buff, offset);
+ }
+
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/InetAddressException.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/InetAddressException.java
new file mode 100644
index 0000000..369c2b8
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/InetAddressException.java
@@ -0,0 +1,13 @@
+// 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.
+
+package org.lionsoul.ip2region.xdb;
+
+public class InetAddressException extends Exception {
+
+ public InetAddressException(String str) {
+ super(str);
+ }
+
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LittleEndian.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LittleEndian.java
new file mode 100644
index 0000000..2a8b7b0
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LittleEndian.java
@@ -0,0 +1,58 @@
+// 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.
+
+package org.lionsoul.ip2region.xdb;
+
+// Little Endian basic data type decode and encode.
+// @Author Lion
+// @Date 2025/09/10
+
+public class LittleEndian {
+
+ public final static int[] shiftIndex = {0, 8, 16, 24, 32, 40, 48, 56};
+
+ // put specified bytes to the buffer started from the offset
+ public static void put(final byte[] buff, int offset, long value, int bytes) {
+ if (bytes > 8) {
+ throw new IndexOutOfBoundsException("bytes should be <= 8");
+ }
+
+ for (int i = 0; i < bytes; i++) {
+ buff[offset++] = (byte)((value >>> shiftIndex[i]) & 0xFF);
+ }
+ }
+
+ // put an uint32 (4 bytes long) to the buffer from the offset
+ public static void putUint32(final byte[] buff, int offset, long value) {
+ buff[offset++] = (byte) (value & 0xFF);
+ buff[offset++] = (byte) ((value >> 8) & 0xFF);
+ buff[offset++] = (byte) ((value >> 16) & 0xFF);
+ buff[offset ] = (byte) ((value >> 24) & 0xFF);
+ }
+
+ // put a 2-bytes int to the buffer from the specified offset
+ public static void putInt2(final byte[] buff, int offset, int value) {
+ buff[offset++] = (byte) (value & 0xFF);
+ buff[offset ] = (byte) ((value >> 8) & 0xFF);
+ }
+
+ // get an uint32 from a byte array from the specified offset
+ public static long getUint32(final byte[] buff, int offset) {
+ return (
+ ((buff[offset++] & 0x000000FFL)) |
+ ((buff[offset++] << 8) & 0x0000FF00L) |
+ ((buff[offset++] << 16) & 0x00FF0000L) |
+ ((buff[offset ] << 24) & 0xFF000000L)
+ );
+ }
+
+ // get an 2 bytes int from a byte array from the specified offset
+ public static int getInt2(final byte[] buff, int offset) {
+ return (
+ ((buff[offset++]) & 0x000000FF) |
+ ((buff[offset ] << 8) & 0x0000FF00)
+ );
+ }
+
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Log.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Log.java
new file mode 100644
index 0000000..a5297fe
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Log.java
@@ -0,0 +1,119 @@
+// 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
+// @Date 2022/07/14
+
+package org.lionsoul.ip2region.xdb;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+// simple log implementation
+public class Log {
+
+ /* Log level constants define */
+ public static final int DEBUG = 0;
+ public static final int INFO = 1;
+ public static final int WARN = 2;
+ public static final int ERROR = 3;
+
+ // level name
+ public static final String[] level_string = new String[] {
+ "DEBUG",
+ "INFO",
+ "WARN",
+ "ERROR"
+ };
+
+ public final Class> baseClass;
+ private int level = INFO;
+
+ public Log(Class> baseClass) {
+ this.baseClass = baseClass;
+ }
+
+ public static Log getLogger(Class> baseClass) {
+ return new Log(baseClass);
+ }
+
+ public String format(int level, String format, Object... args) {
+ // append the datetime
+ final StringBuilder sb = new StringBuilder();
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sb.append(String.format("%s %-5s ", sdf.format(new Date()), level_string[level]));
+
+ // append the class name
+ sb.append(baseClass.getName()).append(' ');
+ sb.append(String.format(format, args));
+ return sb.toString();
+ }
+
+ public void printf(int level, String format, Object... args) {
+ if (level < DEBUG || level > ERROR) {
+ throw new IndexOutOfBoundsException("invalid level index " + level);
+ }
+
+ // level filter
+ if (level < this.level) {
+ return;
+ }
+
+ System.out.println(format(level, format, args));
+ System.out.flush();
+ }
+
+ public String getDebugf(String format, Object... args) {
+ return format(DEBUG, format, args);
+ }
+
+ public void debugf(String format, Object... args) {
+ printf(DEBUG, format, args);
+ }
+
+ public String getInfof(String format, Object... args) {
+ return format(INFO, format, args);
+ }
+
+ public void infof(String format, Object... args) {
+ printf(INFO, format, args);
+ }
+
+ public String getWarnf(String format, Object... args) {
+ return format(WARN, format, args);
+ }
+
+ public void warnf(String format, Object... args) {
+ printf(WARN, format, args);
+ }
+
+ public String getErrorf(String format, Object... args) {
+ return format(ERROR, format, args);
+ }
+
+ public void errorf(String format, Object... args) {
+ printf(ERROR, format, args);
+ }
+
+ public Log setLevel(int level) {
+ this.level = level;
+ return this;
+ }
+
+ public Log setLevel(String level) {
+ String v = level.toLowerCase();
+ if ("debug".equals(v)) {
+ this.level = DEBUG;
+ } else if ("info".equals(v)) {
+ this.level = INFO;
+ } else if ("warn".equals(v)) {
+ this.level = WARN;
+ } else if ("error".equals(v)) {
+ this.level = ERROR;
+ }
+
+ return this;
+ }
+
+}
\ No newline at end of file
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LongByteArray.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LongByteArray.java
index aa85b6b..3fb2a07 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LongByteArray.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/LongByteArray.java
@@ -104,27 +104,17 @@ public class LongByteArray {
return copy(offset, buffer, 0, length);
}
- // get a 4-bytes long integer from the specified index
- public long getIntLong(long offset) {
+ // get a 4-bytes uint32 integer from the specified index
+ public long getUint32(long offset) {
final byte[] b = new byte[4];
copy(offset, b, 0, 4);
- return (
- ((b[0] & 0x000000FFL)) |
- ((b[1] << 8) & 0x0000FF00L) |
- ((b[2] << 16) & 0x00FF0000L) |
- ((b[3] << 24) & 0xFF000000L)
- );
+ return LittleEndian.getUint32(b, 0);
}
- public int getInt(long offset) {
+ public int getInt2(long offset) {
final byte[] b = new byte[4];
copy(offset, b, 0, 4);
- return (
- ((b[0] & 0x000000FF)) |
- ((b[1] << 8) & 0x0000FF00) |
- ((b[2] << 16) & 0x00FF0000) |
- ((b[3] << 24) & 0xFF000000)
- );
+ return LittleEndian.getInt2(b, 0);
}
// position entry class
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java
index 52fe054..974da13 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java
@@ -8,21 +8,26 @@ package org.lionsoul.ip2region.xdb;
// @Author Lion
// @Date 2022/06/23
-
import java.io.IOException;
import java.io.RandomAccessFile;
public class Searcher {
+ // xdb structure version no
+ public static final int STRUCTURE_20 = 2;
+ public static final int STRUCTURE_30 = 3;
+
// constant defined copied from the xdb maker
public static final int HeaderInfoLength = 256;
public static final int VectorIndexRows = 256;
public static final int VectorIndexCols = 256;
public static final int VectorIndexSize = 8;
- public static final int SegmentIndexSize = 14;
// Linux max write / read bytes
public static final int MAX_WRITE_BYTES = 0x7ffff000;
+ // ip version
+ private final Version version;
+
// random access file handle for file-based search
private final RandomAccessFile handle;
@@ -40,21 +45,22 @@ public class Searcher {
// --- static method to create searchers
- public static Searcher newWithFileOnly(String dbPath) throws IOException {
- return new Searcher(dbPath, null, null);
+ public static Searcher newWithFileOnly(Version version, String dbPath) throws IOException {
+ return new Searcher(version, dbPath, null, null);
}
- public static Searcher newWithVectorIndex(String dbPath, byte[] vectorIndex) throws IOException {
- return new Searcher(dbPath, vectorIndex, null);
+ public static Searcher newWithVectorIndex(Version version, String dbPath, byte[] vectorIndex) throws IOException {
+ return new Searcher(version, dbPath, vectorIndex, null);
}
- public static Searcher newWithBuffer(LongByteArray cBuff) throws IOException {
- return new Searcher(null, null, cBuff);
+ public static Searcher newWithBuffer(Version version, LongByteArray cBuff) throws IOException {
+ return new Searcher(version, null, null, cBuff);
}
// --- End of creator
- public Searcher(String dbFile, byte[] vectorIndex, LongByteArray cBuff) throws IOException {
+ public Searcher(Version version, String dbFile, byte[] vectorIndex, LongByteArray cBuff) throws IOException {
+ this.version = version;
if (cBuff != null) {
this.handle = null;
this.vectorIndex = null;
@@ -72,62 +78,68 @@ public class Searcher {
}
}
+ public Version getIPVersion() {
+ return version;
+ }
+
public int getIOCount() {
return ioCount;
}
public String search(String ipStr) throws Exception {
- long ip = checkIP(ipStr);
- return search(ip);
+ return search(Util.parseIP(ipStr));
}
- public String search(long ip) throws IOException {
+ public String search(byte[] ip) throws IOException, InetAddressException {
+ // ip version check
+ if (ip.length != version.bytes) {
+ throw new InetAddressException("invalid ip address ("+version.name+" expected)");
+ }
+
// reset the global counter
this.ioCount = 0;
// locate the segment index block based on the vector index
long sPtr = 0, ePtr = 0;
- int il0 = (int) ((ip >> 24) & 0xFF);
- int il1 = (int) ((ip >> 16) & 0xFF);
+ int il0 = (int) (ip[0] & 0xFF);
+ int il1 = (int) (ip[1] & 0xFF);
int idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
// System.out.printf("il0: %d, il1: %d, idx: %d\n", il0, il1, idx);
if (vectorIndex != null) {
- sPtr = getIntLong(vectorIndex, idx);
- ePtr = getIntLong(vectorIndex, idx + 4);
+ sPtr = LittleEndian.getUint32(vectorIndex, idx);
+ ePtr = LittleEndian.getUint32(vectorIndex, idx + 4);
} else if (contentBuff != null) {
- sPtr = contentBuff.getIntLong(HeaderInfoLength + idx);
- ePtr = contentBuff.getIntLong(HeaderInfoLength + idx + 4);
+ sPtr = contentBuff.getUint32(HeaderInfoLength + idx);
+ ePtr = contentBuff.getUint32(HeaderInfoLength + idx + 4);
} else {
final byte[] buff = new byte[VectorIndexSize];
read(HeaderInfoLength + idx, buff);
- sPtr = getIntLong(buff, 0);
- ePtr = getIntLong(buff, 4);
+ sPtr = LittleEndian.getUint32(buff, 0);
+ ePtr = LittleEndian.getUint32(buff, 4);
}
// System.out.printf("sPtr: %d, ePtr: %d\n", sPtr, ePtr);
// binary search the segment index block to get the region info
- final byte[] buff = new byte[SegmentIndexSize];
+ final int bytes = ip.length, dBytes = ip.length << 1;
+ final int segIndexSize = version.segmentIndexSize;
+ final byte[] buff = new byte[segIndexSize];
int dataLen = -1;
- long dataPtr = -1, l = 0, h = (ePtr - sPtr) / SegmentIndexSize;
+ long dataPtr = -1, l = 0, h = (ePtr - sPtr) / segIndexSize;
while (l <= h) {
long m = (l + h) >> 1;
- long p = sPtr + m * SegmentIndexSize;
+ long p = sPtr + m * segIndexSize;
// read the segment index
read(p, buff);
- long sip = getIntLong(buff, 0);
- if (ip < sip) {
+ if (version.ipSubCompare(ip, buff, 0) < 0) {
h = m - 1;
+ } else if (version.ipSubCompare(ip, buff, bytes) > 0) {
+ l = m + 1;
} else {
- long eip = getIntLong(buff, 4);
- if (ip > eip) {
- l = m + 1;
- } else {
- dataLen = getInt2(buff, 8);
- dataPtr = getIntLong(buff, 10);
- break;
- }
+ dataLen = LittleEndian.getInt2(buff, dBytes);
+ dataPtr = LittleEndian.getUint32(buff, dBytes + 2);
+ break;
}
}
@@ -161,7 +173,7 @@ public class Searcher {
}
}
- // --- static cache util function
+ // --- static util function
public static Header loadHeader(RandomAccessFile handle) throws IOException {
handle.seek(0);
@@ -222,63 +234,39 @@ public class Searcher {
return content;
}
- // --- End cache load util function
+ // --- verify util function
- // --- static util method
+ // 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.
+ public static void verify(RandomAccessFile handle) throws IOException, XdbException {
+ final Header header = loadHeader(handle);
- /* 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 getInt(byte[] b, int offset) {
- return (
- ((b[offset++] & 0x000000FF)) |
- ((b[offset++] << 8) & 0x0000FF00) |
- ((b[offset++] << 16) & 0x00FF0000) |
- ((b[offset ] << 24) & 0xFF000000)
- );
- }
-
- public static int getInt2(byte[] b, int offset) {
- return (
- ((b[offset++] & 0x000000FF)) |
- ((b[offset ] << 8) & 0x0000FF00)
- );
- }
-
- /* long int to ip string */
- public static String long2ip( long ip )
- {
- return String.valueOf((ip >> 24) & 0xFF) + '.' +
- ((ip >> 16) & 0xFF) + '.' + ((ip >> 8) & 0xFF) + '.' + ((ip) & 0xFF);
- }
-
- public static final byte[] shiftIndex = {24, 16, 8, 0};
-
- /* check the specified ip address */
- public static long checkIP(String ip) throws Exception {
- String[] ps = ip.split("\\.");
- if (ps.length != 4) {
- throw new Exception("invalid ip address `" + ip + "`");
+ // get the runtime ptr bytes
+ int runtimePtrBytes = 0;
+ if (header.version == STRUCTURE_20) {
+ runtimePtrBytes = 4;
+ } else if (header.version == STRUCTURE_30) {
+ runtimePtrBytes = header.runtimePtrBytes;
+ } else {
+ throw new XdbException("invalid structure version `" + header.version + "`");
}
- long ipDst = 0;
- for (int i = 0; i < ps.length; i++) {
- int val = Integer.parseInt(ps[i]);
- if (val > 255) {
- throw new Exception("ip part `"+ps[i]+"` should be less then 256");
- }
-
- ipDst |= ((long) val << shiftIndex[i]);
+ // 1, confirm the xdb file size
+ // to ensure that the maximum file pointer does not overflow
+ final long maxFilePtr = (1L << (runtimePtrBytes * 8)) - 1;
+ if (handle.length() > maxFilePtr) {
+ throw new XdbException("xdb file exceeds the maximum supported bytes: "+maxFilePtr+"");
}
+ }
- return ipDst & 0xFFFFFFFFL;
+ public static void verifyFromFile(String dbFile) throws IOException, XdbException {
+ final RandomAccessFile handle = new RandomAccessFile(dbFile, "r");
+ verify(handle);
+ handle.close();
}
}
\ No newline at end of file
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java
new file mode 100644
index 0000000..a394327
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java
@@ -0,0 +1,78 @@
+// 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
+// @Date 2022/07/14
+
+package org.lionsoul.ip2region.xdb;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class Util
+{
+
+ // parse the specified IP address and return its bytes.
+ // returns: byte[4] for IPv4 and byte[16] for IPv6 and the bytes should be in Big endian order.
+ public static byte[] parseIP(String ip) throws InetAddressException {
+ try {
+ return InetAddress.getByName(ip).getAddress();
+ } catch (UnknownHostException e) {
+ throw new InetAddressException("invalid ip address `"+ip+"`");
+ }
+ }
+
+ // convert the byte[] ip to string ip address
+ public static String ipToString(final byte[] ip) {
+ if (ip.length != 4 && ip.length != 16) {
+ return String.format("invalid-ip-address-length: %d", ip.length);
+ }
+
+ try {
+ return InetAddress.getByAddress(ip).getHostAddress();
+ } catch (UnknownHostException e) {
+ return String.format("invalid-ip-address `%s`", ipArrayString(ip));
+ }
+ }
+
+ // implode the byte[] ip with its byte value.
+ public static String ipArrayString(byte[] ip) {
+ final StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ for (int i = 0; i < ip.length; i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append((ip[i] & 0xFF));
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ // compare two byte ip
+ // Returns: -1 if ip1 < ip2, 0 if ip1 == ip2, 1 if ip1 > ip2
+ public static int ipCompare(byte[] ip1, byte[] ip2) {
+ return ipSubCompare(ip1, ip2, 0);
+ }
+
+ // compare the ip with the ip in the buffer start from offset
+ // Returns: -1 if ip < buff[offset], 0 if ip == buff[offset], 1 if ip > buff[offset]
+ public static int ipSubCompare(byte[] ip, byte[] buff, int offset) {
+ for (int i = 0; i < ip.length; i++) {
+ // covert the byte to int to sure the uint8 attribute
+ final int i1 = (int)(ip[i] & 0xFF);
+ final int i2 = (int)(buff[offset+i] & 0xFF);
+ if (i1 < i2) {
+ return -1;
+ }
+
+ if (i1 > i2) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Version.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Version.java
new file mode 100644
index 0000000..e062a0d
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Version.java
@@ -0,0 +1,83 @@
+// Copyright 2025 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.
+
+package org.lionsoul.ip2region.xdb;
+
+// IP version abstract manager (IPv4 & IPv6)
+// @Author Lion
+// @Date 2025/09/10
+
+public abstract class Version {
+ public static final int IPv4VersionNo = 4;
+ public static final int IPv6VersionNo = 6;
+
+ public static final IPv4 IPv4 = new IPv4();
+ public static final IPv6 IPv6 = new IPv6();
+
+ // version id and name
+ public final int id;
+ public final String name;
+
+ // the numbers of bytes for one IP
+ public final int bytes;
+
+ // segment index size (bytes)
+ public final int segmentIndexSize;
+
+ public Version(int id, String name, int bytes, int segmentIndexSize) {
+ this.id = id;
+ this.name = name;
+ this.bytes = bytes;
+ this.segmentIndexSize = segmentIndexSize;
+ }
+
+ // encode the specified IP bytes to the specified buffer
+ public abstract int putBytes(byte[] buff, int offset, byte[] ip);
+
+ // compare the two IPs with the current version.
+ // Returns: -1 if ip1 < ip2, 0 if ip1 == ip2, 1 if ip1 > ip2
+ public int ipCompare(byte[] ip1, byte[] ip2) {
+ return ipSubCompare(ip1, ip2, 0);
+ }
+
+ // @see ipCompare
+ public abstract int ipSubCompare(byte[] ip1, byte[] buff, int offset);
+
+ // parse the version from an name
+ public static final Version fromName(String name) throws Exception {
+ final String n = name.toUpperCase();
+ if (n.equals("V4") || n.equals("IPV4")) {
+ return IPv4;
+ } else if (n.equals("V6") || n.equals("IPV6")) {
+ return IPv6;
+ } else {
+ throw new Exception("invalid version name `"+name+"`");
+ }
+ }
+
+ // parse the version from header
+ public static final Version fromHeader(Header header) throws XdbException {
+ // Old 2.0 structure with IPv4 supports ONLY.
+ if (header.version == Searcher.STRUCTURE_20) {
+ return IPv4;
+ }
+
+ // structure 3.0 after IPv6 supporting
+ if (header.version != Searcher.STRUCTURE_30) {
+ throw new XdbException("invalid xdb structure version `"+header.version+"`");
+ }
+
+ if (header.ipVersion == IPv4VersionNo) {
+ return IPv4;
+ } else if (header.ipVersion == IPv6VersionNo) {
+ return IPv6;
+ } else {
+ throw new XdbException("invalid ip version number `" + header.ipVersion + "`");
+ }
+ }
+
+ @Override public String toString() {
+ return String.format("{Id:%d, Name:%s, Bytes:%d, IndexSize: %d}", id, name, bytes, segmentIndexSize);
+ }
+}
\ No newline at end of file
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/XdbException.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/XdbException.java
new file mode 100644
index 0000000..afdded2
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/XdbException.java
@@ -0,0 +1,13 @@
+// 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.
+
+package org.lionsoul.ip2region.xdb;
+
+public class XdbException extends Exception {
+
+ public XdbException(String str) {
+ super(str);
+ }
+
+}
diff --git a/binding/java/src/main/java/xdb/Version.java b/binding/java/src/main/java/xdb/Version.java
new file mode 100644
index 0000000..986f689
--- /dev/null
+++ b/binding/java/src/main/java/xdb/Version.java
@@ -0,0 +1,5 @@
+package xdb;
+
+public class Version {
+
+}
diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/xdb/LittleEndianTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/xdb/LittleEndianTest.java
new file mode 100644
index 0000000..9b216a3
--- /dev/null
+++ b/binding/java/src/test/java/org/lionsoul/ip2region/xdb/LittleEndianTest.java
@@ -0,0 +1,36 @@
+package org.lionsoul.ip2region.xdb;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class LittleEndianTest {
+
+ private static final Log log = Log.getLogger(LittleEndianTest.class).setLevel(Log.DEBUG);
+
+ @Test
+ public void testAll() {
+ final byte[] buff = new byte[14];
+
+ // encode
+ // do the put
+ LittleEndian.put(buff, 0, 1L, 4);
+ LittleEndian.put(buff, 4, 2L, 4);
+
+ // putUint32
+ LittleEndian.putInt2(buff, 8, 24);
+ LittleEndian.putUint32(buff, 10, 1024L);
+
+ // decode
+ assertEquals(LittleEndian.getUint32(buff, 0), 1);
+ assertEquals(LittleEndian.getUint32(buff, 4), 2);
+ assertEquals(LittleEndian.getInt2(buff, 8), 24);
+ assertEquals(LittleEndian.getUint32(buff, 10), 1024);
+
+ log.debugf("uint32(buff, 0): %d", LittleEndian.getUint32(buff, 0));
+ log.debugf("uint32(buff, 4): %d", LittleEndian.getUint32(buff, 4));
+ log.debugf("int2(buff, 8): %d", LittleEndian.getInt2(buff, 8));
+ log.debugf("uint32(buff, 10): %d", LittleEndian.getUint32(buff, 10));
+ }
+
+}
diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/xdb/UtilTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/xdb/UtilTest.java
new file mode 100644
index 0000000..1b31b97
--- /dev/null
+++ b/binding/java/src/test/java/org/lionsoul/ip2region/xdb/UtilTest.java
@@ -0,0 +1,45 @@
+package org.lionsoul.ip2region.xdb;
+
+import org.junit.Test;
+
+public class UtilTest {
+
+ private static final Log log = Log.getLogger(UtilTest.class).setLevel(Log.DEBUG);
+
+ @Test
+ public void testCheckIP() throws InetAddressException {
+ final String[] ips = new String[]{
+ "192.168.1.102",
+ "219.133.111.87",
+ "::",
+ "3000::",
+ "::1001:ffff",
+ "2001:2:0:ffff:ffff:ffff:ffff:ffff",
+ "::ffff:114.114.114.114"
+ };
+
+ for (String ip : ips) {
+ final byte[] ipBytes = Util.parseIP(ip);
+ log.debugf("%s(v=%s) => %s", ip, Util.ipArrayString(ipBytes), Util.ipToString(ipBytes));
+ }
+ }
+
+ @Test
+ public void testIpCompare() throws InetAddressException {
+ final String[][] ipPairs = new String[][]{
+ {"1.0.0.0", "1.0.0.1"},
+ {"192.168.1.101", "192.168.1.90"},
+ {"219.133.111.87", "114.114.114.114"},
+ {"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"}
+ };
+
+ for (String[] ips : ipPairs) {
+ final byte[] ip1 = Util.parseIP(ips[0]);
+ final byte[] ip2 = Util.parseIP(ips[1]);
+ log.debugf("compare(%s, %s): %d", ips[0], ips[1], Util.ipCompare(ip1, ip2));
+ }
+ }
+
+}
diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/xdb/VersionTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/xdb/VersionTest.java
new file mode 100644
index 0000000..8c38773
--- /dev/null
+++ b/binding/java/src/test/java/org/lionsoul/ip2region/xdb/VersionTest.java
@@ -0,0 +1,22 @@
+package org.lionsoul.ip2region.xdb;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class VersionTest {
+
+ private static final Log log = Log.getLogger(VersionTest.class).setLevel(Log.DEBUG);
+
+ @Test
+ public void testFromName() throws Exception {
+ final String[] vers = new String[]{"IPv4", "IPv6"};
+ final Version v4 = Version.fromName(vers[0]);
+ assertEquals(v4.name, vers[0]);
+ final Version v6 = Version.fromName(vers[1]);
+ assertEquals(v6.name, vers[1]);
+
+ log.debugf("v4: %s", v4);
+ log.debugf("v6: %s", v6);
+ }
+}