// 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. // util functions // @Author Lion const header = require('./header'); const fs = require('fs'); // -- // parse the specified string ip and return its bytes // parse ipv4 address function _parse_ipv4_addr(v4String) { let ps = v4String.split('.', 4); if (ps.length != 4) { throw new Error('invalid ipv4 address'); } var v; const ipBytes = Buffer.alloc(4); for (var i = 0; i < ps.length; i++) { v = parseInt(ps[i], 10); if (isNaN(v)) { throw new Error(`invalid ipv4 part '${ps[i]}', a valid number expected`); } if (v < 0 || v > 255) { throw new Error(`invalid ipv4 part '${ps[i]}' should >= 0 and <= 255`); } ipBytes[i] = (v & 0xFF); } return ipBytes; } // parse ipv6 address function _parse_ipv6_addr(v6String) { let ps = v6String.split(':', 8); if (ps.length < 3) { throw new Error('invalid ipv6 address'); } let dc_num = 0, offset = 0; const ipBytes = Buffer.alloc(16); for (var i = 0; i < ps.length; i++) { let s = ps[i].trim(); // Double colon check and auto padding if (s.length == 0) { // ONLY one double colon allow if (dc_num > 0) { throw new Error('invalid ipv6 address: multi double colon detected'); } let start = i, mi = ps.length - 1; // clear all the consecutive spaces for (i++;;) { s = ps[i].trim(); if (s.length > 0) { i--; break; } if (i >= mi) { break; } i++; } dc_num = 1; // padding = 8 - start - left let padding = 8 - start - (mi - i); // console.log(`i=${i}, padding=${padding}`); offset += 2 * padding; continue; } let v = parseInt(s, 16); if (isNaN(v)) { throw new Error(`invalid ipv6 part '${ps[i]}', a valid hex number expected`); } if (v < 0 || v > 0xFFFF) { throw new Error(`invalid ipv6 part '${ps[i]}' should >= 0 and <= 65534`); } // console.log(`${i}: v=${v}, offset=${offset}`); ipBytes.writeUint16BE(v, offset); offset += 2; } return ipBytes; } // @param ip string // @return Buffer function parseIP(ipString) { let sDot = ipString.indexOf('.'); let cDot = ipString.indexOf(':'); if (sDot > -1 && cDot == -1) { return _parse_ipv4_addr(ipString); } else if (cDot > -1) { return _parse_ipv6_addr(ipString); } else { return null; } } // --- // --- // bytes ip to humen-readable string ip // ipv4 bytes to string function _ipv4_to_string(v4Bytes) { return v4Bytes.join('.'); } // ipv6 bytes to string function _ipv6_to_string(v6Bytes, compress) { let ps = []; for (var i = 0; i < v6Bytes.length; i += 2) { ps.push(v6Bytes.readUint16BE(i).toString(16)); } if (compress === false) { return ps.join(':'); } // auto compression of consecutive zero let j = 0, mi = ps.length - 1; let _ = []; for (i = 0; i < ps.length; i++) { // console.log(`i=${i}, v=${ps[i]}`); if (i >= mi || j > 0) { _.push(ps[i]); continue; } if (ps[i] != '0' || ps[i+1] != '0') { _.push(ps[i]); continue; } // find the first two zero part // and keep find all the zero part for (i += 2; i < ps.length;) { if (ps[i] != '0') { i--; break; } i++; } // make sure there is an empty head. if (_.length == 0) { _.push(''); } _.push(''); // empty for double colon // make sure there is an empty tail if (i == ps.length && _.length == 2) { _.push(''); } } // console.log(`ps.length=${ps.length}, _.length=${_.length}`); return _.join(':'); } function ipToString(ipBytes, compress) { if (!Buffer.isBuffer(ipBytes)) { throw new Error('invalid bytes ip, not a Buffer'); } if (ipBytes.length == 4) { return _ipv4_to_string(ipBytes, compress); } else if (ipBytes.length == 16) { return _ipv6_to_string(ipBytes, compress); } else { throw new Error('invalid bytes ip with length not 4 or 16'); } } // print ip bytes function ipBytesString(ipBytes) { if (!Buffer.isBuffer(ipBytes)) { throw new Error('invalid bytes ip, not a Buffer'); } let ps = []; for (var i = 0; i < ipBytes.length; i++) { ps.push(ipBytes[i] & 0xFF); } // return ps.join('.'); } // compare two byte ips // returns: -1 if ip1 < ip2, 1 if ip1 > ip2 or 0 function ipCompare(ip1, ip2) { } // load header from xdb file function loadHeader(fd) { const buffer = Buffer.alloc(header.HeaderInfoLength); const rBytes = fs.readSync(fd, buffer, 0, header.HeaderInfoLength, 0); if (rBytes != header.HeaderInfoLength) { throw new Error(`incomplete read (${rBytes} read, ${header.HeaderInfoLength} expected)`); } return new header.Header(buffer); } function loadHeaderFromFile(dbPath) { const fd = fs.openSync(dbPath, "r"); const header = loadHeader(fd); fs.closeSync(fd); return header; } function loadVectorIndex(fd) { const vBytes = header.VectorIndexCols * header.VectorIndexRows * header.VectorIndexSize; const buffer = Buffer.alloc(vBytes); const rBytes = fs.readSync(fd, buffer, 0, vBytes, header.HeaderInfoLength); if (rBytes != vBytes) { throw new Error(`incomplete read (${rBytes} read, ${vBytes} expected)`); } return buffer; } function loadVectorIndexFromFile(dbPath) { const fd = fs.openSync(dbPath, "r"); const vIndex = loadVectorIndex(fd); fs.closeSync(fd); return vIndex; } function loadContent(fd) { const stats = fs.fstatSync(fd); const buffer = Buffer.alloc(stats.size); const rBytes = fs.readSync(fd, buffer, 0, buffer.length, 0); if (rBytes != stats.size) { throw new Error(`incomplete read (${rBytes} read, ${stats.size} expected)`); } return buffer; } function loadContentFromFile(dbPath) { const fd = fs.openSync(dbPath, "r"); const content = loadContent(fd); fs.close(fd, function(){}); return content; } module.exports = { parseIP, ipToString, ipBytesString, ipCompare, loadHeader, loadHeaderFromFile, loadVectorIndex, loadVectorIndexFromFile, loadContent, loadContentFromFile }