searcher with ipv4 tests passed
This commit is contained in:
parent
e924a7c17d
commit
ad78dbe547
|
|
@ -0,0 +1,142 @@
|
|||
// 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.
|
||||
|
||||
// searcher implementation
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
const fs = require('fs');
|
||||
const {
|
||||
parseIP,
|
||||
HeaderInfoLength, VectorIndexCols, VectorIndexSize
|
||||
} = require('./util');
|
||||
|
||||
class Searcher {
|
||||
constructor(version, dbPath, vectorIndex, cBuffer) {
|
||||
this.ioCount = 0;
|
||||
this.version = version;
|
||||
if (cBuffer != null) {
|
||||
this.handle = null;
|
||||
this.vectorIndex = null;
|
||||
this.cBuffer = cBuffer;
|
||||
} else {
|
||||
this.handle = fs.openSync(dbPath, 'r');
|
||||
this.vectorIndex = vectorIndex;
|
||||
this.cBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
getIOCount() {
|
||||
return this.ioCount;
|
||||
}
|
||||
|
||||
async search(ip) {
|
||||
let ipBytes;
|
||||
if (Buffer.isBuffer(ip)) {
|
||||
ipBytes = ip;
|
||||
} else {
|
||||
ipBytes = parseIP(ip);
|
||||
}
|
||||
|
||||
// ip version check
|
||||
if (ipBytes.length != this.version.bytes) {
|
||||
throw new Error(`invalid ip address (${this.version.name} expected)`);
|
||||
}
|
||||
|
||||
// reset the global counter
|
||||
this.ioCount = 0;
|
||||
|
||||
// located the segment index block based on the vector index
|
||||
let sPtr = 0, ePtr = 0;
|
||||
let il0 = ipBytes[0] & 0xFF, il1 = ipBytes[1] & 0xFF;
|
||||
let idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
|
||||
if (this.vectorIndex != null) {
|
||||
sPtr = this.vectorIndex.readUint32LE(idx);
|
||||
ePtr = this.vectorIndex.readUint32LE(idx + 4);
|
||||
} else if (this.cBuffer != null) {
|
||||
sPtr = this.vectorIndex.readUint32LE(HeaderInfoLength + idx);
|
||||
ePtr = this.vectorIndex.readUint32LE(HeaderInfoLength + idx + 4);
|
||||
} else {
|
||||
const buff = Buffer.alloc(VectorIndexSize);
|
||||
this.read(HeaderInfoLength + idx, buff);
|
||||
sPtr = buff.readUInt32LE(0);
|
||||
ePtr = buff.readUInt32LE(4);
|
||||
}
|
||||
|
||||
// console.log(`sPtr: ${sPtr}, ePtr: ${ePtr}`);
|
||||
// binary search the segment index block to get the region info
|
||||
const bytes = ipBytes.length, dBytes = ipBytes.length << 1;
|
||||
const indexSize = this.version.indexSize;
|
||||
let buff = Buffer.alloc(indexSize);
|
||||
let dLen = -1, dPtr = -1, l = 0, h = (ePtr - sPtr) / indexSize;
|
||||
while (l <= h) {
|
||||
const m = (l + h) >> 1;
|
||||
const p = sPtr + m * indexSize;
|
||||
|
||||
// read the segment index block
|
||||
this.read(p, buff);
|
||||
if (this.version.ipSubCompare(ipBytes, buff, 0) < 0) {
|
||||
h = m - 1;
|
||||
} else if (this.version.ipSubCompare(ip, buff, bytes) > 0) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
dLen = buff.readUint16LE(dBytes);
|
||||
dPtr = buff.readUint32LE(dBytes + 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(`dLen: ${dLen}, dPtr: ${dPtr}`);
|
||||
// empty match interception
|
||||
// @Note: could this even be a case ?
|
||||
if (dPtr < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const region = Buffer.alloc(dLen);
|
||||
this.read(dPtr, region);
|
||||
return region.toString('utf-8');
|
||||
}
|
||||
|
||||
read(offset, buff, stats) {
|
||||
// check the in-memory buffer first
|
||||
if (this.cBuffer != null) {
|
||||
this.cBuffer.copy(buff, 0, offset, offset + buff.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// increase the io counts
|
||||
this.ioCount++;
|
||||
|
||||
// read the data
|
||||
let rBytes = fs.readSync(this.handle, buff, 0, buff.length, offset);
|
||||
if (rBytes != buff.length) {
|
||||
throw new Error(`incomplete read: read bytes should be ${buff.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
// close the searcher
|
||||
close() {
|
||||
if (this.handle != null) {
|
||||
fs.close(this.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newWithFileOnly(version, dbPath) {
|
||||
return new Searcher(version, dbPath, null, null);
|
||||
}
|
||||
|
||||
function newWithVectorIndex(version, dbPath, vectorIndex) {
|
||||
return new Searcher(version, dbPath, vectorIndex, null);
|
||||
}
|
||||
|
||||
function newWithBuffer(version, cBuffer) {
|
||||
return new Searcher(version, null, null, cBuffer);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
newWithFileOnly,
|
||||
newWithVectorIndex,
|
||||
newWithBuffer
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.
|
||||
|
||||
// searcher tester
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
const {IPv4, parseIP, ipToString} = require('../util');
|
||||
const {newWithFileOnly} = require('../searcher');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = {
|
||||
v4: path.join(__dirname, '..', '..', '..', 'data', 'ip2region_v4.xdb'),
|
||||
v6: path.join(__dirname, '..', '..', '..', 'data', 'ip2region_v6.xdb')
|
||||
}
|
||||
|
||||
test('ipv4 searcher test', async () => {
|
||||
try {
|
||||
let searcher = newWithFileOnly(IPv4, dbPath.v4);
|
||||
let ip_list = [
|
||||
'1.0.0.0',
|
||||
parseIP('113.118.112.93'),
|
||||
'240e:3b7::'
|
||||
];
|
||||
|
||||
for (var i = 0; i < ip_list.length; i++) {
|
||||
let ip = ip_list[i];
|
||||
let region = await searcher.search(ip);
|
||||
let ipStr = Buffer.isBuffer(ip) ? ipToString(ip) : ip;
|
||||
console.log(`search(${ipStr}): {region: ${region}, ioCount: ${searcher.getIOCount()}}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`${e.message}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -16,7 +16,13 @@ test("test version from name", () => {
|
|||
let vs = ["v4", "ipv4", "v4x", "v6", "ipv6", "v6x"];
|
||||
vs.forEach(ele => {
|
||||
let v = util.versionFromName(ele);
|
||||
console.log(`versionFrom(${ele})`, v == null ? null : v.toString());
|
||||
if (v == null) {
|
||||
console.log(`invalid version name ${ele}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`versionFrom(${ele})`, v.toString());
|
||||
console.log(`version.id=${v.id}, version.name=${v.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -33,38 +33,6 @@ class Header {
|
|||
this.buff = buff;
|
||||
}
|
||||
|
||||
version() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
indexPolicy() {
|
||||
return this.indexPolicy;
|
||||
}
|
||||
|
||||
createdAt() {
|
||||
return this.createdAt;
|
||||
}
|
||||
|
||||
startIndexPtr() {
|
||||
return this.startIndexPtr;
|
||||
}
|
||||
|
||||
endIndexPtr() {
|
||||
return this.endIndexPtr;
|
||||
}
|
||||
|
||||
ipVersion() {
|
||||
return this.ipVersion;
|
||||
}
|
||||
|
||||
runtimePtrBytes() {
|
||||
return this.runtimePtrBytes;
|
||||
}
|
||||
|
||||
buff() {
|
||||
return this.buff;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `{
|
||||
"version":${this.version},
|
||||
|
|
@ -88,26 +56,6 @@ class Version {
|
|||
this.ipCompareFunc = ipCompareFunc;
|
||||
}
|
||||
|
||||
id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
name() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
bytes() {
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
indexSize() {
|
||||
return this.indexSize;
|
||||
}
|
||||
|
||||
compareFunc() {
|
||||
return this.ipCompareFunc;
|
||||
}
|
||||
|
||||
ipCompare(ip1, ip2) {
|
||||
return this.ipCompareFunc(ip1, ip2, 0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue