| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- /*
- * Created by Wu Jian Ping on - 2022/07/22.
- */
- const fs = require('fs')
- const VectorIndexSize = 8
- const VectorIndexCols = 256
- const VectorIndexLength = 256 * 256 * (4 + 4)
- const SegmentIndexSize = 14
- const IP_REGEX = /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/
- const getStartEndPtr = Symbol('#getStartEndPtr')
- const getBuffer = Symbol('#getBuffer')
- const openFilePromise = Symbol('#openFilePromise')
- class Searcher {
- constructor (dbFile, vectorIndex, buffer) {
- this._dbFile = dbFile
- this._vectorIndex = vectorIndex
- this._buffer = buffer
- if (this._buffer) {
- this._vectorIndex = this._buffer.subarray(256, 256 + VectorIndexLength)
- }
- }
- async [getStartEndPtr] (idx, fd, ioStatus) {
- if (this._vectorIndex) {
- const sPtr = this._vectorIndex.readUInt32LE(idx)
- const ePtr = this._vectorIndex.readUInt32LE(idx + 4)
- return { sPtr, ePtr }
- } else {
- const buf = await this[getBuffer](256 + idx, 8, fd, ioStatus)
- const sPtr = buf.readUInt32LE()
- const ePtr = buf.readUInt32LE(4)
- return { sPtr, ePtr }
- }
- }
- async [getBuffer] (offset, length, fd, ioStatus) {
- if (this._buffer) {
- return this._buffer.subarray(offset, offset + length)
- } else {
- const buf = Buffer.alloc(length)
- return new Promise((resolve, reject) => {
- ioStatus.ioCount += 1
- fs.read(fd, buf, 0, length, offset, (err) => {
- if (err) {
- reject(err)
- } else {
- resolve(buf)
- }
- })
- })
- }
- }
- [openFilePromise] (fileName) {
- return new Promise((resolve, reject) => {
- fs.open(fileName, 'r', (err, fd) => {
- if (err) {
- reject(err)
- } else {
- resolve(fd)
- }
- })
- })
- }
- async search (ip) {
- const startTime = process.hrtime()
- const ioStatus = {
- ioCount: 0
- }
- if (!isValidIp(ip)) {
- throw new Error(`IP: ${ip} is invalid`)
- }
- let fd = null
- if (!this._buffer) {
- fd = await this[openFilePromise](this._dbFile)
- }
- const ps = ip.split('.')
- const i0 = parseInt(ps[0])
- const i1 = parseInt(ps[1])
- const i2 = parseInt(ps[2])
- const i3 = parseInt(ps[3])
- const ipInt = i0 * 256 * 256 * 256 + i1 * 256 * 256 + i2 * 256 + i3
- const idx = i0 * VectorIndexCols * VectorIndexSize + i1 * VectorIndexSize
- const { sPtr, ePtr } = await this[getStartEndPtr](idx, fd, ioStatus)
- let l = 0
- let h = (ePtr - sPtr) / SegmentIndexSize
- let result = null
- while (l <= h) {
- const m = (l + h) >> 1
- const p = sPtr + m * SegmentIndexSize
- const buff = await this[getBuffer](p, SegmentIndexSize, fd, ioStatus)
- const sip = buff.readUInt32LE(0)
- if (ipInt < sip) {
- h = m - 1
- } else {
- const eip = buff.readUInt32LE(4)
- if (ipInt > eip) {
- l = m + 1
- } else {
- const dataLen = buff.readUInt16LE(8)
- const dataPtr = buff.readUInt32LE(10)
- const data = await this[getBuffer](dataPtr, dataLen, fd, ioStatus)
- result = data.toString('utf-8')
- break
- }
- }
- }
- if (fd) {
- fs.close(fd,function(){})
- }
- const diff = process.hrtime(startTime)
- const took = (diff[0] * 1e9 + diff[1]) / 1e3
- return { region: result, ioCount: ioStatus.ioCount, took }
- }
- }
- const _checkFile = dbPath => {
- try {
- fs.accessSync(dbPath, fs.constants.F_OK)
- } catch (err) {
- throw new Error(`${dbPath} ${err ? 'does not exist' : 'exists'}`)
- }
- try {
- fs.accessSync(dbPath, fs.constants.R_OK)
- } catch (err) {
- throw new Error(`${dbPath} ${err ? 'is not readable' : 'is readable'}`)
- }
- }
- const isValidIp = ip => {
- return IP_REGEX.test(ip)
- }
- const newWithFileOnly = dbPath => {
- _checkFile(dbPath)
- return new Searcher(dbPath, null, null)
- }
- const newWithVectorIndex = (dbPath, vectorIndex) => {
- _checkFile(dbPath)
- if (!Buffer.isBuffer(vectorIndex)) {
- throw new Error('vectorIndex is invalid')
- }
- return new Searcher(dbPath, vectorIndex, null)
- }
- const newWithBuffer = buffer => {
- if (!Buffer.isBuffer(buffer)) {
- throw new Error('buffer is invalid')
- }
- return new Searcher(null, null, buffer)
- }
- const loadVectorIndexFromFile = dbPath => {
- const fd = fs.openSync(dbPath, 'r')
- const buffer = Buffer.alloc(VectorIndexLength)
- fs.readSync(fd, buffer, 0, VectorIndexLength, 256)
- fs.close(fd,function(){})
- return buffer
- }
- const loadContentFromFile = dbPath => {
- const stats = fs.statSync(dbPath)
- const buffer = Buffer.alloc(stats.size)
- const fd = fs.openSync(dbPath, 'r')
- fs.readSync(fd, buffer, 0, stats.size, 0)
- fs.close(fd,function(){})
- return buffer
- }
- module.exports = {
- isValidIp,
- loadVectorIndexFromFile,
- loadContentFromFile,
- newWithFileOnly,
- newWithVectorIndex,
- newWithBuffer
- }
|