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.

397 lines
11 KiB

var bl = require('bl')
var util = require('util')
function IncompleteBufferError (message) {
Error.call(this) // super constructor
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor) // super helper method to include stack trace in error object
}
this.name = this.constructor.name
this.message = message || 'unable to decode'
}
util.inherits(IncompleteBufferError, Error)
module.exports = function buildDecode (decodingTypes) {
return decode
function getSize (first) {
switch (first) {
case 0xc4:
return 2
case 0xc5:
return 3
case 0xc6:
return 5
case 0xc7:
return 3
case 0xc8:
return 4
case 0xc9:
return 6
case 0xca:
return 5
case 0xcb:
return 9
case 0xcc:
return 2
case 0xcd:
return 3
case 0xce:
return 5
case 0xcf:
return 9
case 0xd0:
return 2
case 0xd1:
return 3
case 0xd2:
return 5
case 0xd3:
return 9
case 0xd4:
return 3
case 0xd5:
return 4
case 0xd6:
return 6
case 0xd7:
return 10
case 0xd8:
return 18
case 0xd9:
return 2
case 0xda:
return 3
case 0xdb:
return 5
case 0xde:
return 3
default:
return -1
}
}
function hasMinBufferSize (first, length) {
var size = getSize(first)
if (size !== -1 && length < size) {
return false
} else {
return true
}
}
function isValidDataSize (dataLength, bufLength, headerLength) {
return bufLength >= headerLength + dataLength
}
function buildDecodeResult (value, bytesConsumed) {
return {
value: value,
bytesConsumed: bytesConsumed
}
}
function decode (buf) {
if (!(buf instanceof bl)) {
buf = bl().append(buf)
}
var result = tryDecode(buf)
if (result) {
buf.consume(result.bytesConsumed)
return result.value
} else {
throw new IncompleteBufferError()
}
}
function tryDecode (buf, offset) {
offset = offset === undefined ? 0 : offset
var bufLength = buf.length - offset
if (bufLength <= 0) {
return null
}
var first = buf.readUInt8(offset)
var length
var result = 0
var type
var bytePos
if (!hasMinBufferSize(first, bufLength)) {
return null
}
switch (first) {
case 0xc0:
return buildDecodeResult(null, 1)
case 0xc2:
return buildDecodeResult(false, 1)
case 0xc3:
return buildDecodeResult(true, 1)
case 0xcc:
// 1-byte unsigned int
result = buf.readUInt8(offset + 1)
return buildDecodeResult(result, 2)
case 0xcd:
// 2-bytes BE unsigned int
result = buf.readUInt16BE(offset + 1)
return buildDecodeResult(result, 3)
case 0xce:
// 4-bytes BE unsigned int
result = buf.readUInt32BE(offset + 1)
return buildDecodeResult(result, 5)
case 0xcf:
// 8-bytes BE unsigned int
// Read long byte by byte, big-endian
for (bytePos = 7; bytePos >= 0; bytePos--) {
result += (buf.readUInt8(offset + bytePos + 1) * Math.pow(2, (8 * (7 - bytePos))))
}
return buildDecodeResult(result, 9)
case 0xd0:
// 1-byte signed int
result = buf.readInt8(offset + 1)
return buildDecodeResult(result, 2)
case 0xd1:
// 2-bytes signed int
result = buf.readInt16BE(offset + 1)
return buildDecodeResult(result, 3)
case 0xd2:
// 4-bytes signed int
result = buf.readInt32BE(offset + 1)
return buildDecodeResult(result, 5)
case 0xd3:
result = readInt64BE(buf.slice(offset + 1, offset + 9), 0)
return buildDecodeResult(result, 9)
case 0xca:
// 4-bytes float
result = buf.readFloatBE(offset + 1)
return buildDecodeResult(result, 5)
case 0xcb:
// 8-bytes double
result = buf.readDoubleBE(offset + 1)
return buildDecodeResult(result, 9)
case 0xd9:
// strings up to 2^8 - 1 bytes
length = buf.readUInt8(offset + 1)
if (!isValidDataSize(length, bufLength, 2)) {
return null
}
result = buf.toString('utf8', offset + 2, offset + 2 + length)
return buildDecodeResult(result, 2 + length)
case 0xda:
// strings up to 2^16 - 2 bytes
length = buf.readUInt16BE(offset + 1)
if (!isValidDataSize(length, bufLength, 3)) {
return null
}
result = buf.toString('utf8', offset + 3, offset + 3 + length)
return buildDecodeResult(result, 3 + length)
case 0xdb:
// strings up to 2^32 - 4 bytes
length = buf.readUInt32BE(offset + 1)
if (!isValidDataSize(length, bufLength, 5)) {
return null
}
result = buf.toString('utf8', offset + 5, offset + 5 + length)
return buildDecodeResult(result, 5 + length)
case 0xc4:
// buffers up to 2^8 - 1 bytes
length = buf.readUInt8(offset + 1)
if (!isValidDataSize(length, bufLength, 2)) {
return null
}
result = buf.slice(offset + 2, offset + 2 + length)
return buildDecodeResult(result, 2 + length)
case 0xc5:
// buffers up to 2^16 - 1 bytes
length = buf.readUInt16BE(offset + 1)
if (!isValidDataSize(length, bufLength, 3)) {
return null
}
result = buf.slice(offset + 3, offset + 3 + length)
return buildDecodeResult(result, 3 + length)
case 0xc6:
// buffers up to 2^32 - 1 bytes
length = buf.readUInt32BE(offset + 1)
if (!isValidDataSize(length, bufLength, 5)) {
return null
}
result = buf.slice(offset + 5, offset + 5 + length)
return buildDecodeResult(result, 5 + length)
case 0xdc:
// array up to 2^16 elements - 2 bytes
if (bufLength < 3) {
return null
}
length = buf.readUInt16BE(offset + 1)
return decodeArray(buf, offset, length, 3)
case 0xdd:
// array up to 2^32 elements - 4 bytes
if (bufLength < 5) {
return null
}
length = buf.readUInt32BE(offset + 1)
return decodeArray(buf, offset, length, 5)
case 0xde:
// maps up to 2^16 elements - 2 bytes
length = buf.readUInt16BE(offset + 1)
return decodeMap(buf, offset, length, 3)
case 0xdf:
throw new Error('map too big to decode in JS')
case 0xd4:
return decodeFixExt(buf, offset, 1)
case 0xd5:
return decodeFixExt(buf, offset, 2)
case 0xd6:
return decodeFixExt(buf, offset, 4)
case 0xd7:
return decodeFixExt(buf, offset, 8)
case 0xd8:
return decodeFixExt(buf, offset, 16)
case 0xc7:
// ext up to 2^8 - 1 bytes
length = buf.readUInt8(offset + 1)
type = buf.readUInt8(offset + 2)
if (!isValidDataSize(length, bufLength, 3)) {
return null
}
return decodeExt(buf, offset, type, length, 3)
case 0xc8:
// ext up to 2^16 - 1 bytes
length = buf.readUInt16BE(offset + 1)
type = buf.readUInt8(offset + 3)
if (!isValidDataSize(length, bufLength, 4)) {
return null
}
return decodeExt(buf, offset, type, length, 4)
case 0xc9:
// ext up to 2^32 - 1 bytes
length = buf.readUInt32BE(offset + 1)
type = buf.readUInt8(offset + 5)
if (!isValidDataSize(length, bufLength, 6)) {
return null
}
return decodeExt(buf, offset, type, length, 6)
}
if ((first & 0xf0) === 0x90) {
// we have an array with less than 15 elements
length = first & 0x0f
return decodeArray(buf, offset, length, 1)
} else if ((first & 0xf0) === 0x80) {
// we have a map with less than 15 elements
length = first & 0x0f
return decodeMap(buf, offset, length, 1)
} else if ((first & 0xe0) === 0xa0) {
// fixstr up to 31 bytes
length = first & 0x1f
if (isValidDataSize(length, bufLength, 1)) {
result = buf.toString('utf8', offset + 1, offset + length + 1)
return buildDecodeResult(result, length + 1)
} else {
return null
}
} else if (first >= 0xe0) {
// 5 bits negative ints
result = first - 0x100
return buildDecodeResult(result, 1)
} else if (first < 0x80) {
// 7-bits positive ints
return buildDecodeResult(first, 1)
} else {
throw new Error('not implemented yet')
}
}
function readInt64BE (buf, offset) {
var negate = (buf[offset] & 0x80) == 0x80 // eslint-disable-line
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
}
}
var hi = buf.readUInt32BE(offset + 0)
var lo = buf.readUInt32BE(offset + 4)
return (hi * 4294967296 + lo) * (negate ? -1 : +1)
}
function decodeArray (buf, offset, length, headerLength) {
var result = []
var i
var totalBytesConsumed = 0
offset += headerLength
for (i = 0; i < length; i++) {
var decodeResult = tryDecode(buf, offset)
if (decodeResult) {
result.push(decodeResult.value)
offset += decodeResult.bytesConsumed
totalBytesConsumed += decodeResult.bytesConsumed
} else {
return null
}
}
return buildDecodeResult(result, headerLength + totalBytesConsumed)
}
function decodeMap (buf, offset, length, headerLength) {
var result = {}
var key
var i
var totalBytesConsumed = 0
offset += headerLength
for (i = 0; i < length; i++) {
var keyResult = tryDecode(buf, offset)
if (keyResult) {
offset += keyResult.bytesConsumed
var valueResult = tryDecode(buf, offset)
if (valueResult) {
key = keyResult.value
result[key] = valueResult.value
offset += valueResult.bytesConsumed
totalBytesConsumed += (keyResult.bytesConsumed + valueResult.bytesConsumed)
} else {
return null
}
} else {
return null
}
}
return buildDecodeResult(result, headerLength + totalBytesConsumed)
}
function decodeFixExt (buf, offset, size) {
var type = buf.readUInt8(offset + 1)
return decodeExt(buf, offset, type, size, 2)
}
function decodeExt (buf, offset, type, size, headerSize) {
var i,
toDecode
offset += headerSize
for (i = 0; i < decodingTypes.length; i++) {
if (type === decodingTypes[i].type) {
toDecode = buf.slice(offset, offset + size)
var value = decodingTypes[i].decode(toDecode)
return buildDecodeResult(value, headerSize + size)
}
}
throw new Error('unable to find ext type ' + type)
}
}
module.exports.IncompleteBufferError = IncompleteBufferError