diffusion
Version:
Diffusion JavaScript client
400 lines (399 loc) • 14.4 kB
JavaScript
"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;