@deepkit/bson
Version:
Deepkit BSON parser
348 lines • 12.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseParser = void 0;
exports.readUint32LE = readUint32LE;
exports.readInt32LE = readInt32LE;
exports.readFloat64LE = readFloat64LE;
exports.parseObject = parseObject;
exports.parseArray = parseArray;
exports.deserializeBSONWithoutOptimiser = deserializeBSONWithoutOptimiser;
/*
* Deepkit Framework
* Copyright (C) 2021 Deepkit UG, Marc J. Schmidt
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the MIT License.
*
* You should have received a copy of the MIT License along with this program.
*/
const utils_js_1 = require("./utils.js");
const strings_js_1 = require("./strings.js");
const type_1 = require("@deepkit/type");
const model_js_1 = require("./model.js");
function readUint32LE(buffer, offset) {
return (buffer[offset] |
(buffer[offset + 1] << 8) |
(buffer[offset + 2] << 16) |
(buffer[offset + 3] << 24) >>> 0);
}
readUint32LE.__type = ['buffer', 'offset', 'readUint32LE', 'PW2!\'2"\'/#'];
function readInt32LE(buffer, offset) {
return (buffer[offset] |
(buffer[offset + 1] << 8) |
(buffer[offset + 2] << 16) |
(buffer[offset + 3] << 24));
}
readInt32LE.__type = ['buffer', 'offset', 'readInt32LE', 'PW2!\'2"\'/#'];
const float64Buffer = new ArrayBuffer(8);
const u32 = new Uint32Array(float64Buffer);
const f64 = new Float64Array(float64Buffer);
function readFloat64LE(buffer, offset) {
u32[0] =
buffer[offset] |
(buffer[offset + 1] << 8) |
(buffer[offset + 2] << 16) |
(buffer[offset + 3] << 24);
u32[1] =
buffer[offset + 4] |
(buffer[offset + 5] << 8) |
(buffer[offset + 6] << 16) |
(buffer[offset + 7] << 24);
return f64[0];
}
readFloat64LE.__type = ['buffer', 'offset', 'readFloat64LE', 'PW2!\'2"\'/#'];
/**
* This is the (slowest) base parser which parses all property names as utf8.
*/
class BaseParser {
constructor(buffer, offset = 0) {
this.buffer = buffer;
this.offset = offset;
this.size = buffer.byteLength;
}
peek(elementType, type) {
const offset = this.offset;
const v = this.parse(elementType, type);
this.offset = offset;
return v;
}
parse(elementType, type) {
switch (elementType) {
case utils_js_1.BSONType.STRING:
return this.parseString();
case utils_js_1.BSONType.NUMBER:
return this.parseNumber();
case utils_js_1.BSONType.OID:
return this.parseOid();
case utils_js_1.BSONType.INT:
return this.parseInt();
case utils_js_1.BSONType.DATE:
return this.parseDate();
case utils_js_1.BSONType.LONG:
case utils_js_1.BSONType.TIMESTAMP:
return this.parseLong();
case utils_js_1.BSONType.BOOLEAN:
return this.parseBoolean();
case utils_js_1.BSONType.NULL:
return null;
case utils_js_1.BSONType.UNDEFINED:
return undefined;
case utils_js_1.BSONType.BINARY:
return this.parseBinary(type);
case utils_js_1.BSONType.REGEXP:
return this.parseRegExp();
case utils_js_1.BSONType.OBJECT:
return parseObject(this);
case utils_js_1.BSONType.ARRAY:
return parseArray(this);
default:
throw new type_1.SerializationError('Unsupported BSON type ' + elementType);
}
}
parseRegExp() {
const source = this.eatString(this.stringSize());
const options = this.eatString(this.stringSize());
return new RegExp(source, options.replace('s', 'g'));
}
/**
* read the content without moving the parser offset.
*/
read(elementType, type) {
const start = this.offset;
try {
return this.parse(elementType, type);
}
finally {
this.offset = start;
}
}
parseBoolean() {
return this.eatByte() === 1;
}
parseLong() {
const lowBits = this.eatInt32();
const highBits = this.eatInt32();
return BigInt(highBits) * BigInt(utils_js_1.TWO_PWR_32_DBL_N) + (BigInt(lowBits >>> 0));
}
parseString() {
return this.eatString(this.eatUInt32());
}
parseBinaryBigInt() {
let size = this.eatUInt32();
const subType = this.eatByte();
if (subType === utils_js_1.BSON_BINARY_SUBTYPE_BYTE_ARRAY) {
size = this.eatUInt32();
}
const nextPosition = this.offset + size;
const v = this.readBigIntBinary(size);
this.offset = nextPosition;
return v;
}
parseSignedBinaryBigInt() {
let size = this.eatUInt32();
const subType = this.eatByte();
if (subType === utils_js_1.BSON_BINARY_SUBTYPE_BYTE_ARRAY) {
size = this.eatUInt32();
}
const nextPosition = this.offset + size;
const v = this.readSignedBigIntBinary(size);
this.offset = nextPosition;
return v;
}
parseBinary(type) {
let size = this.eatUInt32();
const subType = this.eatByte();
if (subType === utils_js_1.BSON_BINARY_SUBTYPE_UUID) {
const nextPosition = this.offset + size;
const v = this.parseUUID();
this.offset = nextPosition;
return v;
}
if (subType === utils_js_1.BSON_BINARY_SUBTYPE_BYTE_ARRAY) {
size = this.eatUInt32();
}
const b = this.buffer.slice(this.offset, this.offset + size);
this.seek(size);
if (type && type.kind === type_1.ReflectionKind.class && type.classType === ArrayBuffer) {
return (0, type_1.nodeBufferToArrayBuffer)(b);
}
if (type && type.kind === type_1.ReflectionKind.class) {
const typedArrayConstructor = type.classType;
return new typedArrayConstructor((0, type_1.nodeBufferToArrayBuffer)(b));
}
return b;
}
readBigIntBinary(size) {
if (size === 0)
return BigInt(0);
//todo: check if that is faster than the string concatenation
// let r = BigInt(0);
// const n8 = BigInt(8);
// for (let i = 0; i < size; i++) {
// if (i !== 0) r = r << n8;
// r += BigInt(this.buffer[this.offset + i]);
// }
// return r;
let s = '';
for (let i = 0; i < size; i++) {
s += model_js_1.hexTable[this.buffer[this.offset + i]];
}
return BigInt('0x' + s);
}
readSignedBigIntBinary(size) {
if (size === 0)
return BigInt(0);
let s = '';
const signum = this.buffer[this.offset];
for (let i = 1; i < size; i++) {
s += model_js_1.hexTable[this.buffer[this.offset + i]];
}
//255 means negative
if (signum === 255)
return BigInt('0x' + s) * BigInt(-1);
return BigInt('0x' + s);
}
parseNumber() {
return this.eatDouble();
}
parseOid() {
const offset = this.offset, b = this.buffer;
let o = model_js_1.hexTable[b[offset]]
+ model_js_1.hexTable[b[offset + 1]]
+ model_js_1.hexTable[b[offset + 2]]
+ model_js_1.hexTable[b[offset + 3]]
+ model_js_1.hexTable[b[offset + 4]]
+ model_js_1.hexTable[b[offset + 5]]
+ model_js_1.hexTable[b[offset + 6]]
+ model_js_1.hexTable[b[offset + 7]]
+ model_js_1.hexTable[b[offset + 8]]
+ model_js_1.hexTable[b[offset + 9]]
+ model_js_1.hexTable[b[offset + 10]]
+ model_js_1.hexTable[b[offset + 11]];
this.seek(12);
return o;
}
parseUUID() {
//e.g. bef8de96-41fe-442f-b70c-c3a150f8c96c
// 4 2 2 2 6
const offset = this.offset, b = this.buffer;
let o = model_js_1.hexTable[b[offset]]
+ model_js_1.hexTable[b[offset + 1]]
+ model_js_1.hexTable[b[offset + 2]]
+ model_js_1.hexTable[b[offset + 3]]
+ '-'
+ model_js_1.hexTable[b[offset + 4]]
+ model_js_1.hexTable[b[offset + 5]]
+ '-'
+ model_js_1.hexTable[b[offset + 6]]
+ model_js_1.hexTable[b[offset + 7]]
+ '-'
+ model_js_1.hexTable[b[offset + 8]]
+ model_js_1.hexTable[b[offset + 9]]
+ '-'
+ model_js_1.hexTable[b[offset + 10]]
+ model_js_1.hexTable[b[offset + 11]]
+ model_js_1.hexTable[b[offset + 12]]
+ model_js_1.hexTable[b[offset + 13]]
+ model_js_1.hexTable[b[offset + 14]]
+ model_js_1.hexTable[b[offset + 15]];
this.seek(16);
return o;
}
parseInt() {
return this.eatInt32();
}
parseDate() {
const lowBits = this.eatInt32();
const highBits = this.eatInt32();
return new Date(highBits * utils_js_1.TWO_PWR_32_DBL_N + (lowBits >>> 0));
}
peekUInt32() {
return readUint32LE(this.buffer, this.offset);
}
/**
* Returns the size including \0.
*/
stringSize() {
let end = this.offset;
while (this.buffer[end] !== 0 && end < this.size)
end++;
end++; //null
return end - this.offset;
}
eatObjectPropertyName() {
let end = this.offset;
while (this.buffer[end] !== 0) {
if (end >= this.size)
throw new type_1.SerializationError('Unexpected end of buffer');
end++;
}
const s = (0, strings_js_1.decodeUTF8)(this.buffer, this.offset, end);
this.offset = end + 1;
return s;
}
seek(size) {
this.offset += size;
}
eatByte() {
return this.buffer[this.offset++];
}
eatInt32() {
const value = readInt32LE(this.buffer, this.offset);
this.offset += 4;
return value;
}
eatUInt32() {
const value = readUint32LE(this.buffer, this.offset);
this.offset += 4;
return value;
}
eatDouble() {
const value = readFloat64LE(this.buffer, this.offset);
this.offset += 8;
if (isNaN(value))
return 0;
return value;
}
/**
* Size includes the \0. If not existend, increase by 1.
*/
eatString(size) {
this.offset += size;
return (0, strings_js_1.decodeUTF8)(this.buffer, this.offset - size, this.offset - 1);
}
}
exports.BaseParser = BaseParser;
BaseParser.__type = ['size', 'buffer', 'offset', () => 0, 'constructor', 'elementType', 'Type', 'type', 'peek', 'parse', 'parseRegExp', 'read', 'parseBoolean', 'parseLong', 'parseString', 'parseBinaryBigInt', 'parseSignedBinaryBigInt', 'parseBinary', 'readBigIntBinary', 'readSignedBigIntBinary', 'parseNumber', 'parseOid', 'parseUUID', 'parseInt', 'parseDate', 'peekUInt32', 'stringSize', 'eatObjectPropertyName', 'seek', 'eatByte', 'eatInt32', 'eatUInt32', 'eatDouble', 'eatString', 'BaseParser', '\'3!PW2":\'2#:>$"0%P\'2&"w\'2(8"0)P\'2&"w\'2(8"0*PA0+P\'2&"w\'2(8"0,P"0-P"0.P"0/P*00P*01P"w\'2(8"02P\'2!*03P\'2!*04P"05P&06P&07P"08P"09P\'0:P\'0;P"0<P\'2!"0=P\'0>P\'0?P\'0@P\'0AP\'2!&0B5wC'];
function parseObject(parser) {
const result = {};
const end = parser.eatUInt32() + parser.offset;
while (parser.offset < end) {
const elementType = parser.eatByte();
if (elementType === 0)
break;
const name = parser.eatObjectPropertyName();
result[name] = parser.parse(elementType);
}
return result;
}
parseObject.__type = [() => BaseParser, 'parser', 'parseObject', 'PP7!2""/#'];
function parseArray(parser) {
const result = [];
const end = parser.eatUInt32() + parser.offset;
for (let i = 0; parser.offset < end; i++) {
const elementType = parser.eatByte();
if (elementType === 0)
break;
//arrays are represented as objects, so we skip the key name, since we have `i`
parser.seek((0, utils_js_1.digitByteSize)(i));
result.push(parser.parse(elementType));
}
return result;
}
parseArray.__type = [() => BaseParser, 'parser', 'parseArray', 'PP7!2""F/#'];
function deserializeBSONWithoutOptimiser(buffer, offset = 0) {
return parseObject(new BaseParser(buffer, offset));
}
deserializeBSONWithoutOptimiser.__type = ['buffer', 'offset', 'deserializeBSONWithoutOptimiser', 'PW2!"2""/#'];
//# sourceMappingURL=bson-parser.js.map