@stricahq/cbors
Version:
cbor encoder and decoder with annotation
435 lines (434 loc) • 17 kB
JavaScript
"use strict";
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-bitwise */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const buffer_1 = require("buffer");
const stream = __importStar(require("stream"));
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const utils_1 = require("./utils");
const BufferList_1 = __importDefault(require("./BufferList"));
const SimpleValue_1 = __importDefault(require("./SimpleValue"));
const CborTag_1 = __importDefault(require("./CborTag"));
const CborArray_1 = __importDefault(require("./CborArray"));
const CborMap_1 = __importDefault(require("./CborMap"));
const bytesToBigNumber = (buf) => {
if (buf.length === 0) {
return new bignumber_js_1.default(0);
}
const hex = buf.toString('hex') || '0';
return new bignumber_js_1.default(hex, 16);
};
const readFloat16 = (value) => {
const sign = value & 0x8000;
let exponent = value & 0x7c00;
const fraction = value & 0x03ff;
if (exponent === 0x7c00)
exponent = 0xff << 10;
else if (exponent !== 0)
exponent += (127 - 15) << 10;
else if (fraction !== 0)
return (sign ? -1 : 1) * fraction * utils_1.POW_2_24;
const buf = buffer_1.Buffer.alloc(4);
buf.writeUInt32BE((sign << 16) | (exponent << 13) | (fraction << 13));
return buf.readFloatBE(0);
};
const isBreakPoint = (value) => {
if (value !== 0xff)
return false;
return true;
};
class Decoder extends stream.Transform {
constructor() {
super({
writableObjectMode: false,
readableObjectMode: true,
});
this.needed = null;
this.fresh = true;
this._parser = this.parse();
this.offset = 0;
this.usedBytes = [];
this.bl = new BufferList_1.default();
this.restart();
}
static decode(inputBytes) {
const decoder = new Decoder();
const bs = new BufferList_1.default();
bs.push(inputBytes);
const parser = decoder.parse();
let state = parser.next();
while (!state.done) {
const b = bs.read(state.value);
if (b == null || b.length !== state.value) {
throw new Error('Insufficient data');
}
state = parser.next(b);
}
if (bs.length > 0) {
throw new Error('Remaining Bytes');
}
return {
bytes: inputBytes,
value: state.value,
};
}
readUInt64(f, g, startByte) {
const bigNum = (0, utils_1.getBigNum)(f, g);
if (bignumber_js_1.default.isBigNumber(bigNum)) {
return (0, utils_1.addSpanBytesToObject)(bigNum, [startByte, this.offset]);
}
return bigNum;
}
updateTracker(bytes) {
this.usedBytes.push(bytes);
this.offset += bytes.length;
}
*readIndefiniteStringLength(majorType, startByte) {
let bytes = yield 1;
let length;
const number = bytes.readUInt8(0);
if (number === 0xff) {
length = -1;
}
else {
const ai = number & 0x1f;
// read length
const lengthReader = this.readLength(ai, startByte);
let lengthStatus = lengthReader.next();
while (!lengthStatus.done) {
bytes = yield lengthStatus.value;
lengthStatus = lengthReader.next(bytes);
}
length = lengthStatus.value;
//
if (length < 0 || number >> 5 !== majorType) {
throw new Error('Invalid indefinite length encoding');
}
}
return length;
}
*readLength(additionalInformation, startByte) {
if (additionalInformation < 24) {
return additionalInformation;
}
if (additionalInformation === 24) {
const bytes = yield 1;
return bytes.readUInt8(0);
}
if (additionalInformation === 25) {
const bytes = yield 2;
return bytes.readUInt16BE(0);
}
if (additionalInformation === 26) {
const bytes = yield 4;
return bytes.readUInt32BE(0);
}
if (additionalInformation === 27) {
const fBytes = yield 4;
const f = fBytes.readUInt32BE(0);
const gBytes = yield 4;
const g = gBytes.readUInt32BE(0);
return this.readUInt64(f, g, startByte);
}
if (additionalInformation === 31) {
return -1;
}
throw new Error('Invalid length encoding');
}
_transform(fresh, encoding, cb) {
this.bl.push(fresh);
while (this.bl.length >= this.needed) {
let ret = null;
let chunk;
if (this.needed === null) {
chunk = undefined;
}
else {
chunk = this.bl.read(this.needed);
}
try {
ret = this._parser.next(chunk);
}
catch (e) {
return cb(e);
}
if (this.needed) {
this.fresh = false;
}
if (ret.done) {
this.push({
bytes: this.usedBytes,
value: ret.value,
});
this.restart();
}
else {
this.needed = ret.value || Infinity;
}
}
return cb();
}
*parse(suppliedBytes) {
let startByte = this.offset;
let bytes;
if (suppliedBytes) {
bytes = suppliedBytes;
startByte -= suppliedBytes.length;
}
else {
bytes = yield 1;
this.updateTracker(bytes);
}
const value = bytes.readUInt8(0);
const majorType = value >> 5;
const additionalInformation = value & 0x1f;
let length;
if (majorType === 7) {
if (additionalInformation === 25) {
bytes = yield 2;
this.updateTracker(bytes);
const number = bytes.readUInt16BE(0);
return readFloat16(number);
}
if (additionalInformation === 26) {
bytes = yield 4;
this.updateTracker(bytes);
return bytes.readFloatBE(0);
}
if (additionalInformation === 27) {
bytes = yield 8;
this.updateTracker(bytes);
return bytes.readDoubleBE(0);
}
}
// read length
const lengthReader = this.readLength(additionalInformation, startByte);
let lengthStatus = lengthReader.next();
while (!lengthStatus.done) {
bytes = yield lengthStatus.value;
this.updateTracker(bytes);
lengthStatus = lengthReader.next(bytes);
}
length = lengthStatus.value;
//
if (length < 0 && (majorType < 2 || majorType > 6))
throw new Error('Invalid length');
switch (majorType) {
case 0:
return length;
case 1: {
if (length === Number.MAX_SAFE_INTEGER) {
const bigNum = new bignumber_js_1.default(-1).minus(new bignumber_js_1.default(Number.MAX_SAFE_INTEGER.toString(16), 16));
return (0, utils_1.addSpanBytesToObject)(bigNum, [startByte, this.offset]);
}
if (bignumber_js_1.default.isBigNumber(length)) {
const bigNum = new bignumber_js_1.default(-1).minus(length);
return (0, utils_1.addSpanBytesToObject)(bigNum, [startByte, this.offset]);
}
return -1 - length;
}
case 2: {
if (length < 0) {
const chunks = [];
{
// read indefinite length
const inDefLengthReader = this.readIndefiniteStringLength(majorType, startByte);
let inDefLengthStatus = inDefLengthReader.next();
while (!inDefLengthStatus.done) {
bytes = yield inDefLengthStatus.value;
this.updateTracker(bytes);
inDefLengthStatus = inDefLengthReader.next(bytes);
}
length = inDefLengthStatus.value;
//
}
while (length >= 0) {
bytes = yield length;
this.updateTracker(bytes);
chunks.push(bytes);
{
// read indefinite length
const inDefLengthReader = this.readIndefiniteStringLength(majorType, startByte);
let inDefLengthStatus = inDefLengthReader.next();
while (!inDefLengthStatus.done) {
bytes = yield inDefLengthStatus.value;
this.updateTracker(bytes);
inDefLengthStatus = inDefLengthReader.next(bytes);
}
length = inDefLengthStatus.value;
//
}
}
const buf = buffer_1.Buffer.concat(chunks);
return (0, utils_1.addSpanBytesToObject)(buf, [startByte, this.offset]);
}
bytes = yield length;
this.updateTracker(bytes);
return (0, utils_1.addSpanBytesToObject)(bytes, [startByte, this.offset]);
}
case 3: {
const stringBuf = [];
if (length < 0) {
{
// read indefinite length
const inDefLengthReader = this.readIndefiniteStringLength(majorType, startByte);
let inDefLengthStatus = inDefLengthReader.next();
while (!inDefLengthStatus.done) {
bytes = yield inDefLengthStatus.value;
this.updateTracker(bytes);
inDefLengthStatus = inDefLengthReader.next(bytes);
}
length = inDefLengthStatus.value;
//
}
while (length >= 0) {
bytes = yield length;
this.updateTracker(bytes);
stringBuf.push(bytes);
//
{
// read indefinite length
const inDefLengthReader = this.readIndefiniteStringLength(majorType, startByte);
let inDefLengthStatus = inDefLengthReader.next();
while (!inDefLengthStatus.done) {
bytes = yield inDefLengthStatus.value;
this.updateTracker(bytes);
inDefLengthStatus = inDefLengthReader.next(bytes);
}
length = inDefLengthStatus.value;
//
}
}
const string = (0, utils_1.utf8Decoder)(buffer_1.Buffer.concat(stringBuf));
return string;
}
bytes = yield length;
this.updateTracker(bytes);
const string = (0, utils_1.utf8Decoder)(bytes);
return string;
}
case 4: {
if (length < 0) {
const ary = new CborArray_1.default();
bytes = yield 1;
this.updateTracker(bytes);
let bp = bytes.readUInt8(0);
while (!isBreakPoint(bp)) {
ary.push(yield* this.parse(bytes));
bytes = yield 1;
this.updateTracker(bytes);
bp = bytes.readUInt8(0);
}
ary.setByteSpan([startByte, this.offset]);
return ary;
}
const ary = new CborArray_1.default();
for (let i = 0; i < length; i += 1) {
ary.push(yield* this.parse());
}
ary.setByteSpan([startByte, this.offset]);
return ary;
}
case 5: {
if (length < 0) {
const obj = new CborMap_1.default();
bytes = yield 1;
this.updateTracker(bytes);
let bp = bytes.readUInt8(0);
while (!isBreakPoint(bp)) {
const key = yield* this.parse(bytes);
const val = yield* this.parse();
obj.set(key, val);
bytes = yield 1;
this.updateTracker(bytes);
bp = bytes.readUInt8(0);
}
obj.setByteSpan([startByte, this.offset]);
return obj;
}
const obj = new CborMap_1.default();
for (let i = 0; i < length; i += 1) {
const key = yield* this.parse();
const val = yield* this.parse();
obj.set(key, val);
}
obj.setByteSpan([startByte, this.offset]);
return obj;
}
case 6: {
const tagNumber = length;
const taggedValue = yield* this.parse();
// Handle bignum tags (RFC 8949 / RFC 7049):
// Tag 2: positive bignum
// Tag 3: negative bignum (-1 - n)
if (typeof tagNumber === 'number' && (tagNumber === 2 || tagNumber === 3)) {
if (!buffer_1.Buffer.isBuffer(taggedValue)) {
// as per spec the tagged value for bignums must be a byte string
throw new Error('Invalid bignum encoding: expected byte string');
}
let big = bytesToBigNumber(taggedValue);
if (tagNumber === 3) {
// negative bignum = -1 - n = -(n + 1)
big = big.plus(1).negated();
}
// Track byte span from tag start to end of payload
return (0, utils_1.addSpanBytesToObject)(big, [startByte, this.offset]);
}
// generic tag handling for everything else
const tag = new CborTag_1.default(taggedValue, tagNumber);
tag.setByteSpan([startByte, this.offset]);
return tag;
}
case 7: {
switch (length) {
case 20:
return false;
case 21:
return true;
case 22:
return null;
case 23:
return undefined;
default:
return new SimpleValue_1.default(length);
}
}
default: {
throw new Error('Invalid CBOR encoding');
}
}
}
restart() {
this.needed = null;
this._parser = this.parse();
this.fresh = true;
this.offset = 0;
this.usedBytes = [];
}
_flush(cb) {
cb(this.fresh ? null : new Error('unexpected end of input'));
}
}
exports.default = Decoder;