mmdb-lib
Version:
Maxmind DB (MMDB) Library
111 lines (110 loc) • 4.94 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Reader = void 0;
const decoder_1 = __importDefault(require("./decoder"));
const ip_1 = __importDefault(require("./ip"));
const metadata_1 = require("./metadata");
const walker_1 = __importDefault(require("./reader/walker"));
const DATA_SECTION_SEPARATOR_SIZE = 16;
class Reader {
constructor(db, opts = {}) {
this.opts = opts;
this.load(db);
}
load(db) {
if (!Buffer.isBuffer(db)) {
throw new Error(`mmdb-lib expects an instance of Buffer, got: ${typeof db}`);
}
this.db = db;
this.metadata = (0, metadata_1.parseMetadata)(this.db);
this.decoder = new decoder_1.default(this.db, this.metadata.searchTreeSize + DATA_SECTION_SEPARATOR_SIZE, this.opts.cache);
this.walker = (0, walker_1.default)(this.db, this.metadata.recordSize);
this.ipv4StartNodeNumber = this.ipv4Start();
}
get(ipAddress) {
const [data] = this.getWithPrefixLength(ipAddress);
return data;
}
getWithPrefixLength(ipAddress) {
const [pointer, prefixLength] = this.findAddressInTree(ipAddress);
const data = pointer ? this.resolveDataPointer(pointer) : null;
return [data, prefixLength];
}
findAddressInTree(ipAddress) {
const rawAddress = ip_1.default.parse(ipAddress);
const nodeCount = this.metadata.nodeCount;
const bitLength = rawAddress.length * 8;
// Binary search tree consists of certain (`nodeCount`) number of nodes. Tree
// depth depends on the ip version, it's 32 for IPv4 and 128 for IPv6. Each
// tree node has the same fixed length and usually 6-8 bytes. It consists
// of two records, left and right:
// | node |
// | 0x000000 | 0x000000 |
let bit;
let nodeNumber = 0;
let offset;
let depth = 0;
// When storing IPv4 addresses in an IPv6 tree, they are stored as-is, so they
// occupy the first 32-bits of the address space (from 0 to 2**32 - 1).
// Which means they're padded with zeros.
if (rawAddress.length === 4) {
nodeNumber = this.ipv4StartNodeNumber;
}
// Record value can point to one of three things:
// 1. Another node in the tree (most common case)
// 2. Data section address with relevant information (less common case)
// 3. Point to the value of `nodeCount`, which means IP address is unknown
for (; depth < bitLength && nodeNumber < nodeCount; depth++) {
bit = ip_1.default.bitAt(rawAddress, depth);
offset = nodeNumber * this.metadata.nodeByteSize;
nodeNumber = bit ? this.walker.right(offset) : this.walker.left(offset);
}
if (nodeNumber > nodeCount) {
return [nodeNumber, depth];
}
return [null, depth];
}
resolveDataPointer(pointer) {
// In order to determine where in the file this offset really points to, we also
// need to know where the data section starts. This can be calculated by
// determining the size of the search tree in bytes and then adding an additional
// 16 bytes for the data section separator.
// So the final formula to determine the offset in the file is:
// $offset_in_file = ( $record_value - $node_count )
// + $search_tree_size_in_bytes
const resolved = pointer - this.metadata.nodeCount + this.metadata.searchTreeSize;
return this.decoder.decodeFast(resolved).value;
}
ipv4Start() {
if (this.metadata.ipVersion === 4) {
return 0;
}
const nodeCount = this.metadata.nodeCount;
let pointer = 0;
let i = 0;
for (; i < 96 && pointer < nodeCount; i++) {
const offset = pointer * this.metadata.nodeByteSize;
pointer = this.walker.left(offset);
}
return pointer;
}
}
exports.Reader = Reader;
__exportStar(require("./reader/response"), exports);