Initial commit
This commit is contained in:
738
node_modules/cbor/lib/encoder.js
generated
vendored
Normal file
738
node_modules/cbor/lib/encoder.js
generated
vendored
Normal file
@@ -0,0 +1,738 @@
|
||||
'use strict'
|
||||
|
||||
const stream = require('stream')
|
||||
const url = require('url')
|
||||
const bignumber = require('bignumber.js').BigNumber
|
||||
const NoFilter = require('nofilter')
|
||||
const Tagged = require('./tagged')
|
||||
const Simple = require('./simple')
|
||||
const utils = require('./utils')
|
||||
|
||||
const constants = require('./constants')
|
||||
const MT = constants.MT
|
||||
const NUMBYTES = constants.NUMBYTES
|
||||
const SHIFT32 = constants.SHIFT32
|
||||
const SYMS = constants.SYMS
|
||||
const TAG = constants.TAG
|
||||
const HALF = (constants.MT.SIMPLE_FLOAT << 5) | constants.NUMBYTES.TWO
|
||||
const FLOAT = (constants.MT.SIMPLE_FLOAT << 5) | constants.NUMBYTES.FOUR
|
||||
const DOUBLE = (constants.MT.SIMPLE_FLOAT << 5) | constants.NUMBYTES.EIGHT
|
||||
const TRUE = (constants.MT.SIMPLE_FLOAT << 5) | constants.SIMPLE.TRUE
|
||||
const FALSE = (constants.MT.SIMPLE_FLOAT << 5) | constants.SIMPLE.FALSE
|
||||
const UNDEFINED = (constants.MT.SIMPLE_FLOAT << 5) | constants.SIMPLE.UNDEFINED
|
||||
const NULL = (constants.MT.SIMPLE_FLOAT << 5) | constants.SIMPLE.NULL
|
||||
|
||||
const BI = utils.bigIntize(constants.BI)
|
||||
const BN = constants.BN
|
||||
const BUF_NAN = Buffer.from('f97e00', 'hex')
|
||||
const BUF_INF_NEG = Buffer.from('f9fc00', 'hex')
|
||||
const BUF_INF_POS = Buffer.from('f97c00', 'hex')
|
||||
const BUF_NEG_ZERO = Buffer.from('f98000', 'hex')
|
||||
const LOOP_DETECT = Symbol('CBOR_LOOP_DETECT')
|
||||
|
||||
/**
|
||||
* Transform JavaScript values into CBOR bytes. The `Writable` side of
|
||||
* the stream is in object mode.
|
||||
*
|
||||
* @extends {stream.Transform}
|
||||
*/
|
||||
class Encoder extends stream.Transform {
|
||||
|
||||
/**
|
||||
* Creates an instance of Encoder.
|
||||
*
|
||||
* @param {Object} [options={}] - options for the encoder
|
||||
* @param {any[]} [options.genTypes=[]] - array of pairs of `type`,
|
||||
* `function(Encoder)` for semantic types to be encoded. Not needed
|
||||
* for Array, Date, Buffer, Map, RegExp, Set, Url, or bignumber.
|
||||
* @param {boolean} [options.canonical=false] - should the output be
|
||||
* canonicalized
|
||||
* @param {boolean|Symbol} [options.detectLoops=false] - should object loops
|
||||
* be detected? This will currently modify the encoded object graph
|
||||
* by adding a Symbol property to each object. If this bothers you,
|
||||
* call `removeLoopDetectors` on the encoded object when done. Do not
|
||||
* encode the same object twice on the same encoder, without calling
|
||||
* `removeLoopDetectors` in between.
|
||||
* @param {("number"|"float"|"int"|"string")} [options.dateType="number"] -
|
||||
* how should dates be encoded? "number" means float or int, if no
|
||||
* fractional seconds.
|
||||
* @param {any} [options.encodeUndefined=undefined] - How should an
|
||||
* "undefined" in the input be encoded. By default, just encode a CBOR
|
||||
* undefined. If this is a buffer, use those bytes without re-encoding
|
||||
* them. If this is a function, the function will be called (which is
|
||||
* a good time to throw an exception, if that's what you want), and the
|
||||
* return value will be used according to these rules. Anything
|
||||
* else will be encoded as CBOR.
|
||||
* @param {boolean} [options.disallowUndefinedKeys=false] - Should "undefined"
|
||||
* be disallowed as a key in a Map that is serialized? If this is true,
|
||||
* encode(new Map([[undefined, 1]])) will throw an exception. Note that
|
||||
* it is impossible to get a key of undefined in a normal JS object.
|
||||
* @param {boolean} [options.collapseBigIntegers=false] - Should integers
|
||||
* that come in as BigNumber integers and ECMAscript bigint's be encoded
|
||||
* as normal CBOR integers if they fit, discarding type information?
|
||||
*/
|
||||
constructor(options) {
|
||||
const opts = Object.assign({}, options, {
|
||||
readableObjectMode: false,
|
||||
writableObjectMode: true
|
||||
})
|
||||
super(opts)
|
||||
|
||||
this.canonical = opts.canonical
|
||||
this.encodeUndefined = opts.encodeUndefined
|
||||
this.disallowUndefinedKeys = !!opts.disallowUndefinedKeys
|
||||
this.dateType = (opts.dateType != null) ?
|
||||
opts.dateType.toLowerCase() : 'number'
|
||||
this.collapseBigIntegers = !!opts.collapseBigIntegers
|
||||
|
||||
// new Symbol for each instance. Note: means we can't re-use the same
|
||||
// encoder and encoded object
|
||||
if (typeof(opts.detectLoops) === 'symbol') {
|
||||
this.detectLoops = opts.detectLoops
|
||||
} else {
|
||||
this.detectLoops = !!opts.detectLoops ? Symbol('CBOR_DETECT') : null
|
||||
}
|
||||
|
||||
this.semanticTypes = [
|
||||
Array, this._pushArray,
|
||||
Date, this._pushDate,
|
||||
Buffer, this._pushBuffer,
|
||||
Map, this._pushMap,
|
||||
NoFilter, this._pushNoFilter,
|
||||
RegExp, this._pushRegexp,
|
||||
Set, this._pushSet,
|
||||
bignumber, this._pushBigNumber,
|
||||
ArrayBuffer, this._pushUint8Array,
|
||||
Uint8ClampedArray, this._pushUint8Array,
|
||||
Uint8Array, this._pushUint8Array,
|
||||
Uint16Array, this._pushArray,
|
||||
Uint32Array, this._pushArray,
|
||||
Int8Array, this._pushArray,
|
||||
Int16Array, this._pushArray,
|
||||
Int32Array, this._pushArray,
|
||||
Float32Array, this._pushFloat32Array,
|
||||
Float64Array, this._pushFloat64Array
|
||||
]
|
||||
|
||||
// tsc doesn't know about old Url
|
||||
if (url['Url']) {
|
||||
this.semanticTypes.push(url['Url'], this._pushUrl)
|
||||
}
|
||||
if (url['URL']) {
|
||||
this.semanticTypes.push(url['URL'], this._pushURL)
|
||||
}
|
||||
|
||||
const addTypes = opts.genTypes || []
|
||||
for (let i = 0, len = addTypes.length; i < len; i += 2) {
|
||||
this.addSemanticType(addTypes[i], addTypes[i + 1])
|
||||
}
|
||||
}
|
||||
|
||||
_transform(fresh, encoding, cb) {
|
||||
const ret = this.pushAny(fresh)
|
||||
// Old transformers might not return bool. undefined !== false
|
||||
return cb((ret === false) ? new Error('Push Error') : undefined)
|
||||
}
|
||||
|
||||
_flush(cb) {
|
||||
return cb()
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback encodeFunction
|
||||
* @param {Encoder} encoder - the encoder to serialize into. Call "write"
|
||||
* on the encoder as needed.
|
||||
* @return {bool} - true on success, else false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add an encoding function to the list of supported semantic types. This is
|
||||
* useful for objects for which you can't add an encodeCBOR method
|
||||
*
|
||||
* @param {any} type
|
||||
* @param {any} fun
|
||||
* @returns {encodeFunction}
|
||||
*/
|
||||
addSemanticType(type, fun) {
|
||||
for (let i = 0, len = this.semanticTypes.length; i < len; i += 2) {
|
||||
const typ = this.semanticTypes[i]
|
||||
if (typ === type) {
|
||||
const old = this.semanticTypes[i + 1]
|
||||
this.semanticTypes[i + 1] = fun
|
||||
return old
|
||||
}
|
||||
}
|
||||
this.semanticTypes.push(type, fun)
|
||||
return null
|
||||
}
|
||||
|
||||
_pushUInt8(val) {
|
||||
const b = Buffer.allocUnsafe(1)
|
||||
b.writeUInt8(val, 0)
|
||||
return this.push(b)
|
||||
}
|
||||
|
||||
_pushUInt16BE(val) {
|
||||
const b = Buffer.allocUnsafe(2)
|
||||
b.writeUInt16BE(val, 0)
|
||||
return this.push(b)
|
||||
}
|
||||
|
||||
_pushUInt32BE(val) {
|
||||
const b = Buffer.allocUnsafe(4)
|
||||
b.writeUInt32BE(val, 0)
|
||||
return this.push(b)
|
||||
}
|
||||
|
||||
_pushFloatBE(val) {
|
||||
const b = Buffer.allocUnsafe(4)
|
||||
b.writeFloatBE(val, 0)
|
||||
return this.push(b)
|
||||
}
|
||||
|
||||
_pushDoubleBE(val) {
|
||||
const b = Buffer.allocUnsafe(8)
|
||||
b.writeDoubleBE(val, 0)
|
||||
return this.push(b)
|
||||
}
|
||||
|
||||
_pushNaN() {
|
||||
return this.push(BUF_NAN)
|
||||
}
|
||||
|
||||
_pushInfinity(obj) {
|
||||
const half = (obj < 0) ? BUF_INF_NEG : BUF_INF_POS
|
||||
return this.push(half)
|
||||
}
|
||||
|
||||
_pushFloat(obj) {
|
||||
if (this.canonical) {
|
||||
// TODO: is this enough slower to hide behind canonical?
|
||||
// It's certainly enough of a hack (see utils.parseHalf)
|
||||
|
||||
// From section 3.9:
|
||||
// If a protocol allows for IEEE floats, then additional canonicalization
|
||||
// rules might need to be added. One example rule might be to have all
|
||||
// floats start as a 64-bit float, then do a test conversion to a 32-bit
|
||||
// float; if the result is the same numeric value, use the shorter value
|
||||
// and repeat the process with a test conversion to a 16-bit float. (This
|
||||
// rule selects 16-bit float for positive and negative Infinity as well.)
|
||||
|
||||
// which seems pretty much backwards to me.
|
||||
const b2 = Buffer.allocUnsafe(2)
|
||||
if (utils.writeHalf(b2, obj)) {
|
||||
if (utils.parseHalf(b2) === obj) {
|
||||
return this._pushUInt8(HALF) && this.push(b2)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Math.fround(obj) === obj) {
|
||||
return this._pushUInt8(FLOAT) && this._pushFloatBE(obj)
|
||||
}
|
||||
|
||||
return this._pushUInt8(DOUBLE) && this._pushDoubleBE(obj)
|
||||
}
|
||||
|
||||
_pushInt(obj, mt, orig) {
|
||||
const m = mt << 5
|
||||
switch (false) {
|
||||
case !(obj < 24):
|
||||
return this._pushUInt8(m | obj)
|
||||
case !(obj <= 0xff):
|
||||
return this._pushUInt8(m | NUMBYTES.ONE) && this._pushUInt8(obj)
|
||||
case !(obj <= 0xffff):
|
||||
return this._pushUInt8(m | NUMBYTES.TWO) && this._pushUInt16BE(obj)
|
||||
case !(obj <= 0xffffffff):
|
||||
return this._pushUInt8(m | NUMBYTES.FOUR) && this._pushUInt32BE(obj)
|
||||
case !(obj <= Number.MAX_SAFE_INTEGER):
|
||||
return this._pushUInt8(m | NUMBYTES.EIGHT) &&
|
||||
this._pushUInt32BE(Math.floor(obj / SHIFT32)) &&
|
||||
this._pushUInt32BE(obj % SHIFT32)
|
||||
default:
|
||||
if (mt === MT.NEG_INT) {
|
||||
return this._pushFloat(orig)
|
||||
} else {
|
||||
return this._pushFloat(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pushIntNum(obj) {
|
||||
if (Object.is(obj, -0)) {
|
||||
return this.push(BUF_NEG_ZERO)
|
||||
}
|
||||
|
||||
if (obj < 0) {
|
||||
return this._pushInt(-obj - 1, MT.NEG_INT, obj)
|
||||
} else {
|
||||
return this._pushInt(obj, MT.POS_INT)
|
||||
}
|
||||
}
|
||||
|
||||
_pushNumber(obj) {
|
||||
switch (false) {
|
||||
case !isNaN(obj):
|
||||
return this._pushNaN()
|
||||
case isFinite(obj):
|
||||
return this._pushInfinity(obj)
|
||||
case Math.round(obj) !== obj:
|
||||
return this._pushIntNum(obj)
|
||||
default:
|
||||
return this._pushFloat(obj)
|
||||
}
|
||||
}
|
||||
|
||||
_pushString(obj) {
|
||||
const len = Buffer.byteLength(obj, 'utf8')
|
||||
return this._pushInt(len, MT.UTF8_STRING) && this.push(obj, 'utf8')
|
||||
}
|
||||
|
||||
_pushBoolean(obj) {
|
||||
return this._pushUInt8(obj ? TRUE : FALSE)
|
||||
}
|
||||
|
||||
_pushUndefined(obj) {
|
||||
switch (typeof this.encodeUndefined) {
|
||||
case 'undefined':
|
||||
return this._pushUInt8(UNDEFINED)
|
||||
case 'function':
|
||||
return this.pushAny(this.encodeUndefined.call(this, obj))
|
||||
case 'object':
|
||||
if (Buffer.isBuffer(this.encodeUndefined)) {
|
||||
return this.push(this.encodeUndefined)
|
||||
}
|
||||
}
|
||||
return this.pushAny(this.encodeUndefined)
|
||||
}
|
||||
|
||||
_pushNull(obj) {
|
||||
return this._pushUInt8(NULL)
|
||||
}
|
||||
|
||||
_pushArray(gen, obj) {
|
||||
const len = obj.length
|
||||
if (!gen._pushInt(len, MT.ARRAY)) {
|
||||
return false
|
||||
}
|
||||
for (let j = 0; j < len; j++) {
|
||||
if (!gen.pushAny(obj[j])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_pushTag(tag) {
|
||||
return this._pushInt(tag, MT.TAG)
|
||||
}
|
||||
|
||||
_pushDate(gen, obj) {
|
||||
switch (gen.dateType) {
|
||||
case 'string':
|
||||
return gen._pushTag(TAG.DATE_STRING) &&
|
||||
gen._pushString(obj.toISOString())
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return gen._pushTag(TAG.DATE_EPOCH) &&
|
||||
gen._pushIntNum(Math.round(obj / 1000))
|
||||
case 'float':
|
||||
// force float
|
||||
return gen._pushTag(TAG.DATE_EPOCH) &&
|
||||
gen._pushFloat(obj / 1000)
|
||||
case 'number':
|
||||
default:
|
||||
// if we happen to have an integral number of seconds,
|
||||
// use integer. Otherwise, use float.
|
||||
return gen._pushTag(TAG.DATE_EPOCH) &&
|
||||
gen.pushAny(obj / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
_pushBuffer(gen, obj) {
|
||||
return gen._pushInt(obj.length, MT.BYTE_STRING) && gen.push(obj)
|
||||
}
|
||||
|
||||
_pushNoFilter(gen, obj) {
|
||||
return gen._pushBuffer(gen, obj.slice())
|
||||
}
|
||||
|
||||
_pushRegexp(gen, obj) {
|
||||
return gen._pushTag(TAG.REGEXP) && gen.pushAny(obj.source)
|
||||
}
|
||||
|
||||
_pushSet(gen, obj) {
|
||||
if (!gen._pushInt(obj.size, MT.ARRAY)) {
|
||||
return false
|
||||
}
|
||||
for (const x of obj) {
|
||||
if (!gen.pushAny(x)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_pushUrl(gen, obj) {
|
||||
return gen._pushTag(TAG.URI) && gen.pushAny(obj.format())
|
||||
}
|
||||
|
||||
_pushURL(gen, obj) {
|
||||
return gen._pushTag(TAG.URI) && gen.pushAny(obj.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {bignumber} obj
|
||||
* @private
|
||||
*/
|
||||
_pushBigint(obj) {
|
||||
let m = MT.POS_INT
|
||||
let tag = TAG.POS_BIGINT
|
||||
|
||||
if (obj.isNegative()) {
|
||||
obj = obj.negated().minus(1)
|
||||
m = MT.NEG_INT
|
||||
tag = TAG.NEG_BIGINT
|
||||
}
|
||||
|
||||
if (this.collapseBigIntegers &&
|
||||
obj.lte(BN.MAXINT64)) {
|
||||
// special handiling for 64bits
|
||||
if (obj.lte(BN.MAXINT32)) {
|
||||
return this._pushInt(obj.toNumber(), m)
|
||||
}
|
||||
return this._pushUInt8((m << 5) | NUMBYTES.EIGHT) &&
|
||||
this._pushUInt32BE(obj.dividedToIntegerBy(BN.SHIFT32).toNumber()) &&
|
||||
this._pushUInt32BE(obj.mod(BN.SHIFT32).toNumber())
|
||||
}
|
||||
let str = obj.toString(16)
|
||||
if (str.length % 2) {
|
||||
str = '0' + str
|
||||
}
|
||||
const buf = Buffer.from(str, 'hex')
|
||||
return this._pushTag(tag) && this._pushBuffer(this, buf)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {bigint} obj
|
||||
* @private
|
||||
*/
|
||||
_pushJSBigint(obj) {
|
||||
let m = MT.POS_INT
|
||||
let tag = TAG.POS_BIGINT
|
||||
// BigInt doesn't have -0
|
||||
if (obj < 0) {
|
||||
obj = -obj + BI.MINUS_ONE
|
||||
m = MT.NEG_INT
|
||||
tag = TAG.NEG_BIGINT
|
||||
}
|
||||
|
||||
if (this.collapseBigIntegers &&
|
||||
(obj <= BI.MAXINT64)) {
|
||||
// special handiling for 64bits
|
||||
if (obj <= 0xffffffff) {
|
||||
return this._pushInt(Number(obj), m)
|
||||
}
|
||||
return this._pushUInt8((m << 5) | NUMBYTES.EIGHT) &&
|
||||
this._pushUInt32BE(Number(obj / BI.SHIFT32)) &&
|
||||
this._pushUInt32BE(Number(obj % BI.SHIFT32))
|
||||
}
|
||||
|
||||
let str = obj.toString(16)
|
||||
if (str.length % 2) {
|
||||
str = '0' + str
|
||||
}
|
||||
const buf = Buffer.from(str, 'hex')
|
||||
return this._pushTag(tag) && this._pushBuffer(this, buf)
|
||||
}
|
||||
|
||||
_pushBigNumber(gen, obj) {
|
||||
if (obj.isNaN()) {
|
||||
return gen._pushNaN()
|
||||
}
|
||||
if (!obj.isFinite()) {
|
||||
return gen._pushInfinity(obj.isNegative() ? -Infinity : Infinity)
|
||||
}
|
||||
if (obj.isInteger()) {
|
||||
return gen._pushBigint(obj)
|
||||
}
|
||||
if (!(gen._pushTag(TAG.DECIMAL_FRAC) &&
|
||||
gen._pushInt(2, MT.ARRAY))) {
|
||||
return false
|
||||
}
|
||||
|
||||
const dec = obj.decimalPlaces()
|
||||
const slide = obj.times(new bignumber(10).pow(dec))
|
||||
if (!gen._pushIntNum(-dec)) {
|
||||
return false
|
||||
}
|
||||
if (slide.abs().isLessThan(BN.MAXINT)) {
|
||||
return gen._pushIntNum(slide.toNumber())
|
||||
} else {
|
||||
return gen._pushBigint(slide)
|
||||
}
|
||||
}
|
||||
|
||||
_pushMap(gen, obj) {
|
||||
if (!gen._pushInt(obj.size, MT.MAP)) {
|
||||
return false
|
||||
}
|
||||
// memoizing the cbor only helps in certain cases, and hurts in most
|
||||
// others. Just avoid it.
|
||||
if (gen.canonical) {
|
||||
// keep the key/value pairs together, so we don't have to do odd
|
||||
// gets with object keys later
|
||||
const entries = [...obj.entries()]
|
||||
const enc = new Encoder(this) // TODO: fix genTypes
|
||||
const bs = new NoFilter({highWaterMark:this.readableHighWaterMark})
|
||||
enc.pipe(bs)
|
||||
entries.sort(([a], [b]) => {
|
||||
// a, b are the keys
|
||||
enc.pushAny(a)
|
||||
const a_cbor = bs.read()
|
||||
enc.pushAny(b)
|
||||
const b_cbor = bs.read()
|
||||
return a_cbor.compare(b_cbor)
|
||||
})
|
||||
for (const [k, v] of entries) {
|
||||
if (gen.disallowUndefinedKeys && (typeof k === 'undefined')) {
|
||||
throw new Error('Invalid Map key: undefined')
|
||||
}
|
||||
if (!(gen.pushAny(k) && gen.pushAny(v))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [k, v] of obj) {
|
||||
if (gen.disallowUndefinedKeys && (typeof k === 'undefined')) {
|
||||
throw new Error('Invalid Map key: undefined')
|
||||
}
|
||||
if (!(gen.pushAny(k) && gen.pushAny(v))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_pushUint8Array(gen, obj) {
|
||||
return gen._pushBuffer(gen, Buffer.from(obj))
|
||||
}
|
||||
|
||||
_pushFloat32Array(gen, obj) {
|
||||
const len = obj.length
|
||||
if (!gen._pushInt(len, MT.ARRAY)) {
|
||||
return false
|
||||
}
|
||||
for (let j = 0; j < len; j++) {
|
||||
if (!gen._pushUInt8(FLOAT) || !gen._pushFloatBE(obj[j])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_pushFloat64Array(gen, obj) {
|
||||
const len = obj.length
|
||||
if (!gen._pushInt(len, MT.ARRAY)) {
|
||||
return false
|
||||
}
|
||||
for (let j = 0; j < len; j++) {
|
||||
if (!gen._pushUInt8(DOUBLE) || !gen._pushDoubleBE(obj[j])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
removeLoopDetectors(obj) {
|
||||
if (!this.detectLoops || (typeof(obj) !== 'object') || !obj) {
|
||||
return false
|
||||
}
|
||||
const dl = obj[LOOP_DETECT]
|
||||
if (!dl || (dl !== this.detectLoops)) {
|
||||
// ironically, use loop marking to detect loops on removal as well
|
||||
return false
|
||||
}
|
||||
delete obj[LOOP_DETECT]
|
||||
if (Array.isArray(obj)) {
|
||||
for (const i of obj) {
|
||||
this.removeLoopDetectors(i)
|
||||
}
|
||||
} else {
|
||||
for (const k in obj) {
|
||||
this.removeLoopDetectors(obj[k])
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_pushObject(obj) {
|
||||
if (!obj) {
|
||||
return this._pushNull(obj)
|
||||
}
|
||||
if (this.detectLoops) {
|
||||
if (obj[LOOP_DETECT] === this.detectLoops) {
|
||||
throw new Error('Loop detected while CBOR encoding')
|
||||
} else {
|
||||
obj[LOOP_DETECT] = this.detectLoops
|
||||
}
|
||||
}
|
||||
const f = obj.encodeCBOR
|
||||
if (typeof f === 'function') {
|
||||
return f.call(obj, this)
|
||||
}
|
||||
for (let i = 0, len1 = this.semanticTypes.length; i < len1; i += 2) {
|
||||
const typ = this.semanticTypes[i]
|
||||
if (obj instanceof typ) {
|
||||
return this.semanticTypes[i + 1].call(obj, this, obj)
|
||||
}
|
||||
}
|
||||
const keys = Object.keys(obj)
|
||||
const cbor_keys = {}
|
||||
if (this.canonical) {
|
||||
// note: this can't be a normal sort, because 'b' needs to sort before
|
||||
// 'aa'
|
||||
keys.sort((a, b) => {
|
||||
// Always strings, so don't bother to pass options.
|
||||
// hold on to the cbor versions, since there's no need
|
||||
// to encode more than once
|
||||
const a_cbor = cbor_keys[a] || (cbor_keys[a] = Encoder.encode(a))
|
||||
const b_cbor = cbor_keys[b] || (cbor_keys[b] = Encoder.encode(b))
|
||||
|
||||
return a_cbor.compare(b_cbor)
|
||||
})
|
||||
}
|
||||
if (!this._pushInt(keys.length, MT.MAP)) {
|
||||
return false
|
||||
}
|
||||
let ck
|
||||
for (let j = 0, len2 = keys.length; j < len2; j++) {
|
||||
const k = keys[j]
|
||||
if (this.canonical && ((ck = cbor_keys[k]))) {
|
||||
if (!this.push(ck)) { // already a Buffer
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (!this._pushString(k)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!this.pushAny(obj[k])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Push any supported type onto the encoded stream
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {boolean} true on success
|
||||
*/
|
||||
pushAny(obj) {
|
||||
switch (typeof obj) {
|
||||
case 'number':
|
||||
return this._pushNumber(obj)
|
||||
case 'bigint':
|
||||
return this._pushJSBigint(obj)
|
||||
case 'string':
|
||||
return this._pushString(obj)
|
||||
case 'boolean':
|
||||
return this._pushBoolean(obj)
|
||||
case 'undefined':
|
||||
return this._pushUndefined(obj)
|
||||
case 'object':
|
||||
return this._pushObject(obj)
|
||||
case 'symbol':
|
||||
switch (obj) {
|
||||
case SYMS.NULL:
|
||||
return this._pushNull(null)
|
||||
case SYMS.UNDEFINED:
|
||||
return this._pushUndefined(void 0)
|
||||
// TODO: Add pluggable support for other symbols
|
||||
default:
|
||||
throw new Error('Unknown symbol: ' + obj.toString())
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
'Unknown type: ' + typeof obj + ', ' +
|
||||
(!!obj ? obj.toString() : ''))
|
||||
}
|
||||
}
|
||||
|
||||
/* backwards-compat wrapper */
|
||||
_pushAny(obj) {
|
||||
// TODO: write deprecation warning
|
||||
return this.pushAny(obj)
|
||||
}
|
||||
|
||||
_encodeAll(objs) {
|
||||
const bs = new NoFilter({ highWaterMark:this.readableHighWaterMark })
|
||||
this.pipe(bs)
|
||||
for (const o of objs) {
|
||||
this.pushAny(o)
|
||||
}
|
||||
this.end()
|
||||
return bs.read()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode one or more JavaScript objects, and return a Buffer containing the
|
||||
* CBOR bytes.
|
||||
*
|
||||
* @param {...any} objs - the objects to encode
|
||||
* @returns {Buffer} - the encoded objects
|
||||
*/
|
||||
static encode(...objs) {
|
||||
return new Encoder()._encodeAll(objs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode one or more JavaScript objects canonically (slower!), and return
|
||||
* a Buffer containing the CBOR bytes.
|
||||
*
|
||||
* @param {...any} objs - the objects to encode
|
||||
* @returns {Buffer} - the encoded objects
|
||||
*/
|
||||
static encodeCanonical(...objs) {
|
||||
return new Encoder({canonical: true})._encodeAll(objs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode one JavaScript object using the given options.
|
||||
*
|
||||
* @static
|
||||
* @param {any} obj - the object to encode
|
||||
* @param {Object?} options - passed to the Encoder constructor
|
||||
* @returns {Buffer} - the encoded objects
|
||||
*/
|
||||
static encodeOne(obj, options) {
|
||||
return new Encoder(options)._encodeAll([obj])
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode one JavaScript object using the given options in a way that
|
||||
* is more resilient to objects being larger than the highWaterMark
|
||||
* number of bytes. As with the other static encode functions, this
|
||||
* will still use a large amount of memory. Use a stream-based approach
|
||||
* directly if you need to process large and complicated inputs.
|
||||
*
|
||||
* @param {any} obj - the object to encode
|
||||
* @param {Object?} options - passed to the Encoder constructor
|
||||
*/
|
||||
static encodeAsync(obj, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const bufs = []
|
||||
const enc = new Encoder(options)
|
||||
enc.on('data', buf => bufs.push(buf))
|
||||
enc.on('error', reject)
|
||||
enc.on('finish', () => resolve(Buffer.concat(bufs)))
|
||||
enc.pushAny(obj)
|
||||
enc.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Encoder
|
||||
Reference in New Issue
Block a user