UNPKG

mmdb-lib

Version:

Maxmind DB (MMDB) Library

111 lines (110 loc) 4.94 kB
"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);