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.
1062 lines
28 KiB
1062 lines
28 KiB
'use strict' |
|
|
|
var protocol = require('./constants') |
|
var Buffer = require('safe-buffer').Buffer |
|
var empty = Buffer.allocUnsafe(0) |
|
var zeroBuf = Buffer.from([0]) |
|
var numbers = require('./numbers') |
|
var nextTick = require('process-nextick-args').nextTick |
|
|
|
var numCache = numbers.cache |
|
var generateNumber = numbers.generateNumber |
|
var generateCache = numbers.generateCache |
|
var genBufVariableByteInt = numbers.genBufVariableByteInt |
|
var generate4ByteBuffer = numbers.generate4ByteBuffer |
|
var writeNumber = writeNumberCached |
|
var toGenerate = true |
|
|
|
function generate (packet, stream, opts) { |
|
if (stream.cork) { |
|
stream.cork() |
|
nextTick(uncork, stream) |
|
} |
|
|
|
if (toGenerate) { |
|
toGenerate = false |
|
generateCache() |
|
} |
|
|
|
switch (packet.cmd) { |
|
case 'connect': |
|
return connect(packet, stream, opts) |
|
case 'connack': |
|
return connack(packet, stream, opts) |
|
case 'publish': |
|
return publish(packet, stream, opts) |
|
case 'puback': |
|
case 'pubrec': |
|
case 'pubrel': |
|
case 'pubcomp': |
|
return confirmation(packet, stream, opts) |
|
case 'subscribe': |
|
return subscribe(packet, stream, opts) |
|
case 'suback': |
|
return suback(packet, stream, opts) |
|
case 'unsubscribe': |
|
return unsubscribe(packet, stream, opts) |
|
case 'unsuback': |
|
return unsuback(packet, stream, opts) |
|
case 'pingreq': |
|
case 'pingresp': |
|
return emptyPacket(packet, stream, opts) |
|
case 'disconnect': |
|
return disconnect(packet, stream, opts) |
|
case 'auth': |
|
return auth(packet, stream, opts) |
|
default: |
|
stream.emit('error', new Error('Unknown command')) |
|
return false |
|
} |
|
} |
|
/** |
|
* Controls numbers cache. |
|
* Set to "false" to allocate buffers on-the-flight instead of pre-generated cache |
|
*/ |
|
Object.defineProperty(generate, 'cacheNumbers', { |
|
get: function () { |
|
return writeNumber === writeNumberCached |
|
}, |
|
set: function (value) { |
|
if (value) { |
|
if (!numCache || Object.keys(numCache).length === 0) toGenerate = true |
|
writeNumber = writeNumberCached |
|
} else { |
|
toGenerate = false |
|
writeNumber = writeNumberGenerated |
|
} |
|
} |
|
}) |
|
|
|
function uncork (stream) { |
|
stream.uncork() |
|
} |
|
|
|
function connect (packet, stream, opts) { |
|
var settings = packet || {} |
|
var protocolId = settings.protocolId || 'MQTT' |
|
var protocolVersion = settings.protocolVersion || 4 |
|
var will = settings.will |
|
var clean = settings.clean |
|
var keepalive = settings.keepalive || 0 |
|
var clientId = settings.clientId || '' |
|
var username = settings.username |
|
var password = settings.password |
|
/* mqtt5 new oprions */ |
|
var properties = settings.properties |
|
|
|
if (clean === undefined) clean = true |
|
|
|
var length = 0 |
|
|
|
// Must be a string and non-falsy |
|
if (!protocolId || |
|
(typeof protocolId !== 'string' && !Buffer.isBuffer(protocolId))) { |
|
stream.emit('error', new Error('Invalid protocolId')) |
|
return false |
|
} else length += protocolId.length + 2 |
|
|
|
// Must be 3 or 4 or 5 |
|
if (protocolVersion !== 3 && protocolVersion !== 4 && protocolVersion !== 5) { |
|
stream.emit('error', new Error('Invalid protocol version')) |
|
return false |
|
} else length += 1 |
|
|
|
// ClientId might be omitted in 3.1.1, but only if cleanSession is set to 1 |
|
if ((typeof clientId === 'string' || Buffer.isBuffer(clientId)) && |
|
(clientId || protocolVersion === 4) && (clientId || clean)) { |
|
length += clientId.length + 2 |
|
} else { |
|
if (protocolVersion < 4) { |
|
stream.emit('error', new Error('clientId must be supplied before 3.1.1')) |
|
return false |
|
} |
|
if ((clean * 1) === 0) { |
|
stream.emit('error', new Error('clientId must be given if cleanSession set to 0')) |
|
return false |
|
} |
|
} |
|
|
|
// Must be a two byte number |
|
if (typeof keepalive !== 'number' || |
|
keepalive < 0 || |
|
keepalive > 65535 || |
|
keepalive % 1 !== 0) { |
|
stream.emit('error', new Error('Invalid keepalive')) |
|
return false |
|
} else length += 2 |
|
|
|
// Connect flags |
|
length += 1 |
|
|
|
// Properties |
|
if (protocolVersion === 5) { |
|
var propertiesData = getProperties(stream, properties) |
|
length += propertiesData.length |
|
} |
|
|
|
// If will exists... |
|
if (will) { |
|
// It must be an object |
|
if (typeof will !== 'object') { |
|
stream.emit('error', new Error('Invalid will')) |
|
return false |
|
} |
|
// It must have topic typeof string |
|
if (!will.topic || typeof will.topic !== 'string') { |
|
stream.emit('error', new Error('Invalid will topic')) |
|
return false |
|
} else { |
|
length += Buffer.byteLength(will.topic) + 2 |
|
} |
|
|
|
// Payload |
|
length += 2 // payload length |
|
if (will.payload) { |
|
if (will.payload.length >= 0) { |
|
if (typeof will.payload === 'string') { |
|
length += Buffer.byteLength(will.payload) |
|
} else { |
|
length += will.payload.length |
|
} |
|
} else { |
|
stream.emit('error', new Error('Invalid will payload')) |
|
return false |
|
} |
|
} |
|
// will properties |
|
var willProperties = {} |
|
if (protocolVersion === 5) { |
|
willProperties = getProperties(stream, will.properties) |
|
length += willProperties.length |
|
} |
|
} |
|
|
|
// Username |
|
var providedUsername = false |
|
if (username != null) { |
|
if (isStringOrBuffer(username)) { |
|
providedUsername = true |
|
length += Buffer.byteLength(username) + 2 |
|
} else { |
|
stream.emit('error', new Error('Invalid username')) |
|
return false |
|
} |
|
} |
|
|
|
// Password |
|
if (password != null) { |
|
if (!providedUsername) { |
|
stream.emit('error', new Error('Username is required to use password')) |
|
return false |
|
} |
|
|
|
if (isStringOrBuffer(password)) { |
|
length += byteLength(password) + 2 |
|
} else { |
|
stream.emit('error', new Error('Invalid password')) |
|
return false |
|
} |
|
} |
|
|
|
// Generate header |
|
stream.write(protocol.CONNECT_HEADER) |
|
|
|
// Generate length |
|
writeVarByteInt(stream, length) |
|
|
|
// Generate protocol ID |
|
writeStringOrBuffer(stream, protocolId) |
|
stream.write( |
|
protocolVersion === 4 |
|
? protocol.VERSION4 |
|
: protocolVersion === 5 |
|
? protocol.VERSION5 |
|
: protocol.VERSION3 |
|
) |
|
|
|
// Connect flags |
|
var flags = 0 |
|
flags |= (username != null) ? protocol.USERNAME_MASK : 0 |
|
flags |= (password != null) ? protocol.PASSWORD_MASK : 0 |
|
flags |= (will && will.retain) ? protocol.WILL_RETAIN_MASK : 0 |
|
flags |= (will && will.qos) ? will.qos << protocol.WILL_QOS_SHIFT : 0 |
|
flags |= will ? protocol.WILL_FLAG_MASK : 0 |
|
flags |= clean ? protocol.CLEAN_SESSION_MASK : 0 |
|
|
|
stream.write(Buffer.from([flags])) |
|
|
|
// Keepalive |
|
writeNumber(stream, keepalive) |
|
|
|
// Properties |
|
if (protocolVersion === 5) { |
|
propertiesData.write() |
|
} |
|
|
|
// Client ID |
|
writeStringOrBuffer(stream, clientId) |
|
|
|
// Will |
|
if (will) { |
|
if (protocolVersion === 5) { |
|
willProperties.write() |
|
} |
|
writeString(stream, will.topic) |
|
writeStringOrBuffer(stream, will.payload) |
|
} |
|
|
|
// Username and password |
|
if (username != null) { |
|
writeStringOrBuffer(stream, username) |
|
} |
|
if (password != null) { |
|
writeStringOrBuffer(stream, password) |
|
} |
|
// This is a small packet that happens only once on a stream |
|
// We assume the stream is always free to receive more data after this |
|
return true |
|
} |
|
|
|
function connack (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var rc = version === 5 ? settings.reasonCode : settings.returnCode |
|
var properties = settings.properties |
|
var length = 2 // length of rc and sessionHeader |
|
|
|
// Check return code |
|
if (typeof rc !== 'number') { |
|
stream.emit('error', new Error('Invalid return code')) |
|
return false |
|
} |
|
// mqtt5 properties |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getProperties(stream, properties) |
|
length += propertiesData.length |
|
} |
|
|
|
stream.write(protocol.CONNACK_HEADER) |
|
// length |
|
writeVarByteInt(stream, length) |
|
stream.write(settings.sessionPresent ? protocol.SESSIONPRESENT_HEADER : zeroBuf) |
|
|
|
stream.write(Buffer.from([rc])) |
|
if (propertiesData != null) { |
|
propertiesData.write() |
|
} |
|
return true |
|
} |
|
|
|
function publish (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var qos = settings.qos || 0 |
|
var retain = settings.retain ? protocol.RETAIN_MASK : 0 |
|
var topic = settings.topic |
|
var payload = settings.payload || empty |
|
var id = settings.messageId |
|
var properties = settings.properties |
|
|
|
var length = 0 |
|
|
|
// Topic must be a non-empty string or Buffer |
|
if (typeof topic === 'string') length += Buffer.byteLength(topic) + 2 |
|
else if (Buffer.isBuffer(topic)) length += topic.length + 2 |
|
else { |
|
stream.emit('error', new Error('Invalid topic')) |
|
return false |
|
} |
|
|
|
// Get the payload length |
|
if (!Buffer.isBuffer(payload)) length += Buffer.byteLength(payload) |
|
else length += payload.length |
|
|
|
// Message ID must a number if qos > 0 |
|
if (qos && typeof id !== 'number') { |
|
stream.emit('error', new Error('Invalid messageId')) |
|
return false |
|
} else if (qos) length += 2 |
|
|
|
// mqtt5 properties |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getProperties(stream, properties) |
|
length += propertiesData.length |
|
} |
|
|
|
// Header |
|
stream.write(protocol.PUBLISH_HEADER[qos][settings.dup ? 1 : 0][retain ? 1 : 0]) |
|
|
|
// Remaining length |
|
writeVarByteInt(stream, length) |
|
|
|
// Topic |
|
writeNumber(stream, byteLength(topic)) |
|
stream.write(topic) |
|
|
|
// Message ID |
|
if (qos > 0) writeNumber(stream, id) |
|
|
|
// Properties |
|
if (propertiesData != null) { |
|
propertiesData.write() |
|
} |
|
|
|
// Payload |
|
return stream.write(payload) |
|
} |
|
|
|
/* Puback, pubrec, pubrel and pubcomp */ |
|
function confirmation (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var type = settings.cmd || 'puback' |
|
var id = settings.messageId |
|
var dup = (settings.dup && type === 'pubrel') ? protocol.DUP_MASK : 0 |
|
var qos = 0 |
|
var reasonCode = settings.reasonCode |
|
var properties = settings.properties |
|
var length = version === 5 ? 3 : 2 |
|
|
|
if (type === 'pubrel') qos = 1 |
|
|
|
// Check message ID |
|
if (typeof id !== 'number') { |
|
stream.emit('error', new Error('Invalid messageId')) |
|
return false |
|
} |
|
|
|
// properies mqtt 5 |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) |
|
if (!propertiesData) { return false } |
|
length += propertiesData.length |
|
} |
|
|
|
// Header |
|
stream.write(protocol.ACKS[type][qos][dup][0]) |
|
|
|
// Length |
|
writeVarByteInt(stream, length) |
|
|
|
// Message ID |
|
writeNumber(stream, id) |
|
|
|
// reason code in header |
|
if (version === 5) { |
|
stream.write(Buffer.from([reasonCode])) |
|
} |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
return true |
|
} |
|
|
|
function subscribe (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var dup = settings.dup ? protocol.DUP_MASK : 0 |
|
var id = settings.messageId |
|
var subs = settings.subscriptions |
|
var properties = settings.properties |
|
|
|
var length = 0 |
|
|
|
// Check message ID |
|
if (typeof id !== 'number') { |
|
stream.emit('error', new Error('Invalid messageId')) |
|
return false |
|
} else length += 2 |
|
|
|
// properies mqtt 5 |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getProperties(stream, properties) |
|
length += propertiesData.length |
|
} |
|
|
|
// Check subscriptions |
|
if (typeof subs === 'object' && subs.length) { |
|
for (var i = 0; i < subs.length; i += 1) { |
|
var itopic = subs[i].topic |
|
var iqos = subs[i].qos |
|
|
|
if (typeof itopic !== 'string') { |
|
stream.emit('error', new Error('Invalid subscriptions - invalid topic')) |
|
return false |
|
} |
|
if (typeof iqos !== 'number') { |
|
stream.emit('error', new Error('Invalid subscriptions - invalid qos')) |
|
return false |
|
} |
|
|
|
if (version === 5) { |
|
var nl = subs[i].nl || false |
|
if (typeof nl !== 'boolean') { |
|
stream.emit('error', new Error('Invalid subscriptions - invalid No Local')) |
|
return false |
|
} |
|
var rap = subs[i].rap || false |
|
if (typeof rap !== 'boolean') { |
|
stream.emit('error', new Error('Invalid subscriptions - invalid Retain as Published')) |
|
return false |
|
} |
|
var rh = subs[i].rh || 0 |
|
if (typeof rh !== 'number' || rh > 2) { |
|
stream.emit('error', new Error('Invalid subscriptions - invalid Retain Handling')) |
|
return false |
|
} |
|
} |
|
|
|
length += Buffer.byteLength(itopic) + 2 + 1 |
|
} |
|
} else { |
|
stream.emit('error', new Error('Invalid subscriptions')) |
|
return false |
|
} |
|
|
|
// Generate header |
|
stream.write(protocol.SUBSCRIBE_HEADER[1][dup ? 1 : 0][0]) |
|
|
|
// Generate length |
|
writeVarByteInt(stream, length) |
|
|
|
// Generate message ID |
|
writeNumber(stream, id) |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
|
|
var result = true |
|
|
|
// Generate subs |
|
for (var j = 0; j < subs.length; j++) { |
|
var sub = subs[j] |
|
var jtopic = sub.topic |
|
var jqos = sub.qos |
|
var jnl = +sub.nl |
|
var jrap = +sub.rap |
|
var jrh = sub.rh |
|
var joptions |
|
|
|
// Write topic string |
|
writeString(stream, jtopic) |
|
|
|
// options process |
|
joptions = protocol.SUBSCRIBE_OPTIONS_QOS[jqos] |
|
if (version === 5) { |
|
joptions |= jnl ? protocol.SUBSCRIBE_OPTIONS_NL : 0 |
|
joptions |= jrap ? protocol.SUBSCRIBE_OPTIONS_RAP : 0 |
|
joptions |= jrh ? protocol.SUBSCRIBE_OPTIONS_RH[jrh] : 0 |
|
} |
|
// Write options |
|
result = stream.write(Buffer.from([joptions])) |
|
} |
|
|
|
return result |
|
} |
|
|
|
function suback (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var id = settings.messageId |
|
var granted = settings.granted |
|
var properties = settings.properties |
|
var length = 0 |
|
|
|
// Check message ID |
|
if (typeof id !== 'number') { |
|
stream.emit('error', new Error('Invalid messageId')) |
|
return false |
|
} else length += 2 |
|
|
|
// Check granted qos vector |
|
if (typeof granted === 'object' && granted.length) { |
|
for (var i = 0; i < granted.length; i += 1) { |
|
if (typeof granted[i] !== 'number') { |
|
stream.emit('error', new Error('Invalid qos vector')) |
|
return false |
|
} |
|
length += 1 |
|
} |
|
} else { |
|
stream.emit('error', new Error('Invalid qos vector')) |
|
return false |
|
} |
|
|
|
// properies mqtt 5 |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) |
|
if (!propertiesData) { return false } |
|
length += propertiesData.length |
|
} |
|
|
|
// header |
|
stream.write(protocol.SUBACK_HEADER) |
|
|
|
// Length |
|
writeVarByteInt(stream, length) |
|
|
|
// Message ID |
|
writeNumber(stream, id) |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
|
|
return stream.write(Buffer.from(granted)) |
|
} |
|
|
|
function unsubscribe (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var id = settings.messageId |
|
var dup = settings.dup ? protocol.DUP_MASK : 0 |
|
var unsubs = settings.unsubscriptions |
|
var properties = settings.properties |
|
|
|
var length = 0 |
|
|
|
// Check message ID |
|
if (typeof id !== 'number') { |
|
stream.emit('error', new Error('Invalid messageId')) |
|
return false |
|
} else { |
|
length += 2 |
|
} |
|
// Check unsubs |
|
if (typeof unsubs === 'object' && unsubs.length) { |
|
for (var i = 0; i < unsubs.length; i += 1) { |
|
if (typeof unsubs[i] !== 'string') { |
|
stream.emit('error', new Error('Invalid unsubscriptions')) |
|
return false |
|
} |
|
length += Buffer.byteLength(unsubs[i]) + 2 |
|
} |
|
} else { |
|
stream.emit('error', new Error('Invalid unsubscriptions')) |
|
return false |
|
} |
|
// properies mqtt 5 |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getProperties(stream, properties) |
|
length += propertiesData.length |
|
} |
|
|
|
// Header |
|
stream.write(protocol.UNSUBSCRIBE_HEADER[1][dup ? 1 : 0][0]) |
|
|
|
// Length |
|
writeVarByteInt(stream, length) |
|
|
|
// Message ID |
|
writeNumber(stream, id) |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
|
|
// Unsubs |
|
var result = true |
|
for (var j = 0; j < unsubs.length; j++) { |
|
result = writeString(stream, unsubs[j]) |
|
} |
|
|
|
return result |
|
} |
|
|
|
function unsuback (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var id = settings.messageId |
|
var dup = settings.dup ? protocol.DUP_MASK : 0 |
|
var granted = settings.granted |
|
var properties = settings.properties |
|
var type = settings.cmd |
|
var qos = 0 |
|
|
|
var length = 2 |
|
|
|
// Check message ID |
|
if (typeof id !== 'number') { |
|
stream.emit('error', new Error('Invalid messageId')) |
|
return false |
|
} |
|
|
|
// Check granted |
|
if (version === 5) { |
|
if (typeof granted === 'object' && granted.length) { |
|
for (var i = 0; i < granted.length; i += 1) { |
|
if (typeof granted[i] !== 'number') { |
|
stream.emit('error', new Error('Invalid qos vector')) |
|
return false |
|
} |
|
length += 1 |
|
} |
|
} else { |
|
stream.emit('error', new Error('Invalid qos vector')) |
|
return false |
|
} |
|
} |
|
|
|
// properies mqtt 5 |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) |
|
if (!propertiesData) { return false } |
|
length += propertiesData.length |
|
} |
|
|
|
// Header |
|
stream.write(protocol.ACKS[type][qos][dup][0]) |
|
|
|
// Length |
|
writeVarByteInt(stream, length) |
|
|
|
// Message ID |
|
writeNumber(stream, id) |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
|
|
// payload |
|
if (version === 5) { |
|
stream.write(Buffer.from(granted)) |
|
} |
|
return true |
|
} |
|
|
|
function emptyPacket (packet, stream, opts) { |
|
return stream.write(protocol.EMPTY[packet.cmd]) |
|
} |
|
|
|
function disconnect (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var reasonCode = settings.reasonCode |
|
var properties = settings.properties |
|
var length = version === 5 ? 1 : 0 |
|
|
|
// properies mqtt 5 |
|
var propertiesData = null |
|
if (version === 5) { |
|
propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) |
|
if (!propertiesData) { return false } |
|
length += propertiesData.length |
|
} |
|
|
|
// Header |
|
stream.write(Buffer.from([protocol.codes['disconnect'] << 4])) |
|
|
|
// Length |
|
writeVarByteInt(stream, length) |
|
|
|
// reason code in header |
|
if (version === 5) { |
|
stream.write(Buffer.from([reasonCode])) |
|
} |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
|
|
return true |
|
} |
|
|
|
function auth (packet, stream, opts) { |
|
var version = opts ? opts.protocolVersion : 4 |
|
var settings = packet || {} |
|
var reasonCode = settings.reasonCode |
|
var properties = settings.properties |
|
var length = version === 5 ? 1 : 0 |
|
|
|
if (version !== 5) stream.emit('error', new Error('Invalid mqtt version for auth packet')) |
|
|
|
// properies mqtt 5 |
|
var propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) |
|
if (!propertiesData) { return false } |
|
length += propertiesData.length |
|
|
|
// Header |
|
stream.write(Buffer.from([protocol.codes['auth'] << 4])) |
|
|
|
// Length |
|
writeVarByteInt(stream, length) |
|
|
|
// reason code in header |
|
stream.write(Buffer.from([reasonCode])) |
|
|
|
// properies mqtt 5 |
|
if (propertiesData !== null) { |
|
propertiesData.write() |
|
} |
|
return true |
|
} |
|
|
|
/** |
|
* writeVarByteInt - write an MQTT style variable byte integer to the buffer |
|
* |
|
* @param <Buffer> buffer - destination |
|
* @param <Number> pos - offset |
|
* @param <Number> length - length (>0) |
|
* @returns <Number> number of bytes written |
|
* |
|
* @api private |
|
*/ |
|
|
|
var varByteIntCache = {} |
|
function writeVarByteInt (stream, num) { |
|
var buffer = varByteIntCache[num] |
|
|
|
if (!buffer) { |
|
buffer = genBufVariableByteInt(num).data |
|
if (num < 16384) varByteIntCache[num] = buffer |
|
} |
|
|
|
stream.write(buffer) |
|
} |
|
|
|
/** |
|
* writeString - write a utf8 string to the buffer |
|
* |
|
* @param <Buffer> buffer - destination |
|
* @param <Number> pos - offset |
|
* @param <String> string - string to write |
|
* @return <Number> number of bytes written |
|
* |
|
* @api private |
|
*/ |
|
|
|
function writeString (stream, string) { |
|
var strlen = Buffer.byteLength(string) |
|
writeNumber(stream, strlen) |
|
|
|
stream.write(string, 'utf8') |
|
} |
|
|
|
/** |
|
* writeStringPair - write a utf8 string pairs to the buffer |
|
* |
|
* @param <Buffer> buffer - destination |
|
* @param <String> name - string name to write |
|
* @param <String> value - string value to write |
|
* @return <Number> number of bytes written |
|
* |
|
* @api private |
|
*/ |
|
function writeStringPair (stream, name, value) { |
|
writeString(stream, name) |
|
writeString(stream, value) |
|
} |
|
|
|
/** |
|
* writeNumber - write a two byte number to the buffer |
|
* |
|
* @param <Buffer> buffer - destination |
|
* @param <Number> pos - offset |
|
* @param <String> number - number to write |
|
* @return <Number> number of bytes written |
|
* |
|
* @api private |
|
*/ |
|
function writeNumberCached (stream, number) { |
|
return stream.write(numCache[number]) |
|
} |
|
function writeNumberGenerated (stream, number) { |
|
return stream.write(generateNumber(number)) |
|
} |
|
function write4ByteNumber (stream, number) { |
|
return stream.write(generate4ByteBuffer(number)) |
|
} |
|
/** |
|
* writeStringOrBuffer - write a String or Buffer with the its length prefix |
|
* |
|
* @param <Buffer> buffer - destination |
|
* @param <Number> pos - offset |
|
* @param <String> toWrite - String or Buffer |
|
* @return <Number> number of bytes written |
|
*/ |
|
function writeStringOrBuffer (stream, toWrite) { |
|
if (typeof toWrite === 'string') { |
|
writeString(stream, toWrite) |
|
} else if (toWrite) { |
|
writeNumber(stream, toWrite.length) |
|
stream.write(toWrite) |
|
} else writeNumber(stream, 0) |
|
} |
|
|
|
function getProperties (stream, properties) { |
|
/* connect properties */ |
|
if (typeof properties !== 'object' || properties.length != null) { |
|
return { |
|
length: 1, |
|
write: function () { |
|
writeProperties(stream, {}, 0) |
|
} |
|
} |
|
} |
|
var propertiesLength = 0 |
|
function getLengthProperty (name) { |
|
var type = protocol.propertiesTypes[name] |
|
var value = properties[name] |
|
var length = 0 |
|
switch (type) { |
|
case 'byte': { |
|
if (typeof value !== 'boolean') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + 1 |
|
break |
|
} |
|
case 'int8': { |
|
if (typeof value !== 'number') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + 1 |
|
break |
|
} |
|
case 'binary': { |
|
if (value && value === null) { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + Buffer.byteLength(value) + 2 |
|
break |
|
} |
|
case 'int16': { |
|
if (typeof value !== 'number') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + 2 |
|
break |
|
} |
|
case 'int32': { |
|
if (typeof value !== 'number') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + 4 |
|
break |
|
} |
|
case 'var': { |
|
if (typeof value !== 'number') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + genBufVariableByteInt(value).length |
|
break |
|
} |
|
case 'string': { |
|
if (typeof value !== 'string') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += 1 + 2 + Buffer.byteLength(value.toString()) |
|
break |
|
} |
|
case 'pair': { |
|
if (typeof value !== 'object') { |
|
stream.emit('error', new Error('Invalid ' + name)) |
|
return false |
|
} |
|
length += Object.getOwnPropertyNames(value).reduce(function (result, name) { |
|
var currentValue = value[name] |
|
if (Array.isArray(currentValue)) { |
|
result += currentValue.reduce(function (currentLength, value) { |
|
currentLength += 1 + 2 + Buffer.byteLength(name.toString()) + 2 + Buffer.byteLength(value.toString()) |
|
return currentLength |
|
}, 0) |
|
} else { |
|
result += 1 + 2 + Buffer.byteLength(name.toString()) + 2 + Buffer.byteLength(value[name].toString()) |
|
} |
|
return result |
|
}, 0) |
|
break |
|
} |
|
default: { |
|
stream.emit('error', new Error('Invalid property ' + name)) |
|
return false |
|
} |
|
} |
|
return length |
|
} |
|
if (properties) { |
|
for (var propName in properties) { |
|
var propLength = getLengthProperty(propName) |
|
if (!propLength) return false |
|
propertiesLength += propLength |
|
} |
|
} |
|
var propertiesLengthLength = genBufVariableByteInt(propertiesLength).length |
|
|
|
return { |
|
length: propertiesLengthLength + propertiesLength, |
|
write: function () { |
|
writeProperties(stream, properties, propertiesLength) |
|
} |
|
} |
|
} |
|
|
|
function getPropertiesByMaximumPacketSize (stream, properties, opts, length) { |
|
var mayEmptyProps = ['reasonString', 'userProperties'] |
|
var maximumPacketSize = opts && opts.properties && opts.properties.maximumPacketSize ? opts.properties.maximumPacketSize : 0 |
|
|
|
var propertiesData = getProperties(stream, properties) |
|
if (maximumPacketSize) { |
|
while (length + propertiesData.length > maximumPacketSize) { |
|
var currentMayEmptyProp = mayEmptyProps.shift() |
|
if (currentMayEmptyProp && properties[currentMayEmptyProp]) { |
|
delete properties[currentMayEmptyProp] |
|
propertiesData = getProperties(stream, properties) |
|
} else { |
|
return false |
|
} |
|
} |
|
} |
|
return propertiesData |
|
} |
|
|
|
function writeProperties (stream, properties, propertiesLength) { |
|
/* write properties to stream */ |
|
writeVarByteInt(stream, propertiesLength) |
|
for (var propName in properties) { |
|
if (properties.hasOwnProperty(propName) && properties[propName] !== null) { |
|
var value = properties[propName] |
|
var type = protocol.propertiesTypes[propName] |
|
switch (type) { |
|
case 'byte': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
stream.write(Buffer.from([+value])) |
|
break |
|
} |
|
case 'int8': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
stream.write(Buffer.from([value])) |
|
break |
|
} |
|
case 'binary': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
writeStringOrBuffer(stream, value) |
|
break |
|
} |
|
case 'int16': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
writeNumber(stream, value) |
|
break |
|
} |
|
case 'int32': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
write4ByteNumber(stream, value) |
|
break |
|
} |
|
case 'var': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
writeVarByteInt(stream, value) |
|
break |
|
} |
|
case 'string': { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
writeString(stream, value) |
|
break |
|
} |
|
case 'pair': { |
|
Object.getOwnPropertyNames(value).forEach(function (name) { |
|
var currentValue = value[name] |
|
if (Array.isArray(currentValue)) { |
|
currentValue.forEach(function (value) { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
writeStringPair(stream, name.toString(), value.toString()) |
|
}) |
|
} else { |
|
stream.write(Buffer.from([protocol.properties[propName]])) |
|
writeStringPair(stream, name.toString(), currentValue.toString()) |
|
} |
|
}) |
|
break |
|
} |
|
default: { |
|
stream.emit('error', new Error('Invalid property ' + propName)) |
|
return false |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function byteLength (bufOrString) { |
|
if (!bufOrString) return 0 |
|
else if (bufOrString instanceof Buffer) return bufOrString.length |
|
else return Buffer.byteLength(bufOrString) |
|
} |
|
|
|
function isStringOrBuffer (field) { |
|
return typeof field === 'string' || field instanceof Buffer |
|
} |
|
|
|
module.exports = generate
|
|
|