You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
7.1 KiB

'use strict'
var Buffer = require('safe-buffer').Buffer
var bl = require('bl')
var TOLERANCE = 0.1
module.exports = function buildEncode (encodingTypes, forceFloat64, compatibilityMode) {
function encode (obj, avoidSlice) {
var buf,
len
if (obj === undefined) {
throw new Error('undefined is not encodable in msgpack!')
} else if (obj === null) {
buf = Buffer.allocUnsafe(1)
buf[0] = 0xc0
} else if (obj === true) {
buf = Buffer.allocUnsafe(1)
buf[0] = 0xc3
} else if (obj === false) {
buf = Buffer.allocUnsafe(1)
buf[0] = 0xc2
} else if (typeof obj === 'string') {
len = Buffer.byteLength(obj)
if (len < 32) {
buf = Buffer.allocUnsafe(1 + len)
buf[0] = 0xa0 | len
if (len > 0) {
buf.write(obj, 1)
}
} else if (len <= 0xff && !compatibilityMode) {
// str8, but only when not in compatibility mode
buf = Buffer.allocUnsafe(2 + len)
buf[0] = 0xd9
buf[1] = len
buf.write(obj, 2)
} else if (len <= 0xffff) {
buf = Buffer.allocUnsafe(3 + len)
buf[0] = 0xda
buf.writeUInt16BE(len, 1)
buf.write(obj, 3)
} else {
buf = Buffer.allocUnsafe(5 + len)
buf[0] = 0xdb
buf.writeUInt32BE(len, 1)
buf.write(obj, 5)
}
} else if (obj && (obj.readUInt32LE || obj instanceof Uint8Array)) {
if (obj instanceof Uint8Array) {
obj = Buffer.from(obj)
}
// weird hack to support Buffer
// and Buffer-like objects
if (obj.length <= 0xff) {
buf = Buffer.allocUnsafe(2)
buf[0] = 0xc4
buf[1] = obj.length
} else if (obj.length <= 0xffff) {
buf = Buffer.allocUnsafe(3)
buf[0] = 0xc5
buf.writeUInt16BE(obj.length, 1)
} else {
buf = Buffer.allocUnsafe(5)
buf[0] = 0xc6
buf.writeUInt32BE(obj.length, 1)
}
buf = bl([buf, obj])
} else if (Array.isArray(obj)) {
if (obj.length < 16) {
buf = Buffer.allocUnsafe(1)
buf[0] = 0x90 | obj.length
} else if (obj.length < 65536) {
buf = Buffer.allocUnsafe(3)
buf[0] = 0xdc
buf.writeUInt16BE(obj.length, 1)
} else {
buf = Buffer.allocUnsafe(5)
buf[0] = 0xdd
buf.writeUInt32BE(obj.length, 1)
}
buf = obj.reduce(function (acc, obj) {
acc.append(encode(obj, true))
return acc
}, bl().append(buf))
} else if (typeof obj === 'object') {
buf = encodeExt(obj) || encodeObject(obj)
} else if (typeof obj === 'number') {
if (isFloat(obj)) {
return encodeFloat(obj, forceFloat64)
} else if (obj >= 0) {
if (obj < 128) {
buf = Buffer.allocUnsafe(1)
buf[0] = obj
} else if (obj < 256) {
buf = Buffer.allocUnsafe(2)
buf[0] = 0xcc
buf[1] = obj
} else if (obj < 65536) {
buf = Buffer.allocUnsafe(3)
buf[0] = 0xcd
buf.writeUInt16BE(obj, 1)
} else if (obj <= 0xffffffff) {
buf = Buffer.allocUnsafe(5)
buf[0] = 0xce
buf.writeUInt32BE(obj, 1)
} else if (obj <= 9007199254740991) {
buf = Buffer.allocUnsafe(9)
buf[0] = 0xcf
write64BitUint(buf, obj)
} else {
return encodeFloat(obj, true)
}
} else {
if (obj >= -32) {
buf = Buffer.allocUnsafe(1)
buf[0] = 0x100 + obj
} else if (obj >= -128) {
buf = Buffer.allocUnsafe(2)
buf[0] = 0xd0
buf.writeInt8(obj, 1)
} else if (obj >= -32768) {
buf = Buffer.allocUnsafe(3)
buf[0] = 0xd1
buf.writeInt16BE(obj, 1)
} else if (obj > -214748365) {
buf = Buffer.allocUnsafe(5)
buf[0] = 0xd2
buf.writeInt32BE(obj, 1)
} else if (obj >= -9007199254740991) {
buf = Buffer.allocUnsafe(9)
buf[0] = 0xd3
write64BitInt(buf, 1, obj)
} else {
return encodeFloat(obj, true)
}
}
}
if (!buf) {
throw new Error('not implemented yet')
}
if (avoidSlice) {
return buf
} else {
return buf.slice()
}
}
function encodeExt (obj) {
var i
var encoded
var length = -1
var headers = []
for (i = 0; i < encodingTypes.length; i++) {
if (encodingTypes[i].check(obj)) {
encoded = encodingTypes[i].encode(obj)
break
}
}
if (!encoded) {
return null
}
// we subtract 1 because the length does not
// include the type
length = encoded.length - 1
if (length === 1) {
headers.push(0xd4)
} else if (length === 2) {
headers.push(0xd5)
} else if (length === 4) {
headers.push(0xd6)
} else if (length === 8) {
headers.push(0xd7)
} else if (length === 16) {
headers.push(0xd8)
} else if (length < 256) {
headers.push(0xc7)
headers.push(length)
} else if (length < 0x10000) {
headers.push(0xc8)
headers.push(length >> 8)
headers.push(length & 0x00ff)
} else {
headers.push(0xc9)
headers.push(length >> 24)
headers.push((length >> 16) & 0x000000ff)
headers.push((length >> 8) & 0x000000ff)
headers.push(length & 0x000000ff)
}
return bl().append(Buffer.from(headers)).append(encoded)
}
function encodeObject (obj) {
var acc = []
var length = 0
var key
var header
for (key in obj) {
if (obj.hasOwnProperty(key) &&
obj[key] !== undefined &&
typeof obj[key] !== 'function') {
++length
acc.push(encode(key, true))
acc.push(encode(obj[key], true))
}
}
if (length < 16) {
header = Buffer.allocUnsafe(1)
header[0] = 0x80 | length
} else {
header = Buffer.allocUnsafe(3)
header[0] = 0xde
header.writeUInt16BE(length, 1)
}
acc.unshift(header)
var result = acc.reduce(function (list, buf) {
return list.append(buf)
}, bl())
return result
}
return encode
}
function write64BitUint (buf, obj) {
// Write long byte by byte, in big-endian order
for (var currByte = 7; currByte >= 0; currByte--) {
buf[currByte + 1] = (obj & 0xff)
obj = obj / 256
}
}
function write64BitInt (buf, offset, num) {
var negate = num < 0
if (negate) {
num = Math.abs(num)
}
var lo = num % 4294967296
var hi = num / 4294967296
buf.writeUInt32BE(Math.floor(hi), offset + 0)
buf.writeUInt32BE(lo, offset + 4)
if (negate) {
var carry = 1
for (var i = offset + 7; i >= offset; i--) {
var v = (buf[i] ^ 0xff) + carry
buf[i] = v & 0xff
carry = v >> 8
}
}
}
function isFloat (n) {
return n !== Math.floor(n)
}
function encodeFloat (obj, forceFloat64) {
var buf
buf = Buffer.allocUnsafe(5)
buf[0] = 0xca
buf.writeFloatBE(obj, 1)
// FIXME is there a way to check if a
// value fits in a float?
if (forceFloat64 || Math.abs(obj - buf.readFloatBE(1)) > TOLERANCE) {
buf = Buffer.allocUnsafe(9)
buf[0] = 0xcb
buf.writeDoubleBE(obj, 1)
}
return buf
}