UNPKG

mmdb-lib

Version:

Maxmind DB (MMDB) Library

250 lines (249 loc) 10.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = __importDefault(require("./utils")); utils_1.default.assert(typeof BigInt !== 'undefined', 'Apparently you are using old version of node. Please upgrade to node 10.4.x or above.'); const MAX_INT_32 = 2_147_483_647; var DataType; (function (DataType) { DataType[DataType["Extended"] = 0] = "Extended"; DataType[DataType["Pointer"] = 1] = "Pointer"; DataType[DataType["Utf8String"] = 2] = "Utf8String"; DataType[DataType["Double"] = 3] = "Double"; DataType[DataType["Bytes"] = 4] = "Bytes"; DataType[DataType["Uint16"] = 5] = "Uint16"; DataType[DataType["Uint32"] = 6] = "Uint32"; DataType[DataType["Map"] = 7] = "Map"; DataType[DataType["Int32"] = 8] = "Int32"; DataType[DataType["Uint64"] = 9] = "Uint64"; DataType[DataType["Uint128"] = 10] = "Uint128"; DataType[DataType["Array"] = 11] = "Array"; DataType[DataType["Container"] = 12] = "Container"; DataType[DataType["EndMarker"] = 13] = "EndMarker"; DataType[DataType["Boolean"] = 14] = "Boolean"; DataType[DataType["Float"] = 15] = "Float"; })(DataType || (DataType = {})); const pointerValueOffset = [0, 2048, 526336, 0]; const noCache = { get: () => undefined, set: () => undefined, }; const cursor = (value, offset) => ({ value, offset }); class Decoder { constructor(db, baseOffset = 0, cache = noCache) { this.telemetry = {}; utils_1.default.assert(Boolean(db), 'Database buffer is required'); this.db = db; this.baseOffset = baseOffset; this.cache = cache; } decode(offset) { let tmp; const ctrlByte = this.db[offset++]; let type = ctrlByte >> 5; if (type === DataType.Pointer) { tmp = this.decodePointer(ctrlByte, offset); return cursor(this.decodeFast(tmp.value).value, tmp.offset); } if (type === DataType.Extended) { tmp = this.db[offset] + 7; if (tmp < 8) { throw new Error('Invalid Extended Type at offset ' + offset + ' val ' + tmp); } type = tmp; offset++; } const size = this.sizeFromCtrlByte(ctrlByte, offset); return this.decodeByType(type, size.offset, size.value); } decodeFast(offset) { const cached = this.cache.get(offset); if (cached) { return cached; } const result = this.decode(offset); this.cache.set(offset, result); return result; } decodeByType(type, offset, size) { const newOffset = offset + size; // ipv4 types occurrence stats: // 3618591 x utf8_string // 448163 x map // 175085 x uint32 // 83040 x double // 24745 x array // 3 x uint16 // 1 x uint64 // 14 x boolean switch (type) { case DataType.Utf8String: return cursor(this.decodeString(offset, size), newOffset); case DataType.Map: return this.decodeMap(size, offset); case DataType.Uint32: return cursor(this.decodeUint(offset, size), newOffset); case DataType.Double: return cursor(this.decodeDouble(offset), newOffset); case DataType.Array: return this.decodeArray(size, offset); case DataType.Boolean: return cursor(this.decodeBoolean(size), offset); case DataType.Float: return cursor(this.decodeFloat(offset), newOffset); case DataType.Bytes: return cursor(this.decodeBytes(offset, size), newOffset); case DataType.Uint16: return cursor(this.decodeUint(offset, size), newOffset); case DataType.Int32: return cursor(this.decodeInt32(offset, size), newOffset); case DataType.Uint64: return cursor(this.decodeBigUint(offset, size), newOffset); case DataType.Uint128: return cursor(this.decodeBigUint(offset, size), newOffset); } throw new Error('Unknown type ' + type + ' at offset ' + offset); } sizeFromCtrlByte(ctrlByte, offset) { // The first three bits of the control byte tell you what type the field is. If // these bits are all 0, then this is an "extended" type, which means that the // *next* byte contains the actual type. Otherwise, the first three bits will // contain a number from 1 to 7, the actual type for the field. // var type = ctrlByte >> 3; // The next five bits in the control byte tell you how long the data field's // payload is, except for maps and pointers. Maps and pointers use this size // information a bit differently.`` const size = ctrlByte & 0x1f; // If the five bits are smaller than 29, then those bits are the payload size in // bytes. For example: // 01000010 UTF-8 string - 2 bytes long // 01011100 UTF-8 string - 28 bytes long // 11000001 unsigned 32-bit int - 1 byte long // 00000011 00000011 unsigned 128-bit int - 3 bytes long if (size < 29) { return cursor(size, offset); } // If the value is 29, then the size is 29 + *the next byte after the type // specifying bytes as an unsigned integer*. if (size === 29) { return cursor(29 + this.db[offset], offset + 1); } // If the value is 30, then the size is 285 + *the next two bytes after the type // specifying bytes as a single unsigned integer*. if (size === 30) { return cursor(285 + this.db.readUInt16BE(offset), offset + 2); } // At this point `size` is always 31. // If the value is 31, then the size is 65,821 + *the next three bytes after the // type specifying bytes as a single unsigned integer*. return cursor(65821 + this.db.readUIntBE(offset, 3), offset + 3); } decodeBytes(offset, size) { return this.db.subarray(offset, offset + size); } decodePointer(ctrlByte, offset) { // Pointers use the last five bits in the control byte to calculate the pointer value. // To calculate the pointer value, we start by subdividing the five bits into two // groups. The first two bits indicate the size, and the next three bits are part // of the value, so we end up with a control byte breaking down like this: // 001SSVVV. const pointerSize = (ctrlByte >> 3) & 3; const pointer = this.baseOffset + pointerValueOffset[pointerSize]; let packed = 0; // The size can be 0, 1, 2, or 3. // If the size is 0, the pointer is built by appending the next byte to the last // three bits to produce an 11-bit value. if (pointerSize === 0) { packed = ((ctrlByte & 7) << 8) | this.db[offset]; // If the size is 1, the pointer is built by appending the next two bytes to the // last three bits to produce a 19-bit value + 2048. } else if (pointerSize === 1) { packed = ((ctrlByte & 7) << 16) | this.db.readUInt16BE(offset); // If the size is 2, the pointer is built by appending the next three bytes to the // last three bits to produce a 27-bit value + 526336. } else if (pointerSize === 2) { packed = ((ctrlByte & 7) << 24) | this.db.readUIntBE(offset, 3); // At next point `size` is always 3. // Finally, if the size is 3, the pointer's value is contained in the next four // bytes as a 32-bit value. In this case, the last three bits of the control byte // are ignored. } else { packed = this.db.readUInt32BE(offset); } offset += pointerSize + 1; return cursor(pointer + packed, offset); } decodeArray(size, offset) { let tmp; const array = new Array(size); for (let i = 0; i < size; i++) { tmp = this.decode(offset); offset = tmp.offset; array[i] = tmp.value; } return cursor(array, offset); } decodeBoolean(size) { return size !== 0; } decodeDouble(offset) { return this.db.readDoubleBE(offset); } decodeFloat(offset) { return this.db.readFloatBE(offset); } decodeMap(size, offset) { let tmp; let key; const map = {}; for (let i = 0; i < size; i++) { tmp = this.decode(offset); key = tmp.value; tmp = this.decode(tmp.offset); offset = tmp.offset; map[key] = tmp.value; } return cursor(map, offset); } decodeInt32(offset, size) { if (size === 0) { return 0; } if (size < 4) { return this.db.readUIntBE(offset, size); } return this.db.readInt32BE(offset); } decodeUint(offset, size) { if (size === 0) { return 0; } if (size <= 4) { return this.db.readUIntBE(offset, size); } throw new Error(`Invalid size for unsigned integer: ${size}`); } decodeString(offset, size) { const newOffset = offset + size; return newOffset >= MAX_INT_32 ? this.db.subarray(offset, newOffset).toString('utf8') : this.db.toString('utf8', offset, newOffset); } decodeBigUint(offset, size) { if (size > 16) { throw new Error(`Invalid size for big unsigned integer: ${size}`); } let integer = 0n; for (let i = 0; i < size; i++) { integer <<= 8n; integer |= BigInt(this.db.readUInt8(offset + i)); } return integer; } } exports.default = Decoder;