mmdb-lib
Version:
Maxmind DB (MMDB) Library
250 lines (249 loc) • 10.2 kB
JavaScript
"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;