UNPKG

diffusion

Version:

Diffusion JavaScript client

400 lines (399 loc) 14.4 kB
"use strict"; /** * @module CBOR */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Tokeniser = void 0; var int64_impl_1 = require("./../data/primitive/int64-impl"); var errors_1 = require("../../errors/errors"); var uint8array_1 = require("../util/uint8array"); var consts_1 = require("./consts"); var context_1 = require("./context"); /* eslint-disable jsdoc/require-jsdoc */ // unique object for representing break codes var BREAK_FLAG = new Error(); // used for buffer value of virtual tokens var EMPTY_BUFFER = new Uint8Array(0); var MAX_SAFE_INTEGER = 9007199254740991; var MIN_SAFE_INTEGER = -9007199254740991; /** * Tokeniser for CBOR objects */ var Tokeniser = /** @class */ (function () { /** * Create a tokeniser * * @param data the buffer to tokenise * @param offset the starting position in the buffer * @param length the number of bytes to read from the buffer */ function Tokeniser(data, offset, length) { if (offset === void 0) { offset = 0; } if (length === void 0) { length = data.length; } this.bufferOffset = offset; this.length = length; this.data = data; this.context = new context_1.Context(); this.pos = this.bufferOffset; } /** * Reset the tokeniser */ Tokeniser.prototype.reset = function () { this.context = new context_1.Context(); this.token = undefined; this.type = undefined; this.pos = this.bufferOffset; }; /** * Check if more tokens can be read from the buffer * * @return `true` if there are more tokens */ Tokeniser.prototype.hasRemaining = function () { // if we're parsing a collection, there will be a single // virtual token which does not derive from a concrete byte var ctx = this.context.type(); var len = ctx === context_1.TokenContextType.root ? this.length : this.length + 1; return this.pos < this.bufferOffset + len; }; /** * Return the current reading position in the buffer */ Tokeniser.prototype.offset = function () { return this.pos; }; /** * Get the current context */ Tokeniser.prototype.getContext = function () { return this.context; }; /** * Get the current token */ Tokeniser.prototype.getToken = function () { return this.token; }; /** * Read the next token and return it * * @return the next token or null if no more tokens are remaining in the buffer * @throws an {@link InvalidDataError} if there was an error parsing the next token */ // eslint-disable-next-line complexity Tokeniser.prototype.nextToken = function () { if (!this.hasRemaining()) { return null; } var ctx = this.context.type(); var previousPos = this.pos; if (ctx !== context_1.TokenContextType.root && !this.context.hasRemaining()) { switch (ctx) { case context_1.TokenContextType.object: this.type = consts_1.tokens.MAP_END; break; case context_1.TokenContextType.array: this.type = consts_1.tokens.ARRAY_END; break; case context_1.TokenContextType.string: this.type = consts_1.tokens.STRING_END; break; } this.context.pop(); return { pos: this.pos, type: this.type, getBuffer: function () { return EMPTY_BUFFER; } }; } else { this.context.next(); } // parse next header var header = this.readHeader(); var value; switch (header.type) { case consts_1.types.INT: case consts_1.types.UINT: case consts_1.types.FLOAT: case consts_1.types.SIMPLE: this.type = consts_1.tokens.VALUE; value = this.readValue(header); break; case consts_1.types.BYTES: case consts_1.types.STRING: // handle indefinite length strings: https://tools.ietf.org/html/rfc7049#section-2.2.2 if (header.raw === consts_1.additional.BREAK) { this.context.push(context_1.TokenContextType.string, -1); this.type = consts_1.tokens.STRING_START; } else { this.type = consts_1.tokens.VALUE; value = this.readValue(header); } break; case consts_1.types.ARRAY: this.context.push(context_1.TokenContextType.array, this.readCollectionLength(header)); this.type = consts_1.tokens.ARRAY_START; break; case consts_1.types.MAP: var len = this.readCollectionLength(header); // length specifies how many entries; we need to count keys/values separately if (len >= 0) { len = len * 2; } this.context.push(context_1.TokenContextType.object, len); this.type = consts_1.tokens.MAP_START; break; case consts_1.types.SEMANTIC: return this.nextToken(); default: throw new errors_1.InvalidDataError('Unknown CBOR header type: ' + header.type); } // break out of existing context if we encounter a break value if (value instanceof Error) { if (this.context.acceptsBreakMarker()) { // by breaking the current context and then recursing, we synthesise the same conditions as if the // collection had naturally exhausted its length this.context.break(); return this.nextToken(); } else { throw new errors_1.InvalidDataError('Unexpected break flag outside of indefinite-length context'); } } var data = this.data; this.token = { pos: previousPos, type: this.type, value: value, header: header, length: this.pos, // non-arrow function // `this` refers to the token's properties getBuffer: function () { return data.subarray(this.pos, this.length); } }; return this.token; }; /** * Read a value from the CBOR buffer * * @param header the type information from the vallue's header * @return the value that was read * @throws an {@link InvalidDataError} if the value type was not recognised or the * header value could not be read */ Tokeniser.prototype.readValue = function (header) { switch (header.type) { case consts_1.types.UINT: return this.readHeaderValue(header); case consts_1.types.INT: return this.readHeaderValue(header, true); case consts_1.types.BYTES: return this.readBuffer(this.readHeaderValue(header)); case consts_1.types.STRING: return uint8array_1.uint8toUtf8(this.readBuffer(this.readHeaderValue(header))); case consts_1.types.SIMPLE: return this.readSimpleValue(header.raw); } throw new errors_1.InvalidDataError('Unrecognised value type (' + header.type + ')'); }; /** * Read a primitive value * * @param type the type of the value to read * @return the value that was read. A break is signalled by an Error * being returned (not thrown). */ Tokeniser.prototype.readSimpleValue = function (type) { switch (type) { case consts_1.additional.TRUE: return true; case consts_1.additional.FALSE: return false; case consts_1.additional.NULL: return null; case consts_1.additional.BREAK: return BREAK_FLAG; case consts_1.additional.HALF_PRECISION: return this.readFloat16(); case consts_1.additional.SINGLE_PRECISION: return this.readFloat32(); case consts_1.additional.DOUBLE_PRECISION: return this.readFloat64(); default: return undefined; } }; /** * Read a single byte from the buffer * * @return the byte read from the buffer * @throws an {@link InvalidDataError} is there are no more bytes to read */ Tokeniser.prototype.readByte = function () { if (this.pos < this.data.length) { return this.data[this.pos++]; } else { throw new errors_1.InvalidDataError('Exhausted token stream'); } }; /** * Read a number of bytes and return them as a buffer * * @return the byte read from the buffer */ Tokeniser.prototype.readBuffer = function (len) { return this.data.subarray(this.pos, this.pos += len); }; /** * Read a 16 bit unsigned integer from the buffer * * @return the value read from the buffer */ Tokeniser.prototype.readUint16 = function () { var res = uint8array_1.readUInt16BE(this.data, this.pos); this.pos += 2; return res; }; /** * Read a 32 bit unsigned integer from the buffer * * @return the value read from the buffer */ Tokeniser.prototype.readUint32 = function () { var res = uint8array_1.readUInt32BE(this.data, this.pos); this.pos += 4; return res; }; /** * Read a 64 bit unsigned integer from the buffer * * @return the value read from the buffer. If it is possible to represent * the integer using the primitive `number` type, a `number` is returned, * otherwise an `Int64`. */ Tokeniser.prototype.readUint64 = function (signed) { var buf = Uint8Array.from(this.readBuffer(8)); if (signed) { for (var j = 0; j < 8; ++j) { /* eslint-disable-next-line no-bitwise */ buf[j] = ~buf[j]; } } var i = new int64_impl_1.Int64Impl(buf); var n = i.toNumber(); // if addressable as a native 53bit integer, return as a Number if (n <= MAX_SAFE_INTEGER && n >= MIN_SAFE_INTEGER) { return n; } return i; }; /** * Read a 16 bit float from the buffer * * @return the value read from the buffer * * See: http://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript */ Tokeniser.prototype.readFloat16 = function () { var h = this.readUint16(); /* eslint-disable-next-line no-bitwise */ var f = (h & 0x03ff); /* eslint-disable-next-line no-bitwise */ var s = (h & 0x8000) >> 15; /* eslint-disable-next-line no-bitwise */ var e = (h & 0x7c00) >> 10; var sign = s ? -1 : 1; if (e === 0) { return sign * Math.pow(2, -14) * (f / Math.pow(2, 10)); } else if (e === 0x1f) { return f ? NaN : sign * Infinity; } return sign * Math.pow(2, e - 15) * (1 + (f / Math.pow(2, 10))); }; /** * Read a 32 bit float from the buffer * * @return the value read from the buffer */ Tokeniser.prototype.readFloat32 = function () { var res = uint8array_1.readFloatBE(this.data, this.pos); this.pos += 4; return res; }; /** * Read a 64 bit float from the buffer * * @return the value read from the buffer */ Tokeniser.prototype.readFloat64 = function () { var res = uint8array_1.readDoubleBE(this.data, this.pos); this.pos += 8; return res; }; /** * Read a value header from the buffer * * @return the header information read from the buffer * @throws an {@link InvalidDataError} is there are no more bytes to read */ Tokeniser.prototype.readHeader = function () { var header = this.readByte(); return { /* eslint-disable-next-line no-bitwise */ type: header >> 5, /* eslint-disable-next-line no-bitwise */ raw: header & 0x1F }; }; /** * Read a header value * * @param header the header type information * @param signed a flag indicating if the value is negative * @return the header value * @throws an {@link InvalidDataError} is there are no more bytes to read */ Tokeniser.prototype.readHeaderValue = function (header, signed) { if (signed === void 0) { signed = false; } var low = header.raw; var sign = function (value) { return signed ? -1 - value : value; }; if (low < 24) { return sign(low); } switch (low) { case consts_1.additional.SIMPLE: return sign(this.readByte()); case consts_1.additional.HALF_PRECISION: return sign(this.readUint16()); case consts_1.additional.SINGLE_PRECISION: return sign(this.readUint32()); case consts_1.additional.DOUBLE_PRECISION: return this.readUint64(signed); case consts_1.additional.BREAK: return BREAK_FLAG; default: throw new errors_1.InvalidDataError('Unexpected value ' + low); } }; /** * Read the length of a collection * * @param header the CBOR header * @return the number of elements in the collection or -1 for an empty collection * @throws an {@link InvalidDataError} is the header value could not be read */ Tokeniser.prototype.readCollectionLength = function (header) { var result = this.readHeaderValue(header); return (result === BREAK_FLAG) ? -1 : result; }; return Tokeniser; }()); exports.Tokeniser = Tokeniser;