UNPKG

diffusion

Version:

Diffusion JavaScript client

307 lines (252 loc) 8.27 kB
var Int64Impl = require('data/primitive/int64-impl'); var Context = require('./context'); var consts = require('./consts'); var additional = consts.additional, tokens = consts.tokens, types = consts.types; // Unique object for representing break codes var BREAK_FLAG = new Error(); // Used for buffer value of virtual tokens var EMPTY_BUFFER = new Buffer([]); function Tokeniser(data, offset, length) { length = length === undefined ? data.length : length; offset = offset || 0; var context = new Context(); var token; var type; var self = this; var pos = offset; this.reset = function () { context = new Context(); token = undefined; type = undefined; pos = offset; }; this.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 = context.type(); var len = ctx === 'root' ? length : length + 1; return pos < offset + len; }; this.offset = function () { return pos; }; this.getContext = function () { return context; }; this.getToken = function () { return token; }; /*eslint complexity: ["error", 20]*/ this.nextToken = function () { if (!self.hasRemaining()) { return null; } var ctx = context.type(); var previousPos = pos; if (ctx !== 'root' && !context.hasRemaining()) { switch (ctx) { case 'object' : type = tokens.MAP_END; break; case 'array' : type = tokens.ARRAY_END; break; case 'string' : type = tokens.STRING_END; break; } context.pop(); return { pos: pos, type: type, getBuffer: function () { return EMPTY_BUFFER; } }; } else { context.next(); } // Parse next header var header = readHeader(); var value; switch (header.type) { case types.INT : case types.UINT : case types.FLOAT : case types.SIMPLE : type = tokens.VALUE; value = readValue(header); break; case types.BYTES : case types.STRING : // Handle indefinite length strings: https://tools.ietf.org/html/rfc7049#section-2.2.2 if (header.raw === additional.BREAK) { context.push('string', -1); type = tokens.STRING_START; } else { type = tokens.VALUE; value = readValue(header); } break; case types.ARRAY : context.push('array', readCollectionLength(header)); type = tokens.ARRAY_START; break; case types.MAP : var len = readCollectionLength(header); // Length specifies how many entries; we need to count keys/values separately if (len >= 0) { len = len * 2; } context.push('object', len); type = tokens.MAP_START; break; case types.SEMANTIC : return self.nextToken(); default : throw new Error('Unknown CBOR header type: ' + header.type); } // Break out of existing context if we encounter a break value if (value === BREAK_FLAG) { if (context.acceptsBreakMarker()) { // By breaking the current context and then recursing, we synthesise the same conditions as if the // collection had naturally exhausted its length context.break(); return self.nextToken(); } else { throw new Error("Unexpected break flag outside of indefinite-length context"); } } token = { pos: previousPos, type: type, value: value, header: header, length: pos, getBuffer: function () { return data.slice(this.pos, this.length); } }; return token; }; function readValue(header) { switch (header.type) { case types.UINT : return readHeaderValue(header); case types.INT : return readHeaderValue(header, true); case types.BYTES : return readBuffer(readHeaderValue(header)); case types.STRING : return readBuffer(readHeaderValue(header)).toString('utf-8'); case types.SIMPLE : return readSimpleValue(header.raw); } } function readSimpleValue(type) { switch (type) { case additional.TRUE : return true; case additional.FALSE : return false; case additional.NULL : return null; case additional.BREAK : return BREAK_FLAG; case additional.HALF_PRECISION : return readFloat16(); case additional.SINGLE_PRECISION : return readFloat32(); case additional.DOUBLE_PRECISION : return readFloat64(); } } function readByte() { if (pos < data.length) { return data[pos++]; } else { throw new Error('Exhausted token stream'); } } function readBuffer(len) { return data.slice(pos, pos += len); } function readUint16() { var res = data.readUInt16BE(pos); pos += 2; return res; } function readUint32() { var res = data.readUInt32BE(pos); pos += 4; return res; } function readUint64(signed) { var i = new Int64Impl(readBuffer(8)); var n = i.toNumber(); // If addressable as a native 53bit integer, return as a Number if (n <= 9007199254740991 && n >= -9007199254740991) { return signed ? -1 - n : n; } return i; } // See: http://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript function readFloat16() { var h = readUint16(); var f = (h & 0x03ff); var s = (h & 0x8000) >> 15; 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))); } function readFloat32() { var res = data.readFloatBE(pos); pos += 4; return res; } function readFloat64() { var res = data.readDoubleBE(pos); pos += 8; return res; } function readHeader() { var header = readByte(); return { type : header >> 5, raw : header & 0x1F }; } function readHeaderValue(header, signed) { var low = header.raw; var res; if (low < 24) { res = low; } else if (low === additional.SIMPLE) { res = readByte(); } else if (low === additional.HALF_PRECISION) { res = readUint16(); } else if (low === additional.SINGLE_PRECISION) { res = readUint32(); } else if (low === additional.DOUBLE_PRECISION) { return readUint64(signed); } else if (low === additional.BREAK) { return BREAK_FLAG; } return signed ? -1 - res : res; } function readCollectionLength(header) { var l = readHeaderValue(header); if (l === BREAK_FLAG) { return -1; } return l; } } module.exports = Tokeniser;