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.

614 lines
14 KiB

'use strict';
var protocol = require('./constants')
, empty = new Buffer(0)
function generate(packet) {
switch (packet.cmd) {
case 'connect':
return connect(packet)
case 'connack':
return connack(packet)
case 'publish':
return publish(packet)
case 'puback':
case 'pubrec':
case 'pubrel':
case 'pubcomp':
case 'unsuback':
return confirmation(packet)
case 'subscribe':
return subscribe(packet)
case 'suback':
return suback(packet)
case 'unsubscribe':
return unsubscribe(packet)
case 'pingreq':
case 'pingresp':
case 'disconnect':
return emptyPacket(packet)
default:
throw new Error('unknown command')
}
}
function connect(opts) {
var opts = opts || {}
, protocolId = opts.protocolId || 'MQTT'
, protocolVersion = opts.protocolVersion || 4
, will = opts.will
, clean = opts.clean
, keepalive = opts.keepalive || 0
, clientId = opts.clientId || ""
, username = opts.username
, password = opts.password
if (clean === undefined) {
clean = true
}
var length = 0
// Must be a string and non-falsy
if (!protocolId ||
(typeof protocolId !== "string" && !Buffer.isBuffer(protocolId))) {
throw new Error('Invalid protocol id')
} else {
length += protocolId.length + 2
}
// Must be a 1 byte number
if (!protocolVersion ||
'number' !== typeof protocolVersion ||
protocolVersion > 255 ||
protocolVersion < 0) {
throw new Error('Invalid protocol version')
} 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) {
throw new Error('clientId must be supplied before 3.1.1');
}
if(clean == 0) {
throw new Error('clientId must be given if cleanSession set to 0');
}
}
// Must be a two byte number
if ('number' !== typeof keepalive ||
keepalive < 0 ||
keepalive > 65535) {
throw new Error('Invalid keepalive')
} else {
length += 2
}
// Connect flags
length += 1
// If will exists...
if (will) {
// It must be an object
if ('object' !== typeof will) {
throw new Error('Invalid will')
}
// It must have topic typeof string
if (!will.topic || 'string' !== typeof will.topic) {
throw new Error('Invalid will topic')
} else {
length += Buffer.byteLength(will.topic) + 2
}
// Payload
if (will.payload && will.payload) {
if (will.payload.length >= 0) {
if ('string' === typeof will.payload) {
length += Buffer.byteLength(will.payload) + 2
} else {
length += will.payload.length + 2
}
} else {
throw new Error('Invalid will payload')
}
} else {
length += 2
}
}
// Username
if (username) {
if (username.length) {
length += Buffer.byteLength(username) + 2
} else {
throw new Error('Invalid username')
}
}
// Password
if (password) {
if (password.length) {
length += byteLength(password) + 2
} else {
throw new Error('Invalid password')
}
}
var buffer = new Buffer(1 + calcLengthLength(length) + length)
, pos = 0
// Generate header
buffer.writeUInt8(protocol.codes['connect'] << protocol.CMD_SHIFT, pos++, true)
// Generate length
pos += writeLength(buffer, pos, length)
// Generate protocol ID
pos += writeStringOrBuffer(buffer, pos, protocolId)
buffer.writeUInt8(protocolVersion, pos++, true)
// Connect flags
var flags = 0
flags |= username ? protocol.USERNAME_MASK : 0
flags |= password ? 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
buffer.writeUInt8(flags, pos++, true)
// Keepalive
pos += writeNumber(buffer, pos, keepalive)
// Client ID
pos += writeStringOrBuffer(buffer, pos, clientId)
// Will
if (will) {
pos += writeString(buffer, pos, will.topic)
pos += writeStringOrBuffer(buffer, pos, will.payload)
}
// Username and password
if (username)
pos += writeStringOrBuffer(buffer, pos, username)
if (password)
pos += writeStringOrBuffer(buffer, pos, password)
return buffer
}
function connack(opts) {
var opts = opts || {}
, rc = opts.returnCode;
// Check return code
if ('number' !== typeof rc)
throw new Error('Invalid return code');
var buffer = new Buffer(4)
, pos = 0;
buffer.writeUInt8(protocol.codes['connack'] << protocol.CMD_SHIFT, pos++, true);
pos += writeLength(buffer, pos, 2);
buffer.writeUInt8(opts.sessionPresent && protocol.SESSIONPRESENT_MASK || 0, pos++, true);
buffer.writeUInt8(rc, pos++, true);
return buffer;
}
function publish(opts) {
var opts = opts || {}
, dup = opts.dup ? protocol.DUP_MASK : 0
, qos = opts.qos
, retain = opts.retain ? protocol.RETAIN_MASK : 0
, topic = opts.topic
, payload = opts.payload || empty
, id = opts.messageId;
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
throw new Error('Invalid topic');
// 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 && 'number' !== typeof id) {
throw new Error('Invalid message id')
} else if (qos) {
length += 2;
}
var buffer = new Buffer(1 + calcLengthLength(length) + length)
, pos = 0;
// Header
buffer.writeUInt8(
protocol.codes['publish'] << protocol.CMD_SHIFT |
dup |
qos << protocol.QOS_SHIFT |
retain, pos++, true);
// Remaining length
pos += writeLength(buffer, pos, length);
// Topic
pos += writeStringOrBuffer(buffer, pos, topic);
// Message ID
if (qos > 0) {
pos += writeNumber(buffer, pos, id);
}
// Payload
if (!Buffer.isBuffer(payload)) {
writeStringNoPos(buffer, pos, payload);
} else {
writeBuffer(buffer, pos, payload);
}
return buffer;
}
/* Puback, pubrec, pubrel and pubcomp */
function confirmation(opts) {
var opts = opts || {}
, type = opts.cmd || 'puback'
, id = opts.messageId
, dup = (opts.dup && type === 'pubrel') ? protocol.DUP_MASK : 0
, qos = 0
if (type === 'pubrel')
qos = 1
// Check message ID
if ('number' !== typeof id)
throw new Error('Invalid message id');
var buffer = new Buffer(4)
, pos = 0;
// Header
buffer[pos++] =
protocol.codes[type] << protocol.CMD_SHIFT |
dup |
qos << protocol.QOS_SHIFT;
// Length
pos += writeLength(buffer, pos, 2);
// Message ID
pos += writeNumber(buffer, pos, id);
return buffer;
}
function subscribe(opts) {
var opts = opts || {}
, dup = opts.dup ? protocol.DUP_MASK : 0
, qos = opts.qos || 0
, id = opts.messageId
, subs = opts.subscriptions;
var length = 0;
// Check mid
if ('number' !== typeof id) {
throw new Error('Invalid message id');
} else {
length += 2;
}
// Check subscriptions
if ('object' === typeof subs && subs.length) {
for (var i = 0; i < subs.length; i += 1) {
var topic = subs[i].topic
, qos = subs[i].qos;
if ('string' !== typeof topic) {
throw new Error('Invalid subscriptions - invalid topic');
}
if ('number' !== typeof qos) {
throw new Error('Invalid subscriptions - invalid qos');
}
length += Buffer.byteLength(topic) + 2 + 1;
}
} else {
throw new Error('Invalid subscriptions');
}
var buffer = new Buffer(1 + calcLengthLength(length) + length)
, pos = 0;
// Generate header
buffer.writeUInt8(
protocol.codes['subscribe'] << protocol.CMD_SHIFT |
dup |
1 << protocol.QOS_SHIFT, pos++, true);
// Generate length
pos += writeLength(buffer, pos, length);
// Generate message ID
pos += writeNumber(buffer, pos, id);
// Generate subs
for (var i = 0; i < subs.length; i++) {
var sub = subs[i]
, topic = sub.topic
, qos = sub.qos;
// Write topic string
pos += writeString(buffer, pos, topic);
// Write qos
buffer.writeUInt8(qos, pos++, true);
}
return buffer;
}
function suback(opts) {
var opts = opts || {}
, id = opts.messageId
, granted = opts.granted;
var length = 0;
// Check message id
if ('number' !== typeof id) {
throw new Error('Invalid message id');
} else {
length += 2;
}
// Check granted qos vector
if ('object' === typeof granted && granted.length) {
for (var i = 0; i < granted.length; i += 1) {
if ('number' !== typeof granted[i]) {
throw new Error('Invalid qos vector');
}
length += 1;
}
} else {
throw new Error('Invalid qos vector');
}
var buffer = new Buffer(1 + calcLengthLength(length) + length)
, pos = 0;
// Header
buffer.writeUInt8(protocol.codes['suback'] << protocol.CMD_SHIFT, pos++, true);
// Length
pos += writeLength(buffer, pos, length);
// Message ID
pos += writeNumber(buffer, pos, id);
// Subscriptions
for (var i = 0; i < granted.length; i++) {
buffer.writeUInt8(granted[i], pos++, true);
}
return buffer;
}
function unsubscribe(opts) {
var opts = opts || {}
, id = opts.messageId
, dup = opts.dup ? protocol.DUP_MASK : 0
, unsubs = opts.unsubscriptions;
var length = 0;
// Check message id
if ('number' !== typeof id) {
throw new Error('Invalid message id');
} else {
length += 2;
}
// Check unsubs
if ('object' === typeof unsubs && unsubs.length) {
for (var i = 0; i < unsubs.length; i += 1) {
if ('string' !== typeof unsubs[i]) {
throw new Error('Invalid unsubscriptions');
}
length += Buffer.byteLength(unsubs[i]) + 2;
}
} else {
throw new Error('Invalid unsubscriptions');
}
var buffer = new Buffer(1 + calcLengthLength(length) + length)
, pos = 0;
// Header
buffer[pos++] =
protocol.codes['unsubscribe'] << protocol.CMD_SHIFT |
dup |
1 << protocol.QOS_SHIFT;
// Length
pos += writeLength(buffer, pos, length);
// Message ID
pos += writeNumber(buffer, pos, id);
// Unsubs
for (var i = 0; i < unsubs.length; i++) {
pos += writeString(buffer, pos, unsubs[i]);
}
return buffer;
}
function emptyPacket(opts) {
var buf = new Buffer(2);
buf[0] = protocol.codes[opts.cmd] << 4;
buf[1] = 0;
return buf;
}
/**
* calcLengthLength - calculate the length of the remaining
* length field
*
* @api private
*/
function calcLengthLength(length) {
if (length >= 0 && length < 128) {
return 1
} else if (length >= 128 && length < 16384) {
return 2
} else if (length >= 16384 && length < 2097152) {
return 3
} else if (length >= 2097152 && length < 268435456) {
return 4
} else {
return 0
}
}
/**
* writeLength - write an MQTT style length field to the buffer
*
* @param <Buffer> buffer - destination
* @param <Number> pos - offset
* @param <Number> length - length (>0)
* @returns <Number> number of bytes written
*
* @api private
*/
function writeLength(buffer, pos, length) {
var digit = 0
, origPos = pos
do {
digit = length % 128 | 0
length = length / 128 | 0
if (length > 0) {
digit = digit | 0x80
}
buffer.writeUInt8(digit, pos++, true)
} while (length > 0)
return pos - origPos
}
/**
* 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(buffer, pos, string) {
var strlen = Buffer.byteLength(string)
writeNumber(buffer, pos, strlen)
writeStringNoPos(buffer, pos + 2, string)
return strlen + 2
}
function writeStringNoPos(buffer, pos, string) {
buffer.write(string, pos)
}
/**
* write_buffer - write buffer to buffer
*
* @param <Buffer> buffer - dest buffer
* @param <Number> pos - offset
* @param <Buffer> src - source buffer
* @return <Number> number of bytes written
*
* @api private
*/
function writeBuffer(buffer, pos, src) {
src.copy(buffer, pos)
return src.length
}
/**
* 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 writeNumber(buffer, pos, number) {
buffer.writeUInt8(number >> 8, pos, true)
buffer.writeUInt8(number & 0x00FF, pos + 1, true)
return 2
}
/**
* 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(buffer, pos, toWrite) {
var written = 0
if (toWrite && typeof toWrite === 'string') {
written += writeString(buffer, pos + written, toWrite)
} else if (toWrite) {
written += writeNumber(buffer, pos + written, toWrite.length)
written += writeBuffer(buffer, pos + written, toWrite)
} else {
written += writeNumber(buffer, pos + written, 0)
}
return written
}
function byteLength(bufOrString) {
if (Buffer.isBuffer(bufOrString)) {
return bufOrString.length
} else {
return Buffer.byteLength(bufOrString)
}
}
module.exports = generate