IPv6 supporting for Java searcher
This commit is contained in:
parent
c6c0ee954f
commit
6db9654c7d
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<groupId>org.lionsoul</groupId>
|
<groupId>org.lionsoul</groupId>
|
||||||
<artifactId>ip2region</artifactId>
|
<artifactId>ip2region</artifactId>
|
||||||
<version>2.8.1</version>
|
<version>3.1.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>ip2region</name>
|
<name>ip2region</name>
|
||||||
|
|
@ -41,8 +41,8 @@
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<maven.compiler.source>1.6</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.6</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>org.lionsoul.ip2region.SearchTest</mainClass>
|
<mainClass>org.lionsoul.ip2region.SearchApp</mainClass>
|
||||||
</transformer>
|
</transformer>
|
||||||
</transformers>
|
</transformers>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,18 @@
|
||||||
|
|
||||||
package org.lionsoul.ip2region;
|
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.LongByteArray;
|
||||||
import org.lionsoul.ip2region.xdb.Searcher;
|
import org.lionsoul.ip2region.xdb.Searcher;
|
||||||
|
import org.lionsoul.ip2region.xdb.Util;
|
||||||
|
import org.lionsoul.ip2region.xdb.Version;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class SearchTest {
|
public class SearchApp {
|
||||||
|
|
||||||
public static void printHelp(String[] args) {
|
public static void printHelp(String[] args) {
|
||||||
System.out.print("ip2region xdb searcher\n");
|
System.out.print("ip2region xdb searcher\n");
|
||||||
|
|
@ -24,21 +27,33 @@ public class SearchTest {
|
||||||
System.out.print(" bench search bench test\n");
|
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)) {
|
if ("file".equals(cachePolicy)) {
|
||||||
return Searcher.newWithFileOnly(dbPath);
|
return Searcher.newWithFileOnly(version, dbPath);
|
||||||
} else if ("vectorIndex".equals(cachePolicy)) {
|
} else if ("vectorIndex".equals(cachePolicy)) {
|
||||||
byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
|
byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
|
||||||
return Searcher.newWithVectorIndex(dbPath, vIndex);
|
return Searcher.newWithVectorIndex(version, dbPath, vIndex);
|
||||||
} else if ("content".equals(cachePolicy)) {
|
} else if ("content".equals(cachePolicy)) {
|
||||||
LongByteArray cBuff = Searcher.loadContentFromFile(dbPath);
|
LongByteArray cBuff = Searcher.loadContentFromFile(dbPath);
|
||||||
return Searcher.newWithBuffer(cBuff);
|
return Searcher.newWithBuffer(version, cBuff);
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("invalid cache policy `" + cachePolicy + "`, options: file/vectorIndex/content");
|
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";
|
String dbPath = "", cachePolicy = "vectorIndex";
|
||||||
for (final String r : args) {
|
for (final String r : args) {
|
||||||
if (r.length() < 5) {
|
if (r.length() < 5) {
|
||||||
|
|
@ -105,7 +120,7 @@ public class SearchTest {
|
||||||
System.out.println("searcher test program exited, thanks for trying");
|
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";
|
String dbPath = "", srcPath = "", cachePolicy = "vectorIndex";
|
||||||
for (final String r : args) {
|
for (final String r : args) {
|
||||||
if (r.length() < 5) {
|
if (r.length() < 5) {
|
||||||
|
|
@ -155,40 +170,44 @@ public class SearchTest {
|
||||||
String l = line.trim();
|
String l = line.trim();
|
||||||
String[] ps = l.split("\\|", 3);
|
String[] ps = l.split("\\|", 3);
|
||||||
if (ps.length != 3) {
|
if (ps.length != 3) {
|
||||||
|
reader.close();
|
||||||
System.out.printf("invalid ip segment `%s`\n", l);
|
System.out.printf("invalid ip segment `%s`\n", l);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long sip;
|
byte[] sip;
|
||||||
try {
|
try {
|
||||||
sip = Searcher.checkIP(ps[0]);
|
sip = Util.parseIP(ps[0]);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
reader.close();
|
||||||
System.out.printf("check start ip `%s`: %s\n", ps[0], e);
|
System.out.printf("check start ip `%s`: %s\n", ps[0], e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long eip;
|
byte[] eip;
|
||||||
try {
|
try {
|
||||||
eip = Searcher.checkIP(ps[1]);
|
eip = Util.parseIP(ps[1]);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
reader.close();
|
||||||
System.out.printf("check end ip `%s`: %s\n", ps[1], e);
|
System.out.printf("check end ip `%s`: %s\n", ps[1], e);
|
||||||
return;
|
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]);
|
System.out.printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long mip = (sip + eip) >> 1;
|
for (final byte[] ip : new byte[][]{sip, eip}) {
|
||||||
for (final long ip : new long[]{sip, (sip + mip) >> 1, mip, (mip + eip) >> 1, eip}) {
|
|
||||||
long sTime = System.nanoTime();
|
long sTime = System.nanoTime();
|
||||||
String region = searcher.search(ip);
|
String region = searcher.search(ip);
|
||||||
costs += System.nanoTime() - sTime;
|
costs += System.nanoTime() - sTime;
|
||||||
|
|
||||||
// check the region info
|
// check the region info
|
||||||
if (!ps[2].equals(region)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,14 +232,14 @@ public class SearchTest {
|
||||||
if ("search".equals(args[0])) {
|
if ("search".equals(args[0])) {
|
||||||
try {
|
try {
|
||||||
searchTest(args);
|
searchTest(args);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
System.out.printf("failed running search test: %s\n", e);
|
System.out.printf("failed running search test: %s\n", e);
|
||||||
}
|
}
|
||||||
} else if ("bench".equals(args[0])) {
|
} else if ("bench".equals(args[0])) {
|
||||||
try {
|
try {
|
||||||
benchTest(args);
|
benchTest(args);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
System.out.printf("failed running bench test: %s\n", e);
|
System.out.printf("fwailed running bench test: %s\n", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printHelp(args);
|
printHelp(args);
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
|
||||||
// @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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -9,18 +9,25 @@ package org.lionsoul.ip2region.xdb;
|
||||||
public class Header {
|
public class Header {
|
||||||
public final int version;
|
public final int version;
|
||||||
public final int indexPolicy;
|
public final int indexPolicy;
|
||||||
public final int createdAt;
|
public final long createdAt;
|
||||||
public final int startIndexPtr;
|
public final long startIndexPtr;
|
||||||
public final int endIndexPtr;
|
public final long endIndexPtr;
|
||||||
|
|
||||||
|
// since xdb 3.0 with IPv6 supporting
|
||||||
|
public final int ipVersion;
|
||||||
|
public final int runtimePtrBytes;
|
||||||
|
|
||||||
public final byte[] buffer;
|
public final byte[] buffer;
|
||||||
|
|
||||||
public Header(byte[] buff) {
|
public Header(byte[] buff) {
|
||||||
assert buff.length >= 16;
|
assert buff.length >= 16;
|
||||||
version = Searcher.getInt2(buff, 0);
|
version = LittleEndian.getInt2(buff, 0);
|
||||||
indexPolicy = Searcher.getInt2(buff, 2);
|
indexPolicy = LittleEndian.getInt2(buff, 2);
|
||||||
createdAt = Searcher.getInt(buff, 4);
|
createdAt = LittleEndian.getUint32(buff, 4);
|
||||||
startIndexPtr = Searcher.getInt(buff, 8);
|
startIndexPtr = LittleEndian.getUint32(buff, 8);
|
||||||
endIndexPtr = Searcher.getInt(buff, 12);
|
endIndexPtr = LittleEndian.getUint32(buff, 12);
|
||||||
|
ipVersion = LittleEndian.getInt2(buff, 16);
|
||||||
|
runtimePtrBytes = LittleEndian.getInt2(buff, 18);
|
||||||
buffer = buff;
|
buffer = buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,7 +37,9 @@ public class Header {
|
||||||
"IndexPolicy: " + indexPolicy + ',' +
|
"IndexPolicy: " + indexPolicy + ',' +
|
||||||
"CreatedAt: " + createdAt + ',' +
|
"CreatedAt: " + createdAt + ',' +
|
||||||
"StartIndexPtr: " + startIndexPtr + ',' +
|
"StartIndexPtr: " + startIndexPtr + ',' +
|
||||||
"EndIndexPtr: " + endIndexPtr +
|
"EndIndexPtr: " + endIndexPtr + ',' +
|
||||||
|
"IPVersion: " + ipVersion + ',' +
|
||||||
|
"RuntimePtrBytes: " + runtimePtrBytes +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
||||||
|
// @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
||||||
|
// @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
||||||
|
// @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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
||||||
|
// @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -104,27 +104,17 @@ public class LongByteArray {
|
||||||
return copy(offset, buffer, 0, length);
|
return copy(offset, buffer, 0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get a 4-bytes long integer from the specified index
|
// get a 4-bytes uint32 integer from the specified index
|
||||||
public long getIntLong(long offset) {
|
public long getUint32(long offset) {
|
||||||
final byte[] b = new byte[4];
|
final byte[] b = new byte[4];
|
||||||
copy(offset, b, 0, 4);
|
copy(offset, b, 0, 4);
|
||||||
return (
|
return LittleEndian.getUint32(b, 0);
|
||||||
((b[0] & 0x000000FFL)) |
|
|
||||||
((b[1] << 8) & 0x0000FF00L) |
|
|
||||||
((b[2] << 16) & 0x00FF0000L) |
|
|
||||||
((b[3] << 24) & 0xFF000000L)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getInt(long offset) {
|
public int getInt2(long offset) {
|
||||||
final byte[] b = new byte[4];
|
final byte[] b = new byte[4];
|
||||||
copy(offset, b, 0, 4);
|
copy(offset, b, 0, 4);
|
||||||
return (
|
return LittleEndian.getInt2(b, 0);
|
||||||
((b[0] & 0x000000FF)) |
|
|
||||||
((b[1] << 8) & 0x0000FF00) |
|
|
||||||
((b[2] << 16) & 0x00FF0000) |
|
|
||||||
((b[3] << 24) & 0xFF000000)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// position entry class
|
// position entry class
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,26 @@ package org.lionsoul.ip2region.xdb;
|
||||||
// @Author Lion <chenxin619315@gmail.com>
|
// @Author Lion <chenxin619315@gmail.com>
|
||||||
// @Date 2022/06/23
|
// @Date 2022/06/23
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
public class Searcher {
|
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
|
// constant defined copied from the xdb maker
|
||||||
public static final int HeaderInfoLength = 256;
|
public static final int HeaderInfoLength = 256;
|
||||||
public static final int VectorIndexRows = 256;
|
public static final int VectorIndexRows = 256;
|
||||||
public static final int VectorIndexCols = 256;
|
public static final int VectorIndexCols = 256;
|
||||||
public static final int VectorIndexSize = 8;
|
public static final int VectorIndexSize = 8;
|
||||||
public static final int SegmentIndexSize = 14;
|
|
||||||
|
|
||||||
// Linux max write / read bytes
|
// Linux max write / read bytes
|
||||||
public static final int MAX_WRITE_BYTES = 0x7ffff000;
|
public static final int MAX_WRITE_BYTES = 0x7ffff000;
|
||||||
|
|
||||||
|
// ip version
|
||||||
|
private final Version version;
|
||||||
|
|
||||||
// random access file handle for file-based search
|
// random access file handle for file-based search
|
||||||
private final RandomAccessFile handle;
|
private final RandomAccessFile handle;
|
||||||
|
|
||||||
|
|
@ -40,21 +45,22 @@ public class Searcher {
|
||||||
|
|
||||||
// --- static method to create searchers
|
// --- static method to create searchers
|
||||||
|
|
||||||
public static Searcher newWithFileOnly(String dbPath) throws IOException {
|
public static Searcher newWithFileOnly(Version version, String dbPath) throws IOException {
|
||||||
return new Searcher(dbPath, null, null);
|
return new Searcher(version, dbPath, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Searcher newWithVectorIndex(String dbPath, byte[] vectorIndex) throws IOException {
|
public static Searcher newWithVectorIndex(Version version, String dbPath, byte[] vectorIndex) throws IOException {
|
||||||
return new Searcher(dbPath, vectorIndex, null);
|
return new Searcher(version, dbPath, vectorIndex, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Searcher newWithBuffer(LongByteArray cBuff) throws IOException {
|
public static Searcher newWithBuffer(Version version, LongByteArray cBuff) throws IOException {
|
||||||
return new Searcher(null, null, cBuff);
|
return new Searcher(version, null, null, cBuff);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- End of creator
|
// --- 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) {
|
if (cBuff != null) {
|
||||||
this.handle = null;
|
this.handle = null;
|
||||||
this.vectorIndex = null;
|
this.vectorIndex = null;
|
||||||
|
|
@ -72,62 +78,68 @@ public class Searcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Version getIPVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
public int getIOCount() {
|
public int getIOCount() {
|
||||||
return ioCount;
|
return ioCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String search(String ipStr) throws Exception {
|
public String search(String ipStr) throws Exception {
|
||||||
long ip = checkIP(ipStr);
|
return search(Util.parseIP(ipStr));
|
||||||
return search(ip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// reset the global counter
|
||||||
this.ioCount = 0;
|
this.ioCount = 0;
|
||||||
|
|
||||||
// locate the segment index block based on the vector index
|
// locate the segment index block based on the vector index
|
||||||
long sPtr = 0, ePtr = 0;
|
long sPtr = 0, ePtr = 0;
|
||||||
int il0 = (int) ((ip >> 24) & 0xFF);
|
int il0 = (int) (ip[0] & 0xFF);
|
||||||
int il1 = (int) ((ip >> 16) & 0xFF);
|
int il1 = (int) (ip[1] & 0xFF);
|
||||||
int idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
|
int idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
|
||||||
// System.out.printf("il0: %d, il1: %d, idx: %d\n", il0, il1, idx);
|
// System.out.printf("il0: %d, il1: %d, idx: %d\n", il0, il1, idx);
|
||||||
if (vectorIndex != null) {
|
if (vectorIndex != null) {
|
||||||
sPtr = getIntLong(vectorIndex, idx);
|
sPtr = LittleEndian.getUint32(vectorIndex, idx);
|
||||||
ePtr = getIntLong(vectorIndex, idx + 4);
|
ePtr = LittleEndian.getUint32(vectorIndex, idx + 4);
|
||||||
} else if (contentBuff != null) {
|
} else if (contentBuff != null) {
|
||||||
sPtr = contentBuff.getIntLong(HeaderInfoLength + idx);
|
sPtr = contentBuff.getUint32(HeaderInfoLength + idx);
|
||||||
ePtr = contentBuff.getIntLong(HeaderInfoLength + idx + 4);
|
ePtr = contentBuff.getUint32(HeaderInfoLength + idx + 4);
|
||||||
} else {
|
} else {
|
||||||
final byte[] buff = new byte[VectorIndexSize];
|
final byte[] buff = new byte[VectorIndexSize];
|
||||||
read(HeaderInfoLength + idx, buff);
|
read(HeaderInfoLength + idx, buff);
|
||||||
sPtr = getIntLong(buff, 0);
|
sPtr = LittleEndian.getUint32(buff, 0);
|
||||||
ePtr = getIntLong(buff, 4);
|
ePtr = LittleEndian.getUint32(buff, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// System.out.printf("sPtr: %d, ePtr: %d\n", sPtr, ePtr);
|
// System.out.printf("sPtr: %d, ePtr: %d\n", sPtr, ePtr);
|
||||||
|
|
||||||
// binary search the segment index block to get the region info
|
// 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;
|
int dataLen = -1;
|
||||||
long dataPtr = -1, l = 0, h = (ePtr - sPtr) / SegmentIndexSize;
|
long dataPtr = -1, l = 0, h = (ePtr - sPtr) / segIndexSize;
|
||||||
while (l <= h) {
|
while (l <= h) {
|
||||||
long m = (l + h) >> 1;
|
long m = (l + h) >> 1;
|
||||||
long p = sPtr + m * SegmentIndexSize;
|
long p = sPtr + m * segIndexSize;
|
||||||
|
|
||||||
// read the segment index
|
// read the segment index
|
||||||
read(p, buff);
|
read(p, buff);
|
||||||
long sip = getIntLong(buff, 0);
|
if (version.ipSubCompare(ip, buff, 0) < 0) {
|
||||||
if (ip < sip) {
|
|
||||||
h = m - 1;
|
h = m - 1;
|
||||||
|
} else if (version.ipSubCompare(ip, buff, bytes) > 0) {
|
||||||
|
l = m + 1;
|
||||||
} else {
|
} else {
|
||||||
long eip = getIntLong(buff, 4);
|
dataLen = LittleEndian.getInt2(buff, dBytes);
|
||||||
if (ip > eip) {
|
dataPtr = LittleEndian.getUint32(buff, dBytes + 2);
|
||||||
l = m + 1;
|
break;
|
||||||
} else {
|
|
||||||
dataLen = getInt2(buff, 8);
|
|
||||||
dataPtr = getIntLong(buff, 10);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +173,7 @@ public class Searcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- static cache util function
|
// --- static util function
|
||||||
|
|
||||||
public static Header loadHeader(RandomAccessFile handle) throws IOException {
|
public static Header loadHeader(RandomAccessFile handle) throws IOException {
|
||||||
handle.seek(0);
|
handle.seek(0);
|
||||||
|
|
@ -222,63 +234,39 @@ public class Searcher {
|
||||||
return content;
|
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 */
|
// get the runtime ptr bytes
|
||||||
public static long getIntLong(byte[] b, int offset) {
|
int runtimePtrBytes = 0;
|
||||||
return (
|
if (header.version == STRUCTURE_20) {
|
||||||
((b[offset++] & 0x000000FFL)) |
|
runtimePtrBytes = 4;
|
||||||
((b[offset++] << 8) & 0x0000FF00L) |
|
} else if (header.version == STRUCTURE_30) {
|
||||||
((b[offset++] << 16) & 0x00FF0000L) |
|
runtimePtrBytes = header.runtimePtrBytes;
|
||||||
((b[offset ] << 24) & 0xFF000000L)
|
} else {
|
||||||
);
|
throw new XdbException("invalid structure version `" + header.version + "`");
|
||||||
}
|
|
||||||
|
|
||||||
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 + "`");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long ipDst = 0;
|
// 1, confirm the xdb file size
|
||||||
for (int i = 0; i < ps.length; i++) {
|
// to ensure that the maximum file pointer does not overflow
|
||||||
int val = Integer.parseInt(ps[i]);
|
final long maxFilePtr = (1L << (runtimePtrBytes * 8)) - 1;
|
||||||
if (val > 255) {
|
if (handle.length() > maxFilePtr) {
|
||||||
throw new Exception("ip part `"+ps[i]+"` should be less then 256");
|
throw new XdbException("xdb file exceeds the maximum supported bytes: "+maxFilePtr+"");
|
||||||
}
|
|
||||||
|
|
||||||
ipDst |= ((long) val << shiftIndex[i]);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ipDst & 0xFFFFFFFFL;
|
public static void verifyFromFile(String dbFile) throws IOException, XdbException {
|
||||||
|
final RandomAccessFile handle = new RandomAccessFile(dbFile, "r");
|
||||||
|
verify(handle);
|
||||||
|
handle.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
||||||
|
// @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 <chenxin619315@gmail.com>
|
||||||
|
// @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package xdb;
|
||||||
|
|
||||||
|
public class Version {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue