diffusion
Version:
Diffusion JavaScript client
307 lines (252 loc) • 8.27 kB
JavaScript
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;