UNPKG

@stricahq/cbors

Version:

cbor encoder and decoder with annotation

435 lines (434 loc) 17 kB
"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;