pax_global_header00006660000000000000000000000064147615530240014521gustar00rootroot0000000000000052 comment=e39fb28c10628720fb50e5eb355492684ff0caaa
mqtt-packet-9.0.2/000077500000000000000000000000001476155302400137635ustar00rootroot00000000000000mqtt-packet-9.0.2/.github/000077500000000000000000000000001476155302400153235ustar00rootroot00000000000000mqtt-packet-9.0.2/.github/workflows/000077500000000000000000000000001476155302400173605ustar00rootroot00000000000000mqtt-packet-9.0.2/.github/workflows/ci.yml000066400000000000000000000006721476155302400205030ustar00rootroot00000000000000name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: |
npm install
- name: Run tests
run: |
npm run ci
mqtt-packet-9.0.2/.gitignore000066400000000000000000000010271476155302400157530ustar00rootroot00000000000000# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
mqtt-packet-9.0.2/.npmrc000066400000000000000000000000231476155302400150760ustar00rootroot00000000000000package-lock=false
mqtt-packet-9.0.2/CONTRIBUTING.md000066400000000000000000000023251476155302400162160ustar00rootroot00000000000000# mqtt-packet is an OPEN Open Source Project
-----------------------------------------
## What?
Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
## Rules
There are a few basic ground-rules for contributors:
1. **No `--force` pushes** or modifying the Git history in any way.
1. **Non-master branches** ought to be used for ongoing work.
1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors.
1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor.
1. Contributors should attempt to adhere to the prevailing code-style.
## Releases
Declaring formal releases remains the prerogative of the project maintainer.
## Changes to this arrangement
This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
-----------------------------------------
mqtt-packet-9.0.2/LICENSE.md000066400000000000000000000023411476155302400153670ustar00rootroot00000000000000The MIT License (MIT)
=====================
Copyright (c) 2014-2017 mqtt-packet contributors
---------------------------------------
*mqtt-packet contributors listed at *
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
mqtt-packet-9.0.2/README.md000066400000000000000000000255741476155302400152570ustar00rootroot00000000000000mqtt-packet
===========
Encode and Decode MQTT 3.1.1, 5.0 packets the node way.
[](https://github.com/feross/standard)
* Installation
* Examples
* Packets
* API
* Contributing
* License & copyright
This library is tested with node v6, v8, v10, v12 and v14. The last version to support
older versions of node was mqtt-packet@4.1.2.
Installation
------------
```bash
npm install mqtt-packet --save
```
Examples
--------
### Generating
```js
const mqtt = require('mqtt-packet');
const object = {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: 10,
topic: 'test',
payload: 'test' // Can also be a Buffer
};
const opts = { protocolVersion: 4 }; // default is 4. Usually, opts is a connect packet
console.log(mqtt.generate(object))
// Prints:
//
//
//
// Which is the same as:
//
// Buffer.from([
// 48, 10, // Header (publish)
// 0, 4, // Topic length
// 116, 101, 115, 116, // Topic (test)
// 116, 101, 115, 116 // Payload (test)
// ])
```
### Parsing
```js
const mqtt = require('mqtt-packet');
const opts = { protocolVersion: 4 }; // default is 4. Usually, opts is a connect packet
const parser = mqtt.parser(opts);
// Synchronously emits all the parsed packets
parser.on('packet', packet => {
console.log(packet)
// Prints:
//
// {
// cmd: 'publish',
// retain: false,
// qos: 0,
// dup: false,
// length: 10,
// topic: 'test',
// payload:
// }
})
parser.parse(Buffer.from([
48, 10, // Header (publish)
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
116, 101, 115, 116 // Payload (test)
]))
// Returns the number of bytes left in the parser
```
API
---
* mqtt#generate()
* mqtt#writeToStream()
* mqtt#parser()
### mqtt.generate(object, [opts])
Generates a `Buffer` containing an MQTT packet.
The object must be one of the ones specified by the [packets](#packets)
section. Throws an `Error` if a packet cannot be generated.
### mqtt.writeToStream(object, stream, [opts])
Writes the mqtt packet defined by `object` to the given stream.
The object must be one of the ones specified by the [packets](#packets)
section. Emits an `Error` on the stream if a packet cannot be generated.
On node >= 0.12, this function automatically calls `cork()` on your stream,
and then it calls `uncork()` on the next tick.
By default cache for number buffers is enabled.
It creates a list of buffers for faster write. To disable cache set `mqtt.writeToStream.cacheNumbers = false`.
Should be set before any `writeToStream` calls.
### mqtt.parser([opts])
Returns a new `Parser` object. `Parser` inherits from `EventEmitter` and
will emit:
* `packet`, when a new packet is parsed, according to
[packets](#packets)
* `error`, if an error happens
#### Parser.parse(buffer)
Parses a given `Buffer` and emits synchronously all the MQTT packets that
are included. Returns the number of bytes left to parse.
If an error happens, an `error` event will be emitted, but no `packet` events
will be emitted after that. Calling `parse()` again clears the error and
previous buffer, as if you created a new `Parser`.
Packets
-------
This section describes the format of all packets emitted by the `Parser`
and that you can input to `generate`.
### Connect
```js
{
cmd: 'connect',
protocolId: 'MQTT', // Or 'MQIsdp' in MQTT 3.1 and 5.0
protocolVersion: 4, // Or 3 in MQTT 3.1, or 5 in MQTT 5.0
clean: true, // Can also be false
clientId: 'my-device',
keepalive: 0, // Seconds which can be any positive number, with 0 as the default setting
username: 'matteo',
password: Buffer.from('collina'), // Passwords are buffers
will: {
topic: 'mydevice/status',
payload: Buffer.from('dead'), // Payloads are buffers
properties: { // MQTT 5.0
willDelayInterval: 1234,
payloadFormatIndicator: false,
messageExpiryInterval: 4321,
contentType: 'test',
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
'test': 'test'
}
}
},
properties: { // MQTT 5.0 properties
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumPacketSize: 100,
topicAliasMaximum: 456,
requestResponseInformation: true,
requestProblemInformation: true,
userProperties: {
'test': 'test'
},
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
}
}
```
If `protocolVersion` is 3, `clientId` is mandatory and `generate` will throw if
missing.
If `password` or `will.payload` are passed as strings, they will
automatically be converted into a `Buffer`.
### Connack
```js
{
cmd: 'connack',
returnCode: 0, // Or whatever else you see fit MQTT < 5.0
sessionPresent: false, // Can also be true.
reasonCode: 0, // reason code MQTT 5.0
properties: { // MQTT 5.0 properties
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumQoS: 1,
retainAvailable: true,
maximumPacketSize: 100,
assignedClientIdentifier: 'test',
topicAliasMaximum: 456,
reasonString: 'test',
userProperties: {
'test': 'test'
},
wildcardSubscriptionAvailable: true,
subscriptionIdentifiersAvailable: true,
sharedSubscriptionAvailable: false,
serverKeepAlive: 1234,
responseInformation: 'test',
serverReference: 'test',
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
}
}
```
The only mandatory argument is `returnCode`, as `generate` will throw if
missing.
### Subscribe
```js
{
cmd: 'subscribe',
messageId: 42,
properties: { // MQTT 5.0 properties
subscriptionIdentifier: 145,
userProperties: {
test: 'test'
}
}
subscriptions: [{
topic: 'test',
qos: 0,
nl: false, // no Local MQTT 5.0 flag
rap: true, // Retain as Published MQTT 5.0 flag
rh: 1 // Retain Handling MQTT 5.0
}]
}
```
All properties are mandatory.
### Suback
```js
{
cmd: 'suback',
messageId: 42,
properties: { // MQTT 5.0 properties
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
granted: [0, 1, 2, 128]
}
```
All the granted qos __must__ be < 256, as they are encoded as UInt8.
All properties are mandatory.
### Unsubscribe
```js
{
cmd: 'unsubscribe',
messageId: 42,
properties: { // MQTT 5.0 properties
userProperties: {
'test': 'test'
}
}
unsubscriptions: [
'test',
'a/topic'
]
}
```
All properties are mandatory.
### Unsuback
```js
{
cmd: 'unsuback',
messageId: 42,
properties: { // MQTT 5.0 properties
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
}
```
All properties are mandatory.
### Publish
```js
{
cmd: 'publish',
messageId: 42,
qos: 2,
dup: false,
topic: 'test',
payload: Buffer.from('test'),
retain: false,
properties: { // optional properties MQTT 5.0
payloadFormatIndicator: true,
messageExpiryInterval: 4321,
topicAlias: 100,
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
'test': 'test'
},
subscriptionIdentifier: 120, // can be an Array in message from broker, if message included in few another subscriptions
contentType: 'test'
}
}
```
Only the `topic` property is mandatory.
Both `topic` and `payload` can be `Buffer` objects instead of strings.
`messageId` is mandatory for `qos > 0`.
### Puback
```js
{
cmd: 'puback',
messageId: 42,
reasonCode: 16, // only for MQTT 5.0
properties: { // MQTT 5.0 properties
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
}
```
The only mandatory property is `messageId`, as `generate` will throw if
missing.
### Pubrec
```js
{
cmd: 'pubrec',
messageId: 42,
reasonCode: 16, // only for MQTT 5.0
properties: { // properties MQTT 5.0
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
}
```
The only mandatory property is `messageId`, as `generate` will throw if
missing.
### Pubrel
```js
{
cmd: 'pubrel',
messageId: 42,
reasonCode: 16, // only for MQTT 5.0
properties: { // properties MQTT 5.0
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
}
```
The only mandatory property is `messageId`, as `generate` will throw if
missing.
### Pubcomp
```js
{
cmd: 'pubcomp',
messageId: 42,
reasonCode: 16, // only for MQTT 5.0
properties: { // properties MQTT 5.0
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
}
```
The only mandatory property is `messageId`, as `generate` will throw if
missing.
### Pingreq
```js
{
cmd: 'pingreq'
}
```
### Pingresp
```js
{
cmd: 'pingresp'
}
```
### Disconnect
```js
{
cmd: 'disconnect',
reasonCode: 0, // MQTT 5.0 code
properties: { // properties MQTT 5.0
sessionExpiryInterval: 145,
reasonString: 'test',
userProperties: {
'test': 'test'
},
serverReference: 'test'
}
}
```
### Auth
```js
{
cmd: 'auth',
reasonCode: 0, // MQTT 5.0 code
properties: { // properties MQTT 5.0
authenticationMethod: 'test',
authenticationData: Buffer.from([0, 1, 2, 3]),
reasonString: 'test',
userProperties: {
'test': 'test'
}
}
}
```
Contributing
------------
mqtt-packet is an **OPEN Open Source Project**. This means that:
> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
See the [CONTRIBUTING.md](https://github.com/mqttjs/mqtt-packet/blob/master/CONTRIBUTING.md) file for more details.
### Contributors
mqtt-packet is only possible due to the excellent work of the following contributors:
License
-------
MIT
mqtt-packet-9.0.2/benchmarks/000077500000000000000000000000001476155302400161005ustar00rootroot00000000000000mqtt-packet-9.0.2/benchmarks/generate.js000066400000000000000000000007021476155302400202270ustar00rootroot00000000000000const mqtt = require('../')
const max = 100000
let i
const buf = Buffer.from('test')
// initialize it
mqtt.generate({
cmd: 'publish',
topic: 'test',
payload: buf
})
const start = Date.now()
for (i = 0; i < max; i++) {
mqtt.generate({
cmd: 'publish',
topic: 'test',
payload: buf
})
}
const time = Date.now() - start
console.log('Total time', time)
console.log('Total packets', max)
console.log('Packet/s', max / time * 1000)
mqtt-packet-9.0.2/benchmarks/generateNet.js000066400000000000000000000017401476155302400207010ustar00rootroot00000000000000const mqtt = require('../')
const max = 1000000
let i = 0
const start = Date.now()
let time
const buf = Buffer.allocUnsafe(10)
const net = require('net')
const server = net.createServer(handle)
let dest
buf.fill('test')
function handle (sock) {
sock.resume()
}
server.listen(0, () => {
dest = net.connect(server.address())
dest.on('connect', tickWait)
dest.on('drain', tickWait)
dest.on('finish', () => {
time = Date.now() - start
console.log('Total time', time)
console.log('Total packets', max)
console.log('Packet/s', max / time * 1000)
server.close()
})
})
function tickWait () {
// console.log('tickWait', i)
let res = true
// var toSend = new Buffer(5 + buf.length)
for (; i < max && res; i++) {
res = dest.write(mqtt.generate({
cmd: 'publish',
topic: 'test',
payload: buf
}))
// buf.copy(toSend, 5)
// res = dest.write(toSend, 'buffer')
// console.log(res)
}
if (i >= max) {
dest.end()
}
}
mqtt-packet-9.0.2/benchmarks/parse.js000066400000000000000000000007471476155302400175600ustar00rootroot00000000000000const mqtt = require('../')
const parser = mqtt.parser()
const max = 10000000
let i
const start = Date.now() / 1000
for (i = 0; i < max; i++) {
parser.parse(Buffer.from([
48, 10, // Header (publish)
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
116, 101, 115, 116 // Payload (test)
]))
}
const time = Date.now() / 1000 - start
console.log('Total packets', max)
console.log('Total time', Math.round(time * 100) / 100)
console.log('Packet/s', max / time)
mqtt-packet-9.0.2/benchmarks/writeToStream.js000066400000000000000000000016401476155302400212500ustar00rootroot00000000000000const mqtt = require('../')
const max = 1000000
let i = 0
const start = Date.now()
let time
const buf = Buffer.allocUnsafe(10)
const net = require('net')
const server = net.createServer(handle)
let dest
function handle (sock) {
sock.resume()
}
buf.fill('test')
server.listen(0, () => {
dest = net.connect(server.address())
dest.on('connect', tickWait)
dest.on('drain', tickWait)
dest.on('finish', () => {
time = Date.now() - start
console.log('Total time', time)
console.log('Total packets', max)
console.log('Packet/s', max / time * 1000)
server.close()
})
})
function tickWait () {
let res = true
// var toSend = new Buffer(5)
for (; i < max && res; i++) {
res = mqtt.writeToStream({
cmd: 'publish',
topic: 'test',
payload: buf
}, dest)
// dest.write(toSend, 'buffer')
// res = dest.write(buf, 'buffer')
}
if (i >= max) {
dest.end()
}
}
mqtt-packet-9.0.2/constants.js000066400000000000000000000172551476155302400163470ustar00rootroot00000000000000/* Protocol - protocol constants */
const protocol = module.exports
const { Buffer } = require('buffer')
/* Command code => mnemonic */
protocol.types = {
0: 'reserved',
1: 'connect',
2: 'connack',
3: 'publish',
4: 'puback',
5: 'pubrec',
6: 'pubrel',
7: 'pubcomp',
8: 'subscribe',
9: 'suback',
10: 'unsubscribe',
11: 'unsuback',
12: 'pingreq',
13: 'pingresp',
14: 'disconnect',
15: 'auth'
}
protocol.requiredHeaderFlags = {
1: 0, // 'connect'
2: 0, // 'connack'
4: 0, // 'puback'
5: 0, // 'pubrec'
6: 2, // 'pubrel'
7: 0, // 'pubcomp'
8: 2, // 'subscribe'
9: 0, // 'suback'
10: 2, // 'unsubscribe'
11: 0, // 'unsuback'
12: 0, // 'pingreq'
13: 0, // 'pingresp'
14: 0, // 'disconnect'
15: 0 // 'auth'
}
protocol.requiredHeaderFlagsErrors = {}
for (const k in protocol.requiredHeaderFlags) {
const v = protocol.requiredHeaderFlags[k]
protocol.requiredHeaderFlagsErrors[k] = 'Invalid header flag bits, must be 0x' + v.toString(16) + ' for ' + protocol.types[k] + ' packet'
}
/* Mnemonic => Command code */
protocol.codes = {}
for (const k in protocol.types) {
const v = protocol.types[k]
protocol.codes[v] = k
}
/* Header */
protocol.CMD_SHIFT = 4
protocol.CMD_MASK = 0xF0
protocol.DUP_MASK = 0x08
protocol.QOS_MASK = 0x03
protocol.QOS_SHIFT = 1
protocol.RETAIN_MASK = 0x01
/* Length */
protocol.VARBYTEINT_MASK = 0x7F
protocol.VARBYTEINT_FIN_MASK = 0x80
protocol.VARBYTEINT_MAX = 268435455
/* Connack */
protocol.SESSIONPRESENT_MASK = 0x01
protocol.SESSIONPRESENT_HEADER = Buffer.from([protocol.SESSIONPRESENT_MASK])
protocol.CONNACK_HEADER = Buffer.from([protocol.codes.connack << protocol.CMD_SHIFT])
/* Connect */
protocol.USERNAME_MASK = 0x80
protocol.PASSWORD_MASK = 0x40
protocol.WILL_RETAIN_MASK = 0x20
protocol.WILL_QOS_MASK = 0x18
protocol.WILL_QOS_SHIFT = 3
protocol.WILL_FLAG_MASK = 0x04
protocol.CLEAN_SESSION_MASK = 0x02
protocol.CONNECT_HEADER = Buffer.from([protocol.codes.connect << protocol.CMD_SHIFT])
/* Properties */
protocol.properties = {
sessionExpiryInterval: 17,
willDelayInterval: 24,
receiveMaximum: 33,
maximumPacketSize: 39,
topicAliasMaximum: 34,
requestResponseInformation: 25,
requestProblemInformation: 23,
userProperties: 38,
authenticationMethod: 21,
authenticationData: 22,
payloadFormatIndicator: 1,
messageExpiryInterval: 2,
contentType: 3,
responseTopic: 8,
correlationData: 9,
maximumQoS: 36,
retainAvailable: 37,
assignedClientIdentifier: 18,
reasonString: 31,
wildcardSubscriptionAvailable: 40,
subscriptionIdentifiersAvailable: 41,
sharedSubscriptionAvailable: 42,
serverKeepAlive: 19,
responseInformation: 26,
serverReference: 28,
topicAlias: 35,
subscriptionIdentifier: 11
}
protocol.propertiesCodes = {}
for (const prop in protocol.properties) {
const id = protocol.properties[prop]
protocol.propertiesCodes[id] = prop
}
protocol.propertiesTypes = {
sessionExpiryInterval: 'int32',
willDelayInterval: 'int32',
receiveMaximum: 'int16',
maximumPacketSize: 'int32',
topicAliasMaximum: 'int16',
requestResponseInformation: 'byte',
requestProblemInformation: 'byte',
userProperties: 'pair',
authenticationMethod: 'string',
authenticationData: 'binary',
payloadFormatIndicator: 'byte',
messageExpiryInterval: 'int32',
contentType: 'string',
responseTopic: 'string',
correlationData: 'binary',
maximumQoS: 'int8',
retainAvailable: 'byte',
assignedClientIdentifier: 'string',
reasonString: 'string',
wildcardSubscriptionAvailable: 'byte',
subscriptionIdentifiersAvailable: 'byte',
sharedSubscriptionAvailable: 'byte',
serverKeepAlive: 'int16',
responseInformation: 'string',
serverReference: 'string',
topicAlias: 'int16',
subscriptionIdentifier: 'var'
}
function genHeader (type) {
return [0, 1, 2].map(qos => {
return [0, 1].map(dup => {
return [0, 1].map(retain => {
const buf = Buffer.alloc(1)
buf.writeUInt8(
protocol.codes[type] << protocol.CMD_SHIFT |
(dup ? protocol.DUP_MASK : 0) |
qos << protocol.QOS_SHIFT | retain, 0, true)
return buf
})
})
})
}
/* Publish */
protocol.PUBLISH_HEADER = genHeader('publish')
/* Subscribe */
protocol.SUBSCRIBE_HEADER = genHeader('subscribe')
protocol.SUBSCRIBE_OPTIONS_QOS_MASK = 0x03
protocol.SUBSCRIBE_OPTIONS_NL_MASK = 0x01
protocol.SUBSCRIBE_OPTIONS_NL_SHIFT = 2
protocol.SUBSCRIBE_OPTIONS_RAP_MASK = 0x01
protocol.SUBSCRIBE_OPTIONS_RAP_SHIFT = 3
protocol.SUBSCRIBE_OPTIONS_RH_MASK = 0x03
protocol.SUBSCRIBE_OPTIONS_RH_SHIFT = 4
protocol.SUBSCRIBE_OPTIONS_RH = [0x00, 0x10, 0x20]
protocol.SUBSCRIBE_OPTIONS_NL = 0x04
protocol.SUBSCRIBE_OPTIONS_RAP = 0x08
protocol.SUBSCRIBE_OPTIONS_QOS = [0x00, 0x01, 0x02]
/* Unsubscribe */
protocol.UNSUBSCRIBE_HEADER = genHeader('unsubscribe')
/* Confirmations */
protocol.ACKS = {
unsuback: genHeader('unsuback'),
puback: genHeader('puback'),
pubcomp: genHeader('pubcomp'),
pubrel: genHeader('pubrel'),
pubrec: genHeader('pubrec')
}
protocol.SUBACK_HEADER = Buffer.from([protocol.codes.suback << protocol.CMD_SHIFT])
/* Protocol versions */
protocol.VERSION3 = Buffer.from([3])
protocol.VERSION4 = Buffer.from([4])
protocol.VERSION5 = Buffer.from([5])
protocol.VERSION131 = Buffer.from([131])
protocol.VERSION132 = Buffer.from([132])
/* QoS */
protocol.QOS = [0, 1, 2].map(qos => {
return Buffer.from([qos])
})
/* Empty packets */
protocol.EMPTY = {
pingreq: Buffer.from([protocol.codes.pingreq << 4, 0]),
pingresp: Buffer.from([protocol.codes.pingresp << 4, 0]),
disconnect: Buffer.from([protocol.codes.disconnect << 4, 0])
}
protocol.MQTT5_PUBACK_PUBREC_CODES = {
0x00: 'Success',
0x10: 'No matching subscribers',
0x80: 'Unspecified error',
0x83: 'Implementation specific error',
0x87: 'Not authorized',
0x90: 'Topic Name invalid',
0x91: 'Packet identifier in use',
0x97: 'Quota exceeded',
0x99: 'Payload format invalid'
}
protocol.MQTT5_PUBREL_PUBCOMP_CODES = {
0x00: 'Success',
0x92: 'Packet Identifier not found'
}
protocol.MQTT5_SUBACK_CODES = {
0x00: 'Granted QoS 0',
0x01: 'Granted QoS 1',
0x02: 'Granted QoS 2',
0x80: 'Unspecified error',
0x83: 'Implementation specific error',
0x87: 'Not authorized',
0x8F: 'Topic Filter invalid',
0x91: 'Packet Identifier in use',
0x97: 'Quota exceeded',
0x9E: 'Shared Subscriptions not supported',
0xA1: 'Subscription Identifiers not supported',
0xA2: 'Wildcard Subscriptions not supported'
}
protocol.MQTT5_UNSUBACK_CODES = {
0x00: 'Success',
0x11: 'No subscription existed',
0x80: 'Unspecified error',
0x83: 'Implementation specific error',
0x87: 'Not authorized',
0x8F: 'Topic Filter invalid',
0x91: 'Packet Identifier in use'
}
protocol.MQTT5_DISCONNECT_CODES = {
0x00: 'Normal disconnection',
0x04: 'Disconnect with Will Message',
0x80: 'Unspecified error',
0x81: 'Malformed Packet',
0x82: 'Protocol Error',
0x83: 'Implementation specific error',
0x87: 'Not authorized',
0x89: 'Server busy',
0x8B: 'Server shutting down',
0x8D: 'Keep Alive timeout',
0x8E: 'Session taken over',
0x8F: 'Topic Filter invalid',
0x90: 'Topic Name invalid',
0x93: 'Receive Maximum exceeded',
0x94: 'Topic Alias invalid',
0x95: 'Packet too large',
0x96: 'Message rate too high',
0x97: 'Quota exceeded',
0x98: 'Administrative action',
0x99: 'Payload format invalid',
0x9A: 'Retain not supported',
0x9B: 'QoS not supported',
0x9C: 'Use another server',
0x9D: 'Server moved',
0x9E: 'Shared Subscriptions not supported',
0x9F: 'Connection rate exceeded',
0xA0: 'Maximum connect time',
0xA1: 'Subscription Identifiers not supported',
0xA2: 'Wildcard Subscriptions not supported'
}
protocol.MQTT5_AUTH_CODES = {
0x00: 'Success',
0x18: 'Continue authentication',
0x19: 'Re-authenticate'
}
mqtt-packet-9.0.2/generate.js000066400000000000000000000023251476155302400161150ustar00rootroot00000000000000const writeToStream = require('./writeToStream')
const { EventEmitter } = require('events')
const { Buffer } = require('buffer')
function generate (packet, opts) {
const stream = new Accumulator()
writeToStream(packet, stream, opts)
return stream.concat()
}
class Accumulator extends EventEmitter {
constructor () {
super()
this._array = new Array(20)
this._i = 0
}
write (chunk) {
this._array[this._i++] = chunk
return true
}
concat () {
let length = 0
const lengths = new Array(this._array.length)
const list = this._array
let pos = 0
let i
for (i = 0; i < list.length && list[i] !== undefined; i++) {
if (typeof list[i] !== 'string') lengths[i] = list[i].length
else lengths[i] = Buffer.byteLength(list[i])
length += lengths[i]
}
const result = Buffer.allocUnsafe(length)
for (i = 0; i < list.length && list[i] !== undefined; i++) {
if (typeof list[i] !== 'string') {
list[i].copy(result, pos)
pos += lengths[i]
} else {
result.write(list[i], pos)
pos += lengths[i]
}
}
return result
}
destroy (err) {
if (err) this.emit('error', err)
}
}
module.exports = generate
mqtt-packet-9.0.2/mqtt.js000066400000000000000000000002101476155302400152770ustar00rootroot00000000000000exports.parser = require('./parser').parser
exports.generate = require('./generate')
exports.writeToStream = require('./writeToStream')
mqtt-packet-9.0.2/numbers.js000066400000000000000000000024451476155302400160010ustar00rootroot00000000000000const { Buffer } = require('buffer')
const max = 65536
const cache = {}
// in node 6 Buffer.subarray returns a Uint8Array instead of a Buffer
// later versions return a Buffer
// alternative is Buffer.slice but that creates a new buffer
// creating new buffers takes time
// SubOk is only false on node < 8
const SubOk = Buffer.isBuffer(Buffer.from([1, 2]).subarray(0, 1))
function generateBuffer (i) {
const buffer = Buffer.allocUnsafe(2)
buffer.writeUInt8(i >> 8, 0)
buffer.writeUInt8(i & 0x00FF, 0 + 1)
return buffer
}
function generateCache () {
for (let i = 0; i < max; i++) {
cache[i] = generateBuffer(i)
}
}
function genBufVariableByteInt (num) {
const maxLength = 4 // max 4 bytes
let digit = 0
let pos = 0
const buffer = Buffer.allocUnsafe(maxLength)
do {
digit = num % 128 | 0
num = num / 128 | 0
if (num > 0) digit = digit | 0x80
buffer.writeUInt8(digit, pos++)
} while (num > 0 && pos < maxLength)
if (num > 0) {
pos = 0
}
return SubOk ? buffer.subarray(0, pos) : buffer.slice(0, pos)
}
function generate4ByteBuffer (num) {
const buffer = Buffer.allocUnsafe(4)
buffer.writeUInt32BE(num, 0)
return buffer
}
module.exports = {
cache,
generateCache,
generateNumber: generateBuffer,
genBufVariableByteInt,
generate4ByteBuffer
}
mqtt-packet-9.0.2/package.json000066400000000000000000000023501476155302400162510ustar00rootroot00000000000000{
"name": "mqtt-packet",
"version": "9.0.2",
"description": "Parse and generate MQTT packets like a breeze",
"main": "mqtt.js",
"types": "types/index.d.ts",
"contributors": [
"Matteo Collina (https://github.com/mcollina)",
"Adam Rudd ",
"Peter Sorowka (https://github.com/psorowka)",
"Wouter Klijn (https://github.com/wuhkuh)",
"Siarhei Buntsevich (https://github.com/scarry1992)"
],
"scripts": {
"test": "tape test.js | tap-spec && standard",
"ci": "tape test.js && node testRandom && standard"
},
"pre-commit": "test",
"repository": {
"type": "git",
"url": "https://github.com/mqttjs/mqtt-packet.git"
},
"keywords": [
"MQTT",
"packet",
"parse",
"publish",
"subscribe",
"pubsub"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/mqttjs/mqtt-packet/issues"
},
"homepage": "https://github.com/mqttjs/mqtt-packet",
"devDependencies": {
"pre-commit": "^1.2.2",
"readable-stream": "^4.4.2",
"standard": "^17.1.0",
"tap-spec": "^5.0.0",
"tape": "^5.7.2"
},
"dependencies": {
"bl": "^6.0.8",
"debug": "^4.3.4",
"process-nextick-args": "^2.0.1"
}
}
mqtt-packet-9.0.2/packet.js000066400000000000000000000003261476155302400155710ustar00rootroot00000000000000class Packet {
constructor () {
this.cmd = null
this.retain = false
this.qos = 0
this.dup = false
this.length = -1
this.topic = null
this.payload = null
}
}
module.exports = Packet
mqtt-packet-9.0.2/parser.js000066400000000000000000000576301476155302400156300ustar00rootroot00000000000000const bl = require('bl')
const { EventEmitter } = require('events')
const Packet = require('./packet')
const constants = require('./constants')
const debug = require('debug')('mqtt-packet:parser')
class Parser extends EventEmitter {
constructor () {
super()
this.parser = this.constructor.parser
}
static parser (opt) {
if (!(this instanceof Parser)) return (new Parser()).parser(opt)
this.settings = opt || {}
this._states = [
'_parseHeader',
'_parseLength',
'_parsePayload',
'_newPacket'
]
this._resetState()
return this
}
_resetState () {
debug('_resetState: resetting packet, error, _list, and _stateCounter')
this.packet = new Packet()
this.error = null
this._list = bl()
this._stateCounter = 0
}
parse (buf) {
if (this.error) this._resetState()
this._list.append(buf)
debug('parse: current state: %s', this._states[this._stateCounter])
while ((this.packet.length !== -1 || this._list.length > 0) &&
this[this._states[this._stateCounter]]() &&
!this.error) {
this._stateCounter++
debug('parse: state complete. _stateCounter is now: %d', this._stateCounter)
debug('parse: packet.length: %d, buffer list length: %d', this.packet.length, this._list.length)
if (this._stateCounter >= this._states.length) this._stateCounter = 0
}
debug('parse: exited while loop. packet: %d, buffer list length: %d', this.packet.length, this._list.length)
return this._list.length
}
_parseHeader () {
// There is at least one byte in the buffer
const zero = this._list.readUInt8(0)
const cmdIndex = zero >> constants.CMD_SHIFT
this.packet.cmd = constants.types[cmdIndex]
const headerFlags = zero & 0xf
const requiredHeaderFlags = constants.requiredHeaderFlags[cmdIndex]
if (requiredHeaderFlags != null && headerFlags !== requiredHeaderFlags) {
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
return this._emitError(new Error(constants.requiredHeaderFlagsErrors[cmdIndex]))
}
this.packet.retain = (zero & constants.RETAIN_MASK) !== 0
this.packet.qos = (zero >> constants.QOS_SHIFT) & constants.QOS_MASK
if (this.packet.qos > 2) {
return this._emitError(new Error('Packet must not have both QoS bits set to 1'))
}
this.packet.dup = (zero & constants.DUP_MASK) !== 0
debug('_parseHeader: packet: %o', this.packet)
this._list.consume(1)
return true
}
_parseLength () {
// There is at least one byte in the list
const result = this._parseVarByteNum(true)
if (result) {
this.packet.length = result.value
this._list.consume(result.bytes)
}
debug('_parseLength %d', result.value)
return !!result
}
_parsePayload () {
debug('_parsePayload: payload %O', this._list)
let result = false
// Do we have a payload? Do we have enough data to complete the payload?
// PINGs have no payload
if (this.packet.length === 0 || this._list.length >= this.packet.length) {
this._pos = 0
switch (this.packet.cmd) {
case 'connect':
this._parseConnect()
break
case 'connack':
this._parseConnack()
break
case 'publish':
this._parsePublish()
break
case 'puback':
case 'pubrec':
case 'pubrel':
case 'pubcomp':
this._parseConfirmation()
break
case 'subscribe':
this._parseSubscribe()
break
case 'suback':
this._parseSuback()
break
case 'unsubscribe':
this._parseUnsubscribe()
break
case 'unsuback':
this._parseUnsuback()
break
case 'pingreq':
case 'pingresp':
// These are empty, nothing to do
break
case 'disconnect':
this._parseDisconnect()
break
case 'auth':
this._parseAuth()
break
default:
this._emitError(new Error('Not supported'))
}
result = true
}
debug('_parsePayload complete result: %s', result)
return result
}
_parseConnect () {
debug('_parseConnect')
let topic // Will topic
let payload // Will payload
let password // Password
let username // Username
const flags = {}
const packet = this.packet
// Parse protocolId
const protocolId = this._parseString()
if (protocolId === null) return this._emitError(new Error('Cannot parse protocolId'))
if (protocolId !== 'MQTT' && protocolId !== 'MQIsdp') {
return this._emitError(new Error('Invalid protocolId'))
}
packet.protocolId = protocolId
// Parse constants version number
if (this._pos >= this._list.length) return this._emitError(new Error('Packet too short'))
packet.protocolVersion = this._list.readUInt8(this._pos)
if (packet.protocolVersion >= 128) {
packet.bridgeMode = true
packet.protocolVersion = packet.protocolVersion - 128
}
if (packet.protocolVersion !== 3 && packet.protocolVersion !== 4 && packet.protocolVersion !== 5) {
return this._emitError(new Error('Invalid protocol version'))
}
this._pos++
if (this._pos >= this._list.length) {
return this._emitError(new Error('Packet too short'))
}
if (this._list.readUInt8(this._pos) & 0x1) {
// The Server MUST validate that the reserved flag in the CONNECT Control Packet is set to zero and disconnect the Client if it is not zero [MQTT-3.1.2-3]
return this._emitError(new Error('Connect flag bit 0 must be 0, but got 1'))
}
// Parse connect flags
flags.username = (this._list.readUInt8(this._pos) & constants.USERNAME_MASK)
flags.password = (this._list.readUInt8(this._pos) & constants.PASSWORD_MASK)
flags.will = (this._list.readUInt8(this._pos) & constants.WILL_FLAG_MASK)
const willRetain = !!(this._list.readUInt8(this._pos) & constants.WILL_RETAIN_MASK)
const willQos = (this._list.readUInt8(this._pos) &
constants.WILL_QOS_MASK) >> constants.WILL_QOS_SHIFT
if (flags.will) {
packet.will = {}
packet.will.retain = willRetain
packet.will.qos = willQos
} else {
if (willRetain) {
return this._emitError(new Error('Will Retain Flag must be set to zero when Will Flag is set to 0'))
}
if (willQos) {
return this._emitError(new Error('Will QoS must be set to zero when Will Flag is set to 0'))
}
}
packet.clean = (this._list.readUInt8(this._pos) & constants.CLEAN_SESSION_MASK) !== 0
this._pos++
// Parse keepalive
packet.keepalive = this._parseNum()
if (packet.keepalive === -1) return this._emitError(new Error('Packet too short'))
// parse properties
if (packet.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
// Parse clientId
const clientId = this._parseString()
if (clientId === null) return this._emitError(new Error('Packet too short'))
packet.clientId = clientId
debug('_parseConnect: packet.clientId: %s', packet.clientId)
if (flags.will) {
if (packet.protocolVersion === 5) {
const willProperties = this._parseProperties()
if (Object.getOwnPropertyNames(willProperties).length) {
packet.will.properties = willProperties
}
}
// Parse will topic
topic = this._parseString()
if (topic === null) return this._emitError(new Error('Cannot parse will topic'))
packet.will.topic = topic
debug('_parseConnect: packet.will.topic: %s', packet.will.topic)
// Parse will payload
payload = this._parseBuffer()
if (payload === null) return this._emitError(new Error('Cannot parse will payload'))
packet.will.payload = payload
debug('_parseConnect: packet.will.paylaod: %s', packet.will.payload)
}
// Parse username
if (flags.username) {
username = this._parseString()
if (username === null) return this._emitError(new Error('Cannot parse username'))
packet.username = username
debug('_parseConnect: packet.username: %s', packet.username)
}
// Parse password
if (flags.password) {
password = this._parseBuffer()
if (password === null) return this._emitError(new Error('Cannot parse password'))
packet.password = password
}
// need for right parse auth packet and self set up
this.settings = packet
debug('_parseConnect: complete')
return packet
}
_parseConnack () {
debug('_parseConnack')
const packet = this.packet
if (this._list.length < 1) return null
const flags = this._list.readUInt8(this._pos++)
if (flags > 1) {
return this._emitError(new Error('Invalid connack flags, bits 7-1 must be set to 0'))
}
packet.sessionPresent = !!(flags & constants.SESSIONPRESENT_MASK)
if (this.settings.protocolVersion === 5) {
if (this._list.length >= 2) {
packet.reasonCode = this._list.readUInt8(this._pos++)
} else {
packet.reasonCode = 0
}
} else {
if (this._list.length < 2) return null
packet.returnCode = this._list.readUInt8(this._pos++)
}
if (packet.returnCode === -1 || packet.reasonCode === -1) return this._emitError(new Error('Cannot parse return code'))
// mqtt 5 properties
if (this.settings.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
debug('_parseConnack: complete')
}
_parsePublish () {
debug('_parsePublish')
const packet = this.packet
packet.topic = this._parseString()
if (packet.topic === null) return this._emitError(new Error('Cannot parse topic'))
// Parse messageId
if (packet.qos > 0) if (!this._parseMessageId()) { return }
// Properties mqtt 5
if (this.settings.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
packet.payload = this._list.slice(this._pos, packet.length)
debug('_parsePublish: payload from buffer list: %o', packet.payload)
}
_parseSubscribe () {
debug('_parseSubscribe')
const packet = this.packet
let topic
let options
let qos
let rh
let rap
let nl
let subscription
packet.subscriptions = []
if (!this._parseMessageId()) { return }
// Properties mqtt 5
if (this.settings.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
if (packet.length <= 0) { return this._emitError(new Error('Malformed subscribe, no payload specified')) }
while (this._pos < packet.length) {
// Parse topic
topic = this._parseString()
if (topic === null) return this._emitError(new Error('Cannot parse topic'))
if (this._pos >= packet.length) return this._emitError(new Error('Malformed Subscribe Payload'))
options = this._parseByte()
if (this.settings.protocolVersion === 5) {
if (options & 0xc0) {
return this._emitError(new Error('Invalid subscribe topic flag bits, bits 7-6 must be 0'))
}
} else {
if (options & 0xfc) {
return this._emitError(new Error('Invalid subscribe topic flag bits, bits 7-2 must be 0'))
}
}
qos = options & constants.SUBSCRIBE_OPTIONS_QOS_MASK
if (qos > 2) {
return this._emitError(new Error('Invalid subscribe QoS, must be <= 2'))
}
nl = ((options >> constants.SUBSCRIBE_OPTIONS_NL_SHIFT) & constants.SUBSCRIBE_OPTIONS_NL_MASK) !== 0
rap = ((options >> constants.SUBSCRIBE_OPTIONS_RAP_SHIFT) & constants.SUBSCRIBE_OPTIONS_RAP_MASK) !== 0
rh = (options >> constants.SUBSCRIBE_OPTIONS_RH_SHIFT) & constants.SUBSCRIBE_OPTIONS_RH_MASK
if (rh > 2) {
return this._emitError(new Error('Invalid retain handling, must be <= 2'))
}
subscription = { topic, qos }
// mqtt 5 options
if (this.settings.protocolVersion === 5) {
subscription.nl = nl
subscription.rap = rap
subscription.rh = rh
} else if (this.settings.bridgeMode) {
subscription.rh = 0
subscription.rap = true
subscription.nl = true
}
// Push pair to subscriptions
debug('_parseSubscribe: push subscription `%s` to subscription', subscription)
packet.subscriptions.push(subscription)
}
}
_parseSuback () {
debug('_parseSuback')
const packet = this.packet
this.packet.granted = []
if (!this._parseMessageId()) { return }
// Properties mqtt 5
if (this.settings.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
if (packet.length <= 0) { return this._emitError(new Error('Malformed suback, no payload specified')) }
// Parse granted QoSes
while (this._pos < this.packet.length) {
const code = this._list.readUInt8(this._pos++)
if (this.settings.protocolVersion === 5) {
if (!constants.MQTT5_SUBACK_CODES[code]) {
return this._emitError(new Error('Invalid suback code'))
}
} else {
if (code > 2 && code !== 0x80) {
return this._emitError(new Error('Invalid suback QoS, must be 0, 1, 2 or 128'))
}
}
this.packet.granted.push(code)
}
}
_parseUnsubscribe () {
debug('_parseUnsubscribe')
const packet = this.packet
packet.unsubscriptions = []
// Parse messageId
if (!this._parseMessageId()) { return }
// Properties mqtt 5
if (this.settings.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
if (packet.length <= 0) { return this._emitError(new Error('Malformed unsubscribe, no payload specified')) }
while (this._pos < packet.length) {
// Parse topic
const topic = this._parseString()
if (topic === null) return this._emitError(new Error('Cannot parse topic'))
// Push topic to unsubscriptions
debug('_parseUnsubscribe: push topic `%s` to unsubscriptions', topic)
packet.unsubscriptions.push(topic)
}
}
_parseUnsuback () {
debug('_parseUnsuback')
const packet = this.packet
if (!this._parseMessageId()) return this._emitError(new Error('Cannot parse messageId'))
if ((this.settings.protocolVersion === 3 ||
this.settings.protocolVersion === 4) && packet.length !== 2) {
return this._emitError(new Error('Malformed unsuback, payload length must be 2'))
}
if (packet.length <= 0) { return this._emitError(new Error('Malformed unsuback, no payload specified')) }
// Properties mqtt 5
if (this.settings.protocolVersion === 5) {
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
// Parse granted QoSes
packet.granted = []
while (this._pos < this.packet.length) {
const code = this._list.readUInt8(this._pos++)
if (!constants.MQTT5_UNSUBACK_CODES[code]) {
return this._emitError(new Error('Invalid unsuback code'))
}
this.packet.granted.push(code)
}
}
}
// parse packets like puback, pubrec, pubrel, pubcomp
_parseConfirmation () {
debug('_parseConfirmation: packet.cmd: `%s`', this.packet.cmd)
const packet = this.packet
this._parseMessageId()
if (this.settings.protocolVersion === 5) {
if (packet.length > 2) {
// response code
packet.reasonCode = this._parseByte()
switch (this.packet.cmd) {
case 'puback':
case 'pubrec':
if (!constants.MQTT5_PUBACK_PUBREC_CODES[packet.reasonCode]) {
return this._emitError(new Error('Invalid ' + this.packet.cmd + ' reason code'))
}
break
case 'pubrel':
case 'pubcomp':
if (!constants.MQTT5_PUBREL_PUBCOMP_CODES[packet.reasonCode]) {
return this._emitError(new Error('Invalid ' + this.packet.cmd + ' reason code'))
}
break
}
debug('_parseConfirmation: packet.reasonCode `%d`', packet.reasonCode)
} else {
packet.reasonCode = 0
}
if (packet.length > 3) {
// properies mqtt 5
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
}
return true
}
// parse disconnect packet
_parseDisconnect () {
const packet = this.packet
debug('_parseDisconnect')
if (this.settings.protocolVersion === 5) {
// response code
if (this._list.length > 0) {
packet.reasonCode = this._parseByte()
if (!constants.MQTT5_DISCONNECT_CODES[packet.reasonCode]) {
this._emitError(new Error('Invalid disconnect reason code'))
}
} else {
packet.reasonCode = 0
}
// properies mqtt 5
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
}
debug('_parseDisconnect result: true')
return true
}
// parse auth packet
_parseAuth () {
debug('_parseAuth')
const packet = this.packet
if (this.settings.protocolVersion !== 5) {
return this._emitError(new Error('Not supported auth packet for this version MQTT'))
}
// response code
packet.reasonCode = this._parseByte()
if (!constants.MQTT5_AUTH_CODES[packet.reasonCode]) {
return this._emitError(new Error('Invalid auth reason code'))
}
// properies mqtt 5
const properties = this._parseProperties()
if (Object.getOwnPropertyNames(properties).length) {
packet.properties = properties
}
debug('_parseAuth: result: true')
return true
}
_parseMessageId () {
const packet = this.packet
packet.messageId = this._parseNum()
if (packet.messageId === null) {
this._emitError(new Error('Cannot parse messageId'))
return false
}
debug('_parseMessageId: packet.messageId %d', packet.messageId)
return true
}
_parseString (maybeBuffer) {
const length = this._parseNum()
const end = length + this._pos
if (length === -1 || end > this._list.length || end > this.packet.length) return null
const result = this._list.toString('utf8', this._pos, end)
this._pos += length
debug('_parseString: result: %s', result)
return result
}
_parseStringPair () {
debug('_parseStringPair')
return {
name: this._parseString(),
value: this._parseString()
}
}
_parseBuffer () {
const length = this._parseNum()
const end = length + this._pos
if (length === -1 || end > this._list.length || end > this.packet.length) return null
const result = this._list.slice(this._pos, end)
this._pos += length
debug('_parseBuffer: result: %o', result)
return result
}
_parseNum () {
if (this._list.length - this._pos < 2) return -1
const result = this._list.readUInt16BE(this._pos)
this._pos += 2
debug('_parseNum: result: %s', result)
return result
}
_parse4ByteNum () {
if (this._list.length - this._pos < 4) return -1
const result = this._list.readUInt32BE(this._pos)
this._pos += 4
debug('_parse4ByteNum: result: %s', result)
return result
}
_parseVarByteNum (fullInfoFlag) {
debug('_parseVarByteNum')
const maxBytes = 4
let bytes = 0
let mul = 1
let value = 0
let result = false
let current
const padding = this._pos ? this._pos : 0
while (bytes < maxBytes && (padding + bytes) < this._list.length) {
current = this._list.readUInt8(padding + bytes++)
value += mul * (current & constants.VARBYTEINT_MASK)
mul *= 0x80
if ((current & constants.VARBYTEINT_FIN_MASK) === 0) {
result = true
break
}
if (this._list.length <= bytes) {
break
}
}
if (!result && bytes === maxBytes && this._list.length >= bytes) {
this._emitError(new Error('Invalid variable byte integer'))
}
if (padding) {
this._pos += bytes
}
if (result) {
if (fullInfoFlag) {
result = { bytes, value }
} else {
result = value
}
} else {
result = false
}
debug('_parseVarByteNum: result: %o', result)
return result
}
_parseByte () {
let result
if (this._pos < this._list.length) {
result = this._list.readUInt8(this._pos)
this._pos++
}
debug('_parseByte: result: %o', result)
return result
}
_parseByType (type) {
debug('_parseByType: type: %s', type)
switch (type) {
case 'byte': {
return this._parseByte() !== 0
}
case 'int8': {
return this._parseByte()
}
case 'int16': {
return this._parseNum()
}
case 'int32': {
return this._parse4ByteNum()
}
case 'var': {
return this._parseVarByteNum()
}
case 'string': {
return this._parseString()
}
case 'pair': {
return this._parseStringPair()
}
case 'binary': {
return this._parseBuffer()
}
}
}
_parseProperties () {
debug('_parseProperties')
const length = this._parseVarByteNum()
const start = this._pos
const end = start + length
const result = {}
while (this._pos < end) {
const type = this._parseByte()
if (!type) {
this._emitError(new Error('Cannot parse property code type'))
return false
}
const name = constants.propertiesCodes[type]
if (!name) {
this._emitError(new Error('Unknown property'))
return false
}
// user properties process
if (name === 'userProperties') {
if (!result[name]) {
result[name] = Object.create(null)
}
const currentUserProperty = this._parseByType(constants.propertiesTypes[name])
if (result[name][currentUserProperty.name]) {
if (Array.isArray(result[name][currentUserProperty.name])) {
result[name][currentUserProperty.name].push(currentUserProperty.value)
} else {
const currentValue = result[name][currentUserProperty.name]
result[name][currentUserProperty.name] = [currentValue]
result[name][currentUserProperty.name].push(currentUserProperty.value)
}
} else {
result[name][currentUserProperty.name] = currentUserProperty.value
}
continue
}
if (result[name]) {
if (Array.isArray(result[name])) {
result[name].push(this._parseByType(constants.propertiesTypes[name]))
} else {
result[name] = [result[name]]
result[name].push(this._parseByType(constants.propertiesTypes[name]))
}
} else {
result[name] = this._parseByType(constants.propertiesTypes[name])
}
}
return result
}
_newPacket () {
debug('_newPacket')
if (this.packet) {
this._list.consume(this.packet.length)
debug('_newPacket: parser emit packet: packet.cmd: %s, packet.payload: %s, packet.length: %d', this.packet.cmd, this.packet.payload, this.packet.length)
this.emit('packet', this.packet)
}
debug('_newPacket: new packet')
this.packet = new Packet()
this._pos = 0
return true
}
_emitError (err) {
debug('_emitError', err)
this.error = err
this.emit('error', err)
}
}
module.exports = Parser
mqtt-packet-9.0.2/test.js000066400000000000000000002364241476155302400153130ustar00rootroot00000000000000const util = require('util')
const test = require('tape')
const mqtt = require('./')
const WS = require('readable-stream').Writable
function normalExpectedObject (object) {
if (object.username != null) object.username = object.username.toString()
if (object.password != null) object.password = Buffer.from(object.password)
return object
}
function testParseGenerate (name, object, buffer, opts) {
test(`${name} parse`, t => {
t.plan(2)
const parser = mqtt.parser(opts)
const expected = object
const fixture = buffer
parser.on('packet', packet => {
if (packet.cmd !== 'publish') {
delete packet.topic
delete packet.payload
}
t.deepLooseEqual(packet, normalExpectedObject(expected), 'expected packet')
})
parser.on('error', err => {
t.fail(err)
})
t.equal(parser.parse(fixture), 0, 'remaining bytes')
})
test(`${name} generate`, t => {
// For really large buffers, the expanded hex string can be so long as to
// generate an error in nodejs 14.x, so only do the test with extra output
// for relatively small buffers.
const bigLength = 10000
const generatedBuffer = mqtt.generate(object, opts)
if (generatedBuffer.length < bigLength && buffer.length < bigLength) {
t.equal(generatedBuffer.toString('hex'), buffer.toString('hex'))
} else {
const bufferOkay = generatedBuffer.equals(buffer)
if (bufferOkay) {
t.pass()
} else {
// Output abbreviated representations of the buffers.
t.comment('Expected:\n' + util.inspect(buffer))
t.comment('Got:\n' + util.inspect(generatedBuffer))
t.fail('Large buffers not equal')
}
}
t.end()
})
test(`${name} mirror`, t => {
t.plan(2)
const parser = mqtt.parser(opts)
const expected = object
const fixture = mqtt.generate(object, opts)
parser.on('packet', packet => {
if (packet.cmd !== 'publish') {
delete packet.topic
delete packet.payload
}
t.deepLooseEqual(packet, normalExpectedObject(expected), 'expected packet')
})
parser.on('error', err => {
t.fail(err)
})
t.equal(parser.parse(fixture), 0, 'remaining bytes')
})
test(`${name} writeToStream`, t => {
const stream = WS()
stream.write = () => true
stream.on('error', (err) => t.fail(err))
const result = mqtt.writeToStream(object, stream, opts)
t.equal(result, true, 'should return true')
t.end()
})
}
// the API allows to pass strings as buffers to writeToStream and generate
// parsing them back will result in a string so only generate and compare to buffer
function testGenerateOnly (name, object, buffer, opts) {
test(name, t => {
t.equal(mqtt.generate(object, opts).toString('hex'), buffer.toString('hex'))
t.end()
})
}
function testParseOnly (name, object, buffer, opts) {
test(name, t => {
const parser = mqtt.parser(opts)
// const expected = object
// const fixture = buffer
t.plan(2 + Object.keys(object).length)
parser.on('packet', packet => {
t.equal(Object.keys(object).length, Object.keys(packet).length, 'key count')
Object.keys(object).forEach(key => {
t.deepEqual(packet[key], object[key], `expected packet property ${key}`)
})
})
t.equal(parser.parse(buffer), 0, 'remaining bytes')
t.end()
})
}
function testParseError (expected, fixture, opts) {
test(expected, t => {
t.plan(1)
const parser = mqtt.parser(opts)
parser.on('error', err => {
t.equal(err.message, expected, 'expected error message')
})
parser.on('packet', () => {
t.fail('parse errors should not be followed by packet events')
})
parser.parse(fixture)
t.end()
})
}
function testGenerateError (expected, fixture, opts, name) {
test(name || expected, t => {
t.plan(1)
try {
mqtt.generate(fixture, opts)
} catch (err) {
t.equal(expected, err.message)
}
t.end()
})
}
function testGenerateErrorMultipleCmds (cmds, expected, fixture, opts) {
cmds.forEach(cmd => {
const obj = Object.assign({}, fixture)
obj.cmd = cmd
testGenerateError(expected, obj, opts, `${expected} on ${cmd}`)
}
)
}
function testParseGenerateDefaults (name, object, buffer, generated, opts) {
testParseOnly(`${name} parse`, generated, buffer, opts)
testGenerateOnly(`${name} generate`, object, buffer, opts)
}
function testParseAndGenerate (name, object, buffer, opts) {
testParseOnly(`${name} parse`, object, buffer, opts)
testGenerateOnly(`${name} generate`, object, buffer, opts)
}
function testWriteToStreamError (expected, fixture) {
test(`writeToStream ${expected} error`, t => {
t.plan(2)
const stream = WS()
stream.write = () => t.fail('should not have called write')
stream.on('error', () => t.pass('error emitted'))
const result = mqtt.writeToStream(fixture, stream)
t.false(result, 'result should be false')
})
}
test('cacheNumbers get/set/unset', t => {
t.true(mqtt.writeToStream.cacheNumbers, 'initial state of cacheNumbers is enabled')
mqtt.writeToStream.cacheNumbers = false
t.false(mqtt.writeToStream.cacheNumbers, 'cacheNumbers can be disabled')
mqtt.writeToStream.cacheNumbers = true
t.true(mqtt.writeToStream.cacheNumbers, 'cacheNumbers can be enabled')
t.end()
})
test('disabled numbers cache', t => {
const stream = WS()
const message = {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: 10,
topic: Buffer.from('test'),
payload: Buffer.from('test')
}
const expected = Buffer.from([
48, 10, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
116, 101, 115, 116 // Payload (test)
])
let written = Buffer.alloc(0)
stream.write = (chunk) => {
written = Buffer.concat([written, chunk])
}
mqtt.writeToStream.cacheNumbers = false
mqtt.writeToStream(message, stream)
t.deepEqual(written, expected, 'written buffer is expected')
mqtt.writeToStream.cacheNumbers = true
stream.end()
t.end()
})
testGenerateError('Unknown command', {})
testParseError('Not supported', Buffer.from([0, 1, 0]), {})
// Length header field
testParseError('Invalid variable byte integer', Buffer.from(
[16, 255, 255, 255, 255]
), {})
testParseError('Invalid variable byte integer', Buffer.from(
[16, 255, 255, 255, 128]
), {})
testParseError('Invalid variable byte integer', Buffer.from(
[16, 255, 255, 255, 255, 1]
), {})
testParseError('Invalid variable byte integer', Buffer.from(
[16, 255, 255, 255, 255, 127]
), {})
testParseError('Invalid variable byte integer', Buffer.from(
[16, 255, 255, 255, 255, 128]
), {})
testParseError('Invalid variable byte integer', Buffer.from(
[16, 255, 255, 255, 255, 255, 1]
), {})
testParseGenerate('minimal connect', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 18,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: false,
keepalive: 30,
clientId: 'test'
}, Buffer.from([
16, 18, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
0, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116 // Client ID
]))
testGenerateOnly('minimal connect with clientId as Buffer', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 18,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: false,
keepalive: 30,
clientId: Buffer.from('test')
}, Buffer.from([
16, 18, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
0, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116 // Client ID
]))
testParseGenerate('connect MQTT bridge 131', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 18,
protocolId: 'MQIsdp',
protocolVersion: 3,
bridgeMode: true,
clean: false,
keepalive: 30,
clientId: 'test'
}, Buffer.from([
16, 18, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
131, // Protocol version
0, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116 // Client ID
]))
testParseGenerate('connect MQTT bridge 132', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 18,
protocolId: 'MQIsdp',
protocolVersion: 4,
bridgeMode: true,
clean: false,
keepalive: 30,
clientId: 'test'
}, Buffer.from([
16, 18, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
132, // Protocol version
0, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116 // Client ID
]))
testParseGenerate('connect MQTT 5', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 125,
protocolId: 'MQTT',
protocolVersion: 5,
will: {
retain: true,
qos: 2,
properties: {
willDelayInterval: 1234,
payloadFormatIndicator: false,
messageExpiryInterval: 4321,
contentType: 'test',
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
}
},
topic: 'topic',
payload: Buffer.from([4, 3, 2, 1])
},
clean: true,
keepalive: 30,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumPacketSize: 100,
topicAliasMaximum: 456,
requestResponseInformation: true,
requestProblemInformation: true,
userProperties: {
test: 'test'
},
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
},
clientId: 'test'
}, Buffer.from([
16, 125, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
5, // Protocol version
54, // Connect flags
0, 30, // Keepalive
47, // properties length
17, 0, 0, 4, 210, // sessionExpiryInterval
33, 1, 176, // receiveMaximum
39, 0, 0, 0, 100, // maximumPacketSize
34, 1, 200, // topicAliasMaximum
25, 1, // requestResponseInformation
23, 1, // requestProblemInformation,
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties,
21, 0, 4, 116, 101, 115, 116, // authenticationMethod
22, 0, 4, 1, 2, 3, 4, // authenticationData
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
47, // will properties
24, 0, 0, 4, 210, // will delay interval
1, 0, // payload format indicator
2, 0, 0, 16, 225, // message expiry interval
3, 0, 4, 116, 101, 115, 116, // content type
8, 0, 5, 116, 111, 112, 105, 99, // response topic
9, 0, 4, 1, 2, 3, 4, // corelation data
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // user properties
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 4, // Will payload length
4, 3, 2, 1// Will payload
]))
testParseGenerate('connect MQTT 5 with will properties but with empty will payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 121,
protocolId: 'MQTT',
protocolVersion: 5,
will: {
retain: true,
qos: 2,
properties: {
willDelayInterval: 1234,
payloadFormatIndicator: false,
messageExpiryInterval: 4321,
contentType: 'test',
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
}
},
topic: 'topic',
payload: Buffer.from([])
},
clean: true,
keepalive: 30,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumPacketSize: 100,
topicAliasMaximum: 456,
requestResponseInformation: true,
requestProblemInformation: true,
userProperties: {
test: 'test'
},
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
},
clientId: 'test'
}, Buffer.from([
16, 121, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
5, // Protocol version
54, // Connect flags
0, 30, // Keepalive
47, // properties length
17, 0, 0, 4, 210, // sessionExpiryInterval
33, 1, 176, // receiveMaximum
39, 0, 0, 0, 100, // maximumPacketSize
34, 1, 200, // topicAliasMaximum
25, 1, // requestResponseInformation
23, 1, // requestProblemInformation,
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties,
21, 0, 4, 116, 101, 115, 116, // authenticationMethod
22, 0, 4, 1, 2, 3, 4, // authenticationData
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
47, // will properties
24, 0, 0, 4, 210, // will delay interval
1, 0, // payload format indicator
2, 0, 0, 16, 225, // message expiry interval
3, 0, 4, 116, 101, 115, 116, // content type
8, 0, 5, 116, 111, 112, 105, 99, // response topic
9, 0, 4, 1, 2, 3, 4, // corelation data
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // user properties
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 0 // Will payload length
]))
testParseGenerate('connect MQTT 5 w/o will properties', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 78,
protocolId: 'MQTT',
protocolVersion: 5,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: Buffer.from([4, 3, 2, 1])
},
clean: true,
keepalive: 30,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumPacketSize: 100,
topicAliasMaximum: 456,
requestResponseInformation: true,
requestProblemInformation: true,
userProperties: {
test: 'test'
},
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
},
clientId: 'test'
}, Buffer.from([
16, 78, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
5, // Protocol version
54, // Connect flags
0, 30, // Keepalive
47, // properties length
17, 0, 0, 4, 210, // sessionExpiryInterval
33, 1, 176, // receiveMaximum
39, 0, 0, 0, 100, // maximumPacketSize
34, 1, 200, // topicAliasMaximum
25, 1, // requestResponseInformation
23, 1, // requestProblemInformation,
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties,
21, 0, 4, 116, 101, 115, 116, // authenticationMethod
22, 0, 4, 1, 2, 3, 4, // authenticationData
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, // will properties
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 4, // Will payload length
4, 3, 2, 1// Will payload
]))
testParseGenerate('no clientId with 3.1.1', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 12,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
keepalive: 30,
clientId: ''
}, Buffer.from([
16, 12, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
4, // Protocol version
2, // Connect flags
0, 30, // Keepalive
0, 0 // Client ID length
]))
testParseGenerateDefaults('no clientId with 5.0', {
cmd: 'connect',
protocolId: 'MQTT',
protocolVersion: 5,
clean: true,
keepalive: 60,
properties:
{
receiveMaximum: 20
},
clientId: ''
}, Buffer.from(
[16, 16, 0, 4, 77, 81, 84, 84, 5, 2, 0, 60, 3, 33, 0, 20, 0, 0]
), {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 16,
topic: null,
payload: null,
protocolId: 'MQTT',
protocolVersion: 5,
clean: true,
keepalive: 60,
properties: {
receiveMaximum: 20
},
clientId: ''
}, { protocolVersion: 5 })
testParseGenerateDefaults('utf-8 clientId with 5.0', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 23,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
keepalive: 30,
clientId: 'Ŧėśt🜄'
}, Buffer.from([
16, 23, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
4, // Protocol version
2, // Connect flags
0, 30, // Keepalive
0, 11, // Client ID length
197, 166, // Ŧ (UTF-8: 0xc5a6)
196, 151, // ė (UTF-8: 0xc497)
197, 155, // ś (utf-8: 0xc59b)
116, // t (utf-8: 0x74)
240, 159, 156, 132 // 🜄 (utf-8: 0xf09f9c84)
]), {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 23,
topic: null,
payload: null,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
keepalive: 30,
clientId: 'Ŧėśt🜄'
}, { protocol: 5 })
testParseGenerateDefaults('default connect', {
cmd: 'connect',
clientId: 'test'
}, Buffer.from([
16, 16, 0, 4, 77, 81, 84,
84, 4, 2, 0, 0,
0, 4, 116, 101, 115, 116
]), {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 16,
topic: null,
payload: null,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
keepalive: 0,
clientId: 'test'
})
testParseAndGenerate('Version 4 CONACK', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
sessionPresent: false,
returnCode: 1
}, Buffer.from([
32, 2, // Fixed Header (CONNACK, Remaining Length)
0, 1 // Variable Header (Session not present, Connection Refused - unacceptable protocol version)
]), {}) // Default protocolVersion (4)
testParseAndGenerate('Version 5 CONACK', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 3,
topic: null,
payload: null,
sessionPresent: false,
reasonCode: 140
}, Buffer.from([
32, 3, // Fixed Header (CONNACK, Remaining Length)
0, 140, // Variable Header (Session not present, Bad authentication method)
0 // Property Length Zero
]), { protocolVersion: 5 })
testParseOnly('Version 4 CONACK in Version 5 mode', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
sessionPresent: false,
reasonCode: 1 // a version 4 return code stored in the version 5 reasonCode because this client is in version 5
}, Buffer.from([
32, 2, // Fixed Header (CONNACK, Remaining Length)
0, 1 // Variable Header (Session not present, Connection Refused - unacceptable protocol version)
]), { protocolVersion: 5 }) // message is in version 4 format, but this client is in version 5 mode
testParseOnly('Version 5 PUBACK test 1', {
cmd: 'puback',
messageId: 42,
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
64, 2, // Fixed Header (PUBACK, Remaining Length)
0, 42 // Variable Header (2 Bytes: Packet Identifier 42, Implied Reason code: Success, Implied no properties)
]), { protocolVersion: 5 }
)
testParseAndGenerate('Version 5 PUBACK test 2', {
cmd: 'puback',
messageId: 42,
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
64, 2, // Fixed Header (PUBACK, Remaining Length)
0, 42 // Variable Header (2 Bytes: Packet Identifier 42, Implied reason code: 0 Success, Implied no properties)
]), { protocolVersion: 5 }
)
testParseOnly('Version 5 PUBACK test 2.1', {
cmd: 'puback',
messageId: 42,
retain: false,
qos: 0,
dup: false,
length: 3,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
64, 3, // Fixed Header (PUBACK, Remaining Length)
0, 42, 0 // Variable Header (2 Bytes: Packet Identifier 42, Reason code: 0 Success, Implied no properties)
]), { protocolVersion: 5 }
)
testParseOnly('Version 5 PUBACK test 3', {
cmd: 'puback',
messageId: 42,
retain: false,
qos: 0,
dup: false,
length: 4,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
64, 4, // Fixed Header (PUBACK, Remaining Length)
0, 42, 0, // Variable Header (2 Bytes: Packet Identifier 42, Reason code: 0 Success)
0 // no properties
]), { protocolVersion: 5 }
)
testParseOnly('Version 5 CONNACK test 1', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 1,
topic: null,
payload: null,
sessionPresent: true,
reasonCode: 0
}, Buffer.from([
32, 1, // Fixed Header (CONNACK, Remaining Length)
1 // Variable Header (Session Present: 1 => true, Implied Reason code: Success, Implied no properties)
]), { protocolVersion: 5 }
)
testParseOnly('Version 5 CONNACK test 2', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
sessionPresent: true,
reasonCode: 0
}, Buffer.from([
32, 2, // Fixed Header (CONNACK, Remaining Length)
1, 0 // Variable Header (Session Present: 1 => true, Connect Reason code: Success, Implied no properties)
]), { protocolVersion: 5 }
)
testParseAndGenerate('Version 5 CONNACK test 3', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 3,
topic: null,
payload: null,
sessionPresent: true,
reasonCode: 0
}, Buffer.from([
32, 3, // Fixed Header (CONNACK, Remaining Length)
1, 0, // Variable Header (Session Present: 1 => true, Connect Reason code: Success)
0 // no properties
]), { protocolVersion: 5 }
)
testParseOnly('Version 5 DISCONNECT test 1', {
cmd: 'disconnect',
retain: false,
qos: 0,
dup: false,
length: 0,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
224, 0 // Fixed Header (DISCONNECT, Remaining Length), Implied Reason code: Normal Disconnection
]), { protocolVersion: 5 }
)
testParseOnly('Version 5 DISCONNECT test 2', {
cmd: 'disconnect',
retain: false,
qos: 0,
dup: false,
length: 1,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
224, 1, // Fixed Header (DISCONNECT, Remaining Length)
0 // reason Code (Normal disconnection)
]), { protocolVersion: 5 }
)
testParseAndGenerate('Version 5 DISCONNECT test 3', {
cmd: 'disconnect',
retain: false,
qos: 0,
dup: false,
length: 2,
topic: null,
payload: null,
reasonCode: 0
}, Buffer.from([
224, 2, // Fixed Header (DISCONNECT, Remaining Length)
0, // reason Code (Normal disconnection)
0 // no properties
]), { protocolVersion: 5 }
)
testParseGenerate('empty will payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 47,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: Buffer.alloc(0)
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: Buffer.from('password')
}, Buffer.from([
16, 47, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 0, // Will payload length
// Will payload
0, 8, // Username length
117, 115, 101, 114, 110, 97, 109, 101, // Username
0, 8, // Password length
112, 97, 115, 115, 119, 111, 114, 100 // Password
]))
testParseGenerate('empty buffer username payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 20,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: true,
keepalive: 30,
clientId: 'test',
username: Buffer.from('')
}, Buffer.from([
16, 20, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
130, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 0 // Username length
// Empty Username payload
]))
testParseGenerate('empty string username payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 20,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: true,
keepalive: 30,
clientId: 'test',
username: ''
}, Buffer.from([
16, 20, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
130, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 0 // Username length
// Empty Username payload
]))
testParseGenerate('empty buffer password payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 30,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: Buffer.from('')
}, Buffer.from([
16, 30, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
194, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 8, // Username length
117, 115, 101, 114, 110, 97, 109, 101, // Username payload
0, 0 // Password length
// Empty password payload
]))
testParseGenerate('empty string password payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 30,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: ''
}, Buffer.from([
16, 30, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
194, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 8, // Username length
117, 115, 101, 114, 110, 97, 109, 101, // Username payload
0, 0 // Password length
// Empty password payload
]))
testParseGenerate('empty string username and password payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 22,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: true,
keepalive: 30,
clientId: 'test',
username: '',
password: Buffer.from('')
}, Buffer.from([
16, 22, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
194, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 0, // Username length
// Empty Username payload
0, 0 // Password length
// Empty password payload
]))
testParseGenerate('maximal connect', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: Buffer.from('payload')
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: Buffer.from('password')
}, Buffer.from([
16, 54, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 7, // Will payload length
112, 97, 121, 108, 111, 97, 100, // Will payload
0, 8, // Username length
117, 115, 101, 114, 110, 97, 109, 101, // Username
0, 8, // Password length
112, 97, 115, 115, 119, 111, 114, 100 // Password
]))
testParseGenerate('max connect with special chars', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 57,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'tòpic',
payload: Buffer.from('pay£oad')
},
clean: true,
keepalive: 30,
clientId: 'te$t',
username: 'u$ern4me',
password: Buffer.from('p4$$w0£d')
}, Buffer.from([
16, 57, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 36, 116, // Client ID
0, 6, // Will topic length
116, 195, 178, 112, 105, 99, // Will topic
0, 8, // Will payload length
112, 97, 121, 194, 163, 111, 97, 100, // Will payload
0, 8, // Username length
117, 36, 101, 114, 110, 52, 109, 101, // Username
0, 9, // Password length
112, 52, 36, 36, 119, 48, 194, 163, 100 // Password
]))
testGenerateOnly('connect all strings generate', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 'password'
}, Buffer.from([
16, 54, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116, // Client ID
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 7, // Will payload length
112, 97, 121, 108, 111, 97, 100, // Will payload
0, 8, // Username length
117, 115, 101, 114, 110, 97, 109, 101, // Username
0, 8, // Password length
112, 97, 115, 115, 119, 111, 114, 100 // Password
]))
testParseError('Cannot parse protocolId', Buffer.from([
16, 4,
0, 6,
77, 81
]))
// missing protocol version on connect
testParseError('Packet too short', Buffer.from([
16, 8, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112 // Protocol ID
]))
// missing keepalive on connect
testParseError('Packet too short', Buffer.from([
16, 10, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246 // Connect flags
]))
// missing clientid on connect
testParseError('Packet too short', Buffer.from([
16, 10, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30 // Keepalive
]))
// missing will topic on connect
testParseError('Cannot parse will topic', Buffer.from([
16, 16, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 2, // Will topic length
0, 0 // Will topic
]))
// missing will payload on connect
testParseError('Cannot parse will payload', Buffer.from([
16, 23, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 2, // Will payload length
0, 0 // Will payload
]))
// missing username on connect
testParseError('Cannot parse username', Buffer.from([
16, 32, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 7, // Will payload length
112, 97, 121, 108, 111, 97, 100, // Will payload
0, 2, // Username length
0, 0 // Username
]))
// missing password on connect
testParseError('Cannot parse password', Buffer.from([
16, 42, // Header
0, 6, // Protocol ID length
77, 81, 73, 115, 100, 112, // Protocol ID
3, // Protocol version
246, // Connect flags
0, 30, // Keepalive
0, 5, // Will topic length
116, 111, 112, 105, 99, // Will topic
0, 7, // Will payload length
112, 97, 121, 108, 111, 97, 100, // Will payload
0, 8, // Username length
117, 115, 101, 114, 110, 97, 109, 101, // Username
0, 2, // Password length
0, 0 // Password
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for connect packet', Buffer.from([
18, 10, // Header
0, 4, // Protocol ID length
0x4d, 0x51, 0x54, 0x54, // Protocol ID
3, // Protocol version
2, // Connect flags
0, 30 // Keepalive
]))
// The Server MUST validate that the reserved flag in the CONNECT Control Packet is set to zero and disconnect the Client if it is not zero [MQTT-3.1.2-3]
testParseError('Connect flag bit 0 must be 0, but got 1', Buffer.from([
16, 10, // Header
0, 4, // Protocol ID length
0x4d, 0x51, 0x54, 0x54, // Protocol ID
3, // Protocol version
3, // Connect flags
0, 30 // Keepalive
]))
// If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload [MQTT-3.1.2-11].
testParseError('Will Retain Flag must be set to zero when Will Flag is set to 0', Buffer.from([
16, 10, // Header
0, 4, // Protocol ID length
0x4d, 0x51, 0x54, 0x54, // Protocol ID
3, // Protocol version
0x22, // Connect flags
0, 30 // Keepalive
]))
// If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload [MQTT-3.1.2-11].
testParseError('Will QoS must be set to zero when Will Flag is set to 0', Buffer.from([
16, 10, // Header
0, 4, // Protocol ID length
0x4d, 0x51, 0x54, 0x54, // Protocol ID
3, // Protocol version
0x12, // Connect flags
0, 30 // Keepalive
]))
// If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload [MQTT-3.1.2-11].
testParseError('Will QoS must be set to zero when Will Flag is set to 0', Buffer.from([
16, 10, // Header
0, 4, // Protocol ID length
0x4d, 0x51, 0x54, 0x54, // Protocol ID
3, // Protocol version
0xa, // Connect flags
0, 30 // Keepalive
]))
// CONNECT, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK (v.5) packets must have payload
// CONNECT
testParseError('Packet too short', Buffer.from([
16, // Header
8, // Packet length
0, 4, // Protocol ID length
77, 81, 84, 84, // MQTT
5, // Version
2, // Clean Start enabled
0, 0, // Keep-Alive
0, // Property Length
0, 0 // Properties
// No payload
]), { protocolVersion: 5 })
// SUBSCRIBE
testParseError('Malformed subscribe, no payload specified', Buffer.from([
130, // Header
0 // Packet length
]), { protocolVersion: 5 })
// SUBACK
testParseError('Malformed suback, no payload specified', Buffer.from([
144, // Header
0 // Packet length
]), { protocolVersion: 5 })
// UNSUBSCRIBE
testParseError('Malformed unsubscribe, no payload specified', Buffer.from([
162, // Header
0 // Packet length
]), { protocolVersion: 5 })
// UNSUBACK (v.5)
testParseError('Malformed unsuback, no payload specified', Buffer.from([
176, // Header
0 // Packet length
]), { protocolVersion: 5 })
// UNSUBACK (v.4)
testParseError('Malformed unsuback, payload length must be 2', Buffer.from([
176, // Header
1, // Packet length
1
]), { protocolVersion: 4 })
// UNSUBACK (v.3)
testParseError('Malformed unsuback, payload length must be 2', Buffer.from([
176, // Header
1, // Packet length
1
]), { protocolVersion: 3 })
testParseGenerate('connack with return code 0', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
sessionPresent: false,
returnCode: 0
}, Buffer.from([
32, 2, 0, 0
]))
testParseGenerate('connack MQTT 5 with properties', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 87,
sessionPresent: false,
reasonCode: 0,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumQoS: 2,
retainAvailable: true,
maximumPacketSize: 100,
assignedClientIdentifier: 'test',
topicAliasMaximum: 456,
reasonString: 'test',
userProperties: {
test: 'test'
},
wildcardSubscriptionAvailable: true,
subscriptionIdentifiersAvailable: true,
sharedSubscriptionAvailable: false,
serverKeepAlive: 1234,
responseInformation: 'test',
serverReference: 'test',
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
}
}, Buffer.from([
32, 87, 0, 0,
84, // properties length
17, 0, 0, 4, 210, // sessionExpiryInterval
33, 1, 176, // receiveMaximum
36, 2, // Maximum qos
37, 1, // retainAvailable
39, 0, 0, 0, 100, // maximumPacketSize
18, 0, 4, 116, 101, 115, 116, // assignedClientIdentifier
34, 1, 200, // topicAliasMaximum
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
40, 1, // wildcardSubscriptionAvailable
41, 1, // subscriptionIdentifiersAvailable
42, 0, // sharedSubscriptionAvailable
19, 4, 210, // serverKeepAlive
26, 0, 4, 116, 101, 115, 116, // responseInformation
28, 0, 4, 116, 101, 115, 116, // serverReference
21, 0, 4, 116, 101, 115, 116, // authenticationMethod
22, 0, 4, 1, 2, 3, 4 // authenticationData
]), { protocolVersion: 5 })
testParseGenerate('connack MQTT 5 with properties and doubled user properties', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 100,
sessionPresent: false,
reasonCode: 0,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumQoS: 2,
retainAvailable: true,
maximumPacketSize: 100,
assignedClientIdentifier: 'test',
topicAliasMaximum: 456,
reasonString: 'test',
userProperties: {
test: ['test', 'test']
},
wildcardSubscriptionAvailable: true,
subscriptionIdentifiersAvailable: true,
sharedSubscriptionAvailable: false,
serverKeepAlive: 1234,
responseInformation: 'test',
serverReference: 'test',
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
}
}, Buffer.from([
32, 100, 0, 0,
97, // properties length
17, 0, 0, 4, 210, // sessionExpiryInterval
33, 1, 176, // receiveMaximum
36, 2, // Maximum qos
37, 1, // retainAvailable
39, 0, 0, 0, 100, // maximumPacketSize
18, 0, 4, 116, 101, 115, 116, // assignedClientIdentifier
34, 1, 200, // topicAliasMaximum
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116,
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
40, 1, // wildcardSubscriptionAvailable
41, 1, // subscriptionIdentifiersAvailable
42, 0, // sharedSubscriptionAvailable
19, 4, 210, // serverKeepAlive
26, 0, 4, 116, 101, 115, 116, // responseInformation
28, 0, 4, 116, 101, 115, 116, // serverReference
21, 0, 4, 116, 101, 115, 116, // authenticationMethod
22, 0, 4, 1, 2, 3, 4 // authenticationData
]), { protocolVersion: 5 })
testParseGenerate('connack with return code 0 session present bit set', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
sessionPresent: true,
returnCode: 0
}, Buffer.from([
32, 2, 1, 0
]))
testParseGenerate('connack with return code 5', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
sessionPresent: false,
returnCode: 5
}, Buffer.from([
32, 2, 0, 5
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for connack packet', Buffer.from([
33, 2, // header
0, // flags
5 // return code
]))
// Byte 1 is the "Connect Acknowledge Flags". Bits 7-1 are reserved and MUST be set to 0 [MQTT-3.2.2-1].
testParseError('Invalid connack flags, bits 7-1 must be set to 0', Buffer.from([
32, 2, // header
2, // flags
5 // return code
]))
testGenerateError('Invalid return code', {
cmd: 'connack',
retain: false,
qos: 0,
dup: false,
length: 2,
sessionPresent: false,
returnCode: '5' // returncode must be a number
})
testParseGenerate('minimal publish', {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: 10,
topic: 'test',
payload: Buffer.from('test')
}, Buffer.from([
48, 10, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
116, 101, 115, 116 // Payload (test)
]))
testParseGenerate('publish MQTT 5 properties', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 86,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: true,
messageExpiryInterval: 4321,
topicAlias: 100,
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: ['test', 'test', 'test']
},
subscriptionIdentifier: 120,
contentType: 'test'
}
}, Buffer.from([
61, 86, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
0, 10, // Message ID
73, // properties length
1, 1, // payloadFormatIndicator
2, 0, 0, 16, 225, // message expiry interval
35, 0, 100, // topicAlias
8, 0, 5, 116, 111, 112, 105, 99, // response topic
9, 0, 4, 1, 2, 3, 4, // correlationData
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
11, 120, // subscriptionIdentifier
3, 0, 4, 116, 101, 115, 116, // content type
116, 101, 115, 116 // Payload (test)
]), { protocolVersion: 5 })
testParseGenerate('publish MQTT 5 with multiple same properties', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 64,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: true,
messageExpiryInterval: 4321,
topicAlias: 100,
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
},
subscriptionIdentifier: [120, 121, 122],
contentType: 'test'
}
}, Buffer.from([
61, 64, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
0, 10, // Message ID
51, // properties length
1, 1, // payloadFormatIndicator
2, 0, 0, 16, 225, // message expiry interval
35, 0, 100, // topicAlias
8, 0, 5, 116, 111, 112, 105, 99, // response topic
9, 0, 4, 1, 2, 3, 4, // correlationData
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
11, 120, // subscriptionIdentifier
11, 121, // subscriptionIdentifier
11, 122, // subscriptionIdentifier
3, 0, 4, 116, 101, 115, 116, // content type
116, 101, 115, 116 // Payload (test)
]), { protocolVersion: 5 })
testParseGenerate('publish MQTT 5 properties with 0-4 byte varbyte', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 27,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: false,
subscriptionIdentifier: [128, 16384, 2097152] // this tests the varbyte handling
}
}, Buffer.from([
61, 27, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
0, 10, // Message ID
14, // properties length
1, 0, // payloadFormatIndicator
11, 128, 1, // subscriptionIdentifier
11, 128, 128, 1, // subscriptionIdentifier
11, 128, 128, 128, 1, // subscriptionIdentifier
116, 101, 115, 116 // Payload (test)
]), { protocolVersion: 5 })
testParseGenerate('publish MQTT 5 properties with max value varbyte', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 22,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: false,
subscriptionIdentifier: [1, 268435455]
}
}, Buffer.from([
61, 22, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
0, 10, // Message ID
9, // properties length
1, 0, // payloadFormatIndicator
11, 1, // subscriptionIdentifier
11, 255, 255, 255, 127, // subscriptionIdentifier (max value)
116, 101, 115, 116 // Payload (test)
]), { protocolVersion: 5 })
; (() => {
const buffer = Buffer.alloc(2048)
testParseGenerate('2KB publish packet', {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: 2054,
topic: 'test',
payload: buffer
}, Buffer.concat([Buffer.from([
48, 134, 16, // Header
0, 4, // Topic length
116, 101, 115, 116 // Topic (test)
]), buffer]))
})()
; (() => {
const maxLength = 268435455
const buffer = Buffer.alloc(maxLength - 6)
testParseGenerate('Max payload publish packet', {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: maxLength,
topic: 'test',
payload: buffer
}, Buffer.concat([Buffer.from([
48, 255, 255, 255, 127, // Header
0, 4, // Topic length
116, 101, 115, 116 // Topic (test)
]), buffer]))
})()
testParseGenerate('maximal publish', {
cmd: 'publish',
retain: true,
qos: 2,
length: 12,
dup: true,
topic: 'test',
messageId: 10,
payload: Buffer.from('test')
}, Buffer.from([
61, 12, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic
0, 10, // Message ID
116, 101, 115, 116 // Payload
]))
test('publish all strings generate', t => {
const message = {
cmd: 'publish',
retain: true,
qos: 2,
length: 12,
dup: true,
topic: 'test',
messageId: 10,
payload: Buffer.from('test')
}
const expected = Buffer.from([
61, 12, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic
0, 10, // Message ID
116, 101, 115, 116 // Payload
])
t.equal(mqtt.generate(message).toString('hex'), expected.toString('hex'))
t.end()
})
testParseGenerate('empty publish', {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: 6,
topic: 'test',
payload: Buffer.alloc(0)
}, Buffer.from([
48, 6, // Header
0, 4, // Topic length
116, 101, 115, 116 // Topic
// Empty payload
]))
// A PUBLISH Packet MUST NOT have both QoS bits set to 1. If a Server or Client receives a PUBLISH Packet which has both QoS bits set to 1 it MUST close the Network Connection [MQTT-3.3.1-4].
testParseError('Packet must not have both QoS bits set to 1', Buffer.from([
0x36, 6, // Header
0, 4, // Topic length
116, 101, 115, 116 // Topic
// Empty payload
]))
test('splitted publish parse', t => {
t.plan(3)
const parser = mqtt.parser()
const expected = {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: 10,
topic: 'test',
payload: Buffer.from('test')
}
parser.on('packet', packet => {
t.deepLooseEqual(packet, expected, 'expected packet')
})
t.equal(parser.parse(Buffer.from([
48, 10, // Header
0, 4, // Topic length
116, 101, 115, 116 // Topic (test)
])), 6, 'remaining bytes')
t.equal(parser.parse(Buffer.from([
116, 101, 115, 116 // Payload (test)
])), 0, 'remaining bytes')
})
test('split publish longer', t => {
t.plan(3)
const length = 255
const topic = 'test'
// Minus two bytes for the topic length specifier
const payloadLength = length - topic.length - 2
const parser = mqtt.parser()
const expected = {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length,
topic,
payload: Buffer.from('a'.repeat(payloadLength))
}
parser.on('packet', packet => {
t.deepLooseEqual(packet, expected, 'expected packet')
})
t.equal(parser.parse(Buffer.from([
48, 255, 1, // Header
0, topic.length, // Topic length
116, 101, 115, 116 // Topic (test)
])), 6, 'remaining bytes')
t.equal(parser.parse(Buffer.from(Array(payloadLength).fill(97))),
0, 'remaining bytes')
})
test('split length parse', t => {
t.plan(4)
const length = 255
const topic = 'test'
const payloadLength = length - topic.length - 2
const parser = mqtt.parser()
const expected = {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length,
topic,
payload: Buffer.from('a'.repeat(payloadLength))
}
parser.on('packet', packet => {
t.deepLooseEqual(packet, expected, 'expected packet')
})
t.equal(parser.parse(Buffer.from([
48, 255 // Header (partial length)
])), 1, 'remaining bytes')
t.equal(parser.parse(Buffer.from([
1, // Rest of header length
0, topic.length, // Topic length
116, 101, 115, 116 // Topic (test)
])), 6, 'remaining bytes')
t.equal(parser.parse(Buffer.from(Array(payloadLength).fill(97))),
0, 'remaining bytes')
})
testGenerateError('Invalid variable byte integer: 268435456', {
cmd: 'publish',
retain: false,
qos: 0,
dup: false,
length: (268435455 + 1),
topic: 'test',
payload: Buffer.alloc(268435455 + 1 - 6)
}, {}, 'Length var byte integer over max allowed value throws error')
testGenerateError('Invalid subscriptionIdentifier: 268435456', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 27,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: false,
subscriptionIdentifier: 268435456
}
}, { protocolVersion: 5 }, 'MQTT 5.0 var byte integer >24 bits throws error')
testParseGenerate('puback', {
cmd: 'puback',
retain: false,
qos: 0,
dup: false,
length: 2,
messageId: 2
}, Buffer.from([
64, 2, // Header
0, 2 // Message ID
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for puback packet', Buffer.from([
65, 2, // Header
0, 2 // Message ID
]))
testParseGenerate('puback without reason and no MQTT 5 properties', {
cmd: 'puback',
retain: false,
qos: 0,
dup: false,
length: 2,
messageId: 2,
reasonCode: 0
}, Buffer.from([
64, 2, // Header
0, 2 // Message ID
]), { protocolVersion: 5 })
testParseGenerate('puback with reason and no MQTT 5 properties', {
cmd: 'puback',
retain: false,
qos: 0,
dup: false,
length: 4,
messageId: 2,
reasonCode: 16
}, Buffer.from([
64, 4, // Header
0, 2, // Message ID
16, // reason code
0 // no user properties
]), { protocolVersion: 5 })
testParseGenerate('puback MQTT 5 properties', {
cmd: 'puback',
retain: false,
qos: 0,
dup: false,
length: 24,
messageId: 2,
reasonCode: 16,
properties: {
reasonString: 'test',
userProperties: {
test: 'test'
}
}
}, Buffer.from([
64, 24, // Header
0, 2, // Message ID
16, // reason code
20, // properties length
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
]), { protocolVersion: 5 })
testParseError('Invalid puback reason code', Buffer.from([
64, 4, // Header
0, 2, // Message ID
0x11, // reason code
0 // properties length
]), { protocolVersion: 5 })
testParseGenerate('pubrec', {
cmd: 'pubrec',
retain: false,
qos: 0,
dup: false,
length: 2,
messageId: 2
}, Buffer.from([
80, 2, // Header
0, 2 // Message ID
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for pubrec packet', Buffer.from([
81, 2, // Header
0, 2 // Message ID
]))
testParseGenerate('pubrec MQTT 5 properties', {
cmd: 'pubrec',
retain: false,
qos: 0,
dup: false,
length: 24,
messageId: 2,
reasonCode: 16,
properties: {
reasonString: 'test',
userProperties: {
test: 'test'
}
}
}, Buffer.from([
80, 24, // Header
0, 2, // Message ID
16, // reason code
20, // properties length
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
]), { protocolVersion: 5 })
testParseGenerate('pubrel', {
cmd: 'pubrel',
retain: false,
qos: 1,
dup: false,
length: 2,
messageId: 2
}, Buffer.from([
98, 2, // Header
0, 2 // Message ID
]))
testParseError('Invalid pubrel reason code', Buffer.from([
98, 4, // Header
0, 2, // Message ID
0x11, // Reason code
0 // Properties length
]), { protocolVersion: 5 })
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x2 for pubrel packet', Buffer.from([
96, 2, // Header
0, 2 // Message ID
]))
testParseGenerate('pubrel MQTT5 properties', {
cmd: 'pubrel',
retain: false,
qos: 1,
dup: false,
length: 24,
messageId: 2,
reasonCode: 0x92,
properties: {
reasonString: 'test',
userProperties: {
test: 'test'
}
}
}, Buffer.from([
98, 24, // Header
0, 2, // Message ID
0x92, // reason code
20, // properties length
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
]), { protocolVersion: 5 })
testParseError('Invalid pubrel reason code', Buffer.from([
98, 4, // Header
0, 2, // Message ID
16, // reason code
0 // properties length
]), { protocolVersion: 5 })
testParseGenerate('pubcomp', {
cmd: 'pubcomp',
retain: false,
qos: 0,
dup: false,
length: 2,
messageId: 2
}, Buffer.from([
112, 2, // Header
0, 2 // Message ID
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for pubcomp packet', Buffer.from([
113, 2, // Header
0, 2 // Message ID
]))
testParseGenerate('pubcomp MQTT 5 properties', {
cmd: 'pubcomp',
retain: false,
qos: 0,
dup: false,
length: 24,
messageId: 2,
reasonCode: 0x92,
properties: {
reasonString: 'test',
userProperties: {
test: 'test'
}
}
}, Buffer.from([
112, 24, // Header
0, 2, // Message ID
0x92, // reason code
20, // properties length
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
]), { protocolVersion: 5 })
testParseError('Invalid pubcomp reason code', Buffer.from([
112, 4, // Header
0, 2, // Message ID
16, // reason code
0 // properties length
]), { protocolVersion: 5 })
testParseError('Invalid header flag bits, must be 0x2 for subscribe packet', Buffer.from([
128, 9, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0 // Qos (0)
]))
testParseGenerate('subscribe to one topic', {
cmd: 'subscribe',
retain: false,
qos: 1,
dup: false,
length: 9,
subscriptions: [
{
topic: 'test',
qos: 0
}
],
messageId: 6
}, Buffer.from([
130, 9, // Header (subscribeqos=1length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0 // Qos (0)
]))
testParseError('Invalid subscribe QoS, must be <= 2', Buffer.from([
130, 9, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
3 // Qos
]))
testParseError('Invalid subscribe topic flag bits, bits 7-6 must be 0', Buffer.from([
130, 10, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, // Property length (0)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0x80 // Flags
]), { protocolVersion: 5 })
testParseError('Invalid retain handling, must be <= 2', Buffer.from([
130, 10, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, // Property length (0)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0x30 // Flags
]), { protocolVersion: 5 })
testParseError('Invalid subscribe topic flag bits, bits 7-2 must be 0', Buffer.from([
130, 9, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0x08 // Flags
]))
testParseGenerate('subscribe to one topic by MQTT 5', {
cmd: 'subscribe',
retain: false,
qos: 1,
dup: false,
length: 26,
subscriptions: [
{
topic: 'test',
qos: 0,
nl: false,
rap: true,
rh: 1
}
],
messageId: 6,
properties: {
subscriptionIdentifier: 145,
userProperties: {
test: 'test'
}
}
}, Buffer.from([
130, 26, // Header (subscribeqos=1length=9)
0, 6, // Message ID (6)
16, // properties length
11, 145, 1, // subscriptionIdentifier
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
24 // settings(qos: 0, noLocal: false, Retain as Published: true, retain handling: 1)
]), { protocolVersion: 5 })
testParseGenerate('subscribe to three topics', {
cmd: 'subscribe',
retain: false,
qos: 1,
dup: false,
length: 23,
subscriptions: [
{
topic: 'test',
qos: 0
}, {
topic: 'uest',
qos: 1
}, {
topic: 'tfst',
qos: 2
}
],
messageId: 6
}, Buffer.from([
130, 23, // Header (publishqos=1length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0, // Qos (0)
0, 4, // Topic length
117, 101, 115, 116, // Topic (uest)
1, // Qos (1)
0, 4, // Topic length
116, 102, 115, 116, // Topic (tfst)
2 // Qos (2)
]))
testParseGenerate('subscribe to 3 topics by MQTT 5', {
cmd: 'subscribe',
retain: false,
qos: 1,
dup: false,
length: 40,
subscriptions: [
{
topic: 'test',
qos: 0,
nl: false,
rap: true,
rh: 1
},
{
topic: 'uest',
qos: 1,
nl: false,
rap: false,
rh: 0
}, {
topic: 'tfst',
qos: 2,
nl: true,
rap: false,
rh: 0
}
],
messageId: 6,
properties: {
subscriptionIdentifier: 145,
userProperties: {
test: 'test'
}
}
}, Buffer.from([
130, 40, // Header (subscribeqos=1length=9)
0, 6, // Message ID (6)
16, // properties length
11, 145, 1, // subscriptionIdentifier
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
24, // settings(qos: 0, noLocal: false, Retain as Published: true, retain handling: 1)
0, 4, // Topic length
117, 101, 115, 116, // Topic (uest)
1, // Qos (1)
0, 4, // Topic length
116, 102, 115, 116, // Topic (tfst)
6 // Qos (2), No Local: true
]), { protocolVersion: 5 })
testParseGenerate('suback', {
cmd: 'suback',
retain: false,
qos: 0,
dup: false,
length: 5,
granted: [0, 1, 2],
messageId: 6
}, Buffer.from([
144, 5, // Header
0, 6, // Message ID
0, 1, 2
]))
testParseGenerate('suback', {
cmd: 'suback',
retain: false,
qos: 0,
dup: false,
length: 7,
granted: [0, 1, 2, 128],
messageId: 6
}, Buffer.from([
144, 7, // Header
0, 6, // Message ID
0, // Property length
0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80
]), { protocolVersion: 5 })
testParseError('Invalid suback QoS, must be 0, 1, 2 or 128', Buffer.from([
144, 6, // Header
0, 6, // Message ID
0, 1, 2, 3 // Granted qos (0, 1, 2)
]))
testParseError('Invalid suback code', Buffer.from([
144, 6, // Header
0, 6, // Message ID
0, 1, 2, 0x79 // Granted qos (0, 1, 2) and an invalid code
]), { protocolVersion: 5 })
testParseGenerate('suback MQTT 5', {
cmd: 'suback',
retain: false,
qos: 0,
dup: false,
length: 27,
granted: [0, 1, 2, 128],
messageId: 6,
properties: {
reasonString: 'test',
userProperties: {
test: 'test'
}
}
}, Buffer.from([
144, 27, // Header
0, 6, // Message ID
20, // properties length
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80
]), { protocolVersion: 5 })
testParseGenerate('unsubscribe', {
cmd: 'unsubscribe',
retain: false,
qos: 1,
dup: false,
length: 14,
unsubscriptions: [
'tfst',
'test'
],
messageId: 7
}, Buffer.from([
162, 14,
0, 7, // Message ID (7)
0, 4, // Topic length
116, 102, 115, 116, // Topic (tfst)
0, 4, // Topic length,
116, 101, 115, 116 // Topic (test)
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x2 for unsubscribe packet', Buffer.from([
160, 14,
0, 7, // Message ID (7)
0, 4, // Topic length
116, 102, 115, 116, // Topic (tfst)
0, 4, // Topic length,
116, 101, 115, 116 // Topic (test)
]))
testGenerateError('Invalid unsubscriptions', {
cmd: 'unsubscribe',
retain: false,
qos: 1,
dup: true,
length: 5,
unsubscriptions: 5,
messageId: 7
}, {}, 'unsubscribe with unsubscriptions not an array')
testGenerateError('Invalid unsubscriptions', {
cmd: 'unsubscribe',
retain: false,
qos: 1,
dup: true,
length: 5,
unsubscriptions: [1, 2],
messageId: 7
}, {}, 'unsubscribe with unsubscriptions as an object')
testParseGenerate('unsubscribe MQTT 5', {
cmd: 'unsubscribe',
retain: false,
qos: 1,
dup: false,
length: 28,
unsubscriptions: [
'tfst',
'test'
],
messageId: 7,
properties: {
userProperties: {
test: 'test'
}
}
}, Buffer.from([
162, 28,
0, 7, // Message ID (7)
13, // properties length
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
0, 4, // Topic length
116, 102, 115, 116, // Topic (tfst)
0, 4, // Topic length,
116, 101, 115, 116 // Topic (test)
]), { protocolVersion: 5 })
testParseGenerate('unsuback', {
cmd: 'unsuback',
retain: false,
qos: 0,
dup: false,
length: 2,
messageId: 8
}, Buffer.from([
176, 2, // Header
0, 8 // Message ID
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for unsuback packet', Buffer.from([
177, 2, // Header
0, 8 // Message ID
]))
testParseGenerate('unsuback MQTT 5', {
cmd: 'unsuback',
retain: false,
qos: 0,
dup: false,
length: 25,
messageId: 8,
properties: {
reasonString: 'test',
userProperties: {
test: 'test'
}
},
granted: [0, 128]
}, Buffer.from([
176, 25, // Header
0, 8, // Message ID
20, // properties length
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
0, 128 // success and error
]), { protocolVersion: 5 })
testParseError('Invalid unsuback code', Buffer.from([
176, 4, // Header
0, 8, // Message ID
0, // properties length
0x84 // reason codes
]), { protocolVersion: 5 })
testParseGenerate('pingreq', {
cmd: 'pingreq',
retain: false,
qos: 0,
dup: false,
length: 0
}, Buffer.from([
192, 0 // Header
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for pingreq packet', Buffer.from([
193, 0 // Header
]))
testParseGenerate('pingresp', {
cmd: 'pingresp',
retain: false,
qos: 0,
dup: false,
length: 0
}, Buffer.from([
208, 0 // Header
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for pingresp packet', Buffer.from([
209, 0 // Header
]))
testParseGenerate('disconnect', {
cmd: 'disconnect',
retain: false,
qos: 0,
dup: false,
length: 0
}, Buffer.from([
224, 0 // Header
]))
// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
testParseError('Invalid header flag bits, must be 0x0 for disconnect packet', Buffer.from([
225, 0 // Header
]))
testParseGenerate('disconnect MQTT 5', {
cmd: 'disconnect',
retain: false,
qos: 0,
dup: false,
length: 34,
reasonCode: 0,
properties: {
sessionExpiryInterval: 145,
reasonString: 'test',
userProperties: {
test: 'test'
},
serverReference: 'test'
}
}, Buffer.from([
224, 34, // Header
0, // reason code
32, // properties length
17, 0, 0, 0, 145, // sessionExpiryInterval
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
28, 0, 4, 116, 101, 115, 116// serverReference
]), { protocolVersion: 5 })
testParseGenerate('disconnect MQTT 5 with no properties', {
cmd: 'disconnect',
retain: false,
qos: 0,
dup: false,
length: 2,
reasonCode: 0
}, Buffer.from([
224, 2, // Fixed Header (DISCONNECT, Remaining Length)
0, // Reason Code (Normal Disconnection)
0 // Property Length (0 => No Properties)
]), { protocolVersion: 5 })
testParseError('Invalid disconnect reason code', Buffer.from([
224, 2, // Fixed Header (DISCONNECT, Remaining Length)
0x05, // Reason Code (Normal Disconnection)
0 // Property Length (0 => No Properties)
]), { protocolVersion: 5 })
testParseGenerate('auth MQTT 5', {
cmd: 'auth',
retain: false,
qos: 0,
dup: false,
length: 36,
reasonCode: 0,
properties: {
authenticationMethod: 'test',
authenticationData: Buffer.from([0, 1, 2, 3]),
reasonString: 'test',
userProperties: {
test: 'test'
}
}
}, Buffer.from([
240, 36, // Header
0, // reason code
34, // properties length
21, 0, 4, 116, 101, 115, 116, // auth method
22, 0, 4, 0, 1, 2, 3, // auth data
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
]), { protocolVersion: 5 })
testParseError('Invalid auth reason code', Buffer.from([
240, 2, // Fixed Header (DISCONNECT, Remaining Length)
0x17, // Reason Code
0 // Property Length (0 => No Properties)
]), { protocolVersion: 5 })
testGenerateError('Invalid protocolId', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 42,
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 'password'
})
testGenerateError('Invalid protocol version', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 1,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 'password'
})
testGenerateError('clientId must be supplied before 3.1.1', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
username: 'username',
password: 'password'
})
testGenerateError('clientId must be given if cleanSession set to 0', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQTT',
protocolVersion: 4,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: false,
keepalive: 30,
username: 'username',
password: 'password'
})
testGenerateError('Invalid keepalive', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 'hello',
clientId: 'test',
username: 'username',
password: 'password'
})
testGenerateError('Invalid keepalive', {
cmd: 'connect',
keepalive: 3.1416
})
testGenerateError('Invalid will', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: 42,
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 'password'
})
testGenerateError('Invalid will topic', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 'password'
})
testGenerateError('Invalid will payload', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 42
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 'password'
})
testGenerateError('Invalid username', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 42,
password: 'password'
})
testGenerateError('Invalid password', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
username: 'username',
password: 42
})
testGenerateError('Username is required to use password', {
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 54,
protocolId: 'MQIsdp',
protocolVersion: 3,
will: {
retain: true,
qos: 2,
topic: 'topic',
payload: 'payload'
},
clean: true,
keepalive: 30,
clientId: 'test',
password: 'password'
})
testGenerateError('Invalid messageExpiryInterval: -4321', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 60,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: true,
messageExpiryInterval: -4321,
topicAlias: 100,
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
},
subscriptionIdentifier: 120,
contentType: 'test'
}
}, { protocolVersion: 5 })
testGenerateError('Invalid topicAlias: -100', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 60,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: true,
messageExpiryInterval: 4321,
topicAlias: -100,
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
},
subscriptionIdentifier: 120,
contentType: 'test'
}
}, { protocolVersion: 5 })
testGenerateError('Invalid subscriptionIdentifier: -120', {
cmd: 'publish',
retain: true,
qos: 2,
dup: true,
length: 60,
topic: 'test',
payload: Buffer.from('test'),
messageId: 10,
properties: {
payloadFormatIndicator: true,
messageExpiryInterval: 4321,
topicAlias: 100,
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
},
subscriptionIdentifier: -120,
contentType: 'test'
}
}, { protocolVersion: 5 })
test('support cork', t => {
t.plan(9)
const dest = WS()
dest._write = (chunk, enc, cb) => {
t.pass('_write called')
cb()
}
mqtt.writeToStream({
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 18,
protocolId: 'MQIsdp',
protocolVersion: 3,
clean: false,
keepalive: 30,
clientId: 'test'
}, dest)
dest.end()
})
// The following test case was designed after experiencing errors
// when trying to connect with tls on a non tls mqtt port
// the specific behaviour is:
// - first byte suggests this is a connect message
// - second byte suggests message length to be smaller than buffer length
// thus payload processing starts
// - the first two bytes suggest a protocol identifier string length
// that leads the parser pointer close to the end of the buffer
// - when trying to read further connect flags the buffer produces
// a "out of range" Error
//
testParseError('Packet too short', Buffer.from([
16, 9,
0, 6,
77, 81, 73, 115, 100, 112,
3
]))
// CONNECT Packets that show other protocol IDs than
// the valid values MQTT and MQIsdp should cause an error
// those packets are a hint that this is not a mqtt connection
testParseError('Invalid protocolId', Buffer.from([
16, 18,
0, 6,
65, 65, 65, 65, 65, 65, // AAAAAA
3, // Protocol version
0, // Connect flags
0, 10, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116 // Client ID
]))
// CONNECT Packets that contain an unsupported protocol version
// Flag (i.e. not `3` or `4` or '5') should cause an error
testParseError('Invalid protocol version', Buffer.from([
16, 18,
0, 6,
77, 81, 73, 115, 100, 112, // Protocol ID
1, // Protocol version
0, // Connect flags
0, 10, // Keepalive
0, 4, // Client ID length
116, 101, 115, 116 // Client ID
]))
// When a packet contains a string in the variable header and the
// given string length of this exceeds the overall length of the packet that
// was specified in the fixed header, parsing must fail.
// this case simulates this behavior with the protocol ID string of the
// CONNECT packet. The fixed header suggests a remaining length of 8 bytes
// which would be exceeded by the string length of 15
// in this case, a protocol ID parse error is expected
testParseError('Cannot parse protocolId', Buffer.from([
16, 8, // Fixed header
0, 15, // string length 15 --> 15 > 8 --> error!
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112,
77, 81, 73, 115, 100, 112
]))
testParseError('Unknown property', Buffer.from([
61, 60, // Header
0, 4, // Topic length
116, 101, 115, 116, // Topic (test)
0, 10, // Message ID
47, // properties length
126, 1, // unknown property
2, 0, 0, 16, 225, // message expiry interval
35, 0, 100, // topicAlias
8, 0, 5, 116, 111, 112, 105, 99, // response topic
9, 0, 4, 1, 2, 3, 4, // correlationData
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
11, 120, // subscriptionIdentifier
3, 0, 4, 116, 101, 115, 116, // content type
116, 101, 115, 116 // Payload (test)
]), { protocolVersion: 5 })
testParseError('Not supported auth packet for this version MQTT', Buffer.from([
240, 36, // Header
0, // reason code
34, // properties length
21, 0, 4, 116, 101, 115, 116, // auth method
22, 0, 4, 0, 1, 2, 3, // auth data
31, 0, 4, 116, 101, 115, 116, // reasonString
38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
]))
// When a Subscribe packet contains a topic_filter and the given
// length is topic_filter.length + 1 then the last byte (requested QoS) is interpreted as topic_filter
// reading the requested_qos at the end causes 'Index out of range' read
testParseError('Malformed Subscribe Payload', Buffer.from([
130, 14, // subscribe header and remaining length
0, 123, // packet ID
0, 10, // topic filter length
104, 105, 106, 107, 108, 47, 109, 110, 111, // topic filter with length of 9 bytes
0 // requested QoS
]))
test('Cannot parse property code type', t => {
const packets = Buffer.from([
16, 16, 0, 4, 77, 81, 84, 84, 5, 2, 0, 60, 3, 33, 0, 20, 0, 0, 98, 2, 211, 1, 224, 2, 0, 32
])
t.plan(3)
const parser = mqtt.parser()
parser.on('error', err => {
t.equal(err.message, 'Cannot parse property code type', 'expected error message')
t.end()
})
parser.on('packet', (packet) => {
t.pass('Packet parsed')
})
parser.parse(packets)
})
testWriteToStreamError('Invalid command', {
cmd: 'invalid'
})
testWriteToStreamError('Invalid protocolId', {
cmd: 'connect',
protocolId: {}
})
test('userProperties null prototype', t => {
t.plan(3)
const packet = mqtt.generate({
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 125,
protocolId: 'MQTT',
protocolVersion: 5,
will: {
retain: true,
qos: 2,
properties: {
willDelayInterval: 1234,
payloadFormatIndicator: false,
messageExpiryInterval: 4321,
contentType: 'test',
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
}
},
topic: 'topic',
payload: Buffer.from([4, 3, 2, 1])
},
clean: true,
keepalive: 30,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumPacketSize: 100,
topicAliasMaximum: 456,
requestResponseInformation: true,
requestProblemInformation: true,
userProperties: {
test: 'test'
},
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
},
clientId: 'test'
})
const parser = mqtt.parser()
parser.on('packet', packet => {
t.equal(packet.cmd, 'connect')
t.equal(Object.getPrototypeOf(packet.properties.userProperties), null)
t.equal(Object.getPrototypeOf(packet.will.properties.userProperties), null)
})
parser.parse(packet)
})
test('stops parsing after first error', t => {
t.plan(4)
const parser = mqtt.parser()
let packetCount = 0
let errorCount = 0
let expectedPackets = 1
let expectedErrors = 1
parser.on('packet', packet => {
t.ok(++packetCount <= expectedPackets, `expected <= ${expectedPackets} packets`)
})
parser.on('error', erroneous => {
t.ok(++errorCount <= expectedErrors, `expected <= ${expectedErrors} errors`)
})
parser.parse(Buffer.from([
// First, a valid connect packet:
16, 12, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
4, // Protocol version
2, // Connect flags
0, 30, // Keepalive
0, 0, // Client ID length
// Then an invalid subscribe packet:
128, 9, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0, // Qos (0)
// And another invalid subscribe packet:
128, 9, // Header (subscribeqos=0length=9)
0, 6, // Message ID (6)
0, 4, // Topic length,
116, 101, 115, 116, // Topic (test)
0, // Qos (0)
// Finally, a valid disconnect packet:
224, 0 // Header
]))
// Calling parse again clears the error and continues parsing
packetCount = 0
errorCount = 0
expectedPackets = 2
expectedErrors = 0
parser.parse(Buffer.from([
// Connect:
16, 12, // Header
0, 4, // Protocol ID length
77, 81, 84, 84, // Protocol ID
4, // Protocol version
2, // Connect flags
0, 30, // Keepalive
0, 0, // Client ID length
// Disconnect:
224, 0 // Header
]))
})
test('undefined properties', t => {
t.plan(2)
const packet = mqtt.generate({
cmd: 'connect',
retain: false,
qos: 0,
dup: false,
length: 125,
protocolId: 'MQTT',
protocolVersion: 5,
will: {
retain: true,
qos: 2,
properties: {
willDelayInterval: 1234,
payloadFormatIndicator: false,
messageExpiryInterval: 4321,
contentType: 'test',
responseTopic: 'topic',
correlationData: Buffer.from([1, 2, 3, 4]),
userProperties: {
test: 'test'
}
},
topic: 'topic',
payload: Buffer.from([4, 3, 2, 1])
},
clean: true,
keepalive: 30,
properties: {
sessionExpiryInterval: 1234,
receiveMaximum: 432,
maximumPacketSize: 100,
topicAliasMaximum: 456,
requestResponseInformation: true,
requestProblemInformation: true,
correlationData: undefined,
userProperties: {
test: 'test'
},
authenticationMethod: 'test',
authenticationData: Buffer.from([1, 2, 3, 4])
},
clientId: 'test'
})
const parser = mqtt.parser()
parser.on('packet', packet => {
t.equal(packet.cmd, 'connect')
t.equal(Object.hasOwn(packet.properties, 'correlationData'), false)
})
parser.parse(packet)
})
testGenerateErrorMultipleCmds([
'publish',
'puback',
'pubrec',
'pubrel',
'subscribe',
'suback',
'unsubscribe',
'unsuback'
], 'Invalid messageId', {
qos: 1, // required for publish
topic: 'test', // required for publish
messageId: 'a'
}, {})
mqtt-packet-9.0.2/testRandom.js000066400000000000000000000050071476155302400164430ustar00rootroot00000000000000const mqtt = require('./')
const crypto = require('crypto')
const max = 1E5
const start = Date.now() / 1000
let errors = 0
let packets = 0
let randomPacket
const firstBytes = [
16 * 1, // CONNECT
16 * 2, // CONNACK
16 * 3, // PUBLISH, QoS: 0, No Retain, No Dup
16 * 3 + 1, // PUBLISH, QoS: 0, Retain, No Dup
16 * 3 + 8, // PUBLISH, QoS: 0, No Retain, Dup
16 * 3 + 1 + 8, // PUBLISH, QoS: 0, Retain, Dup
16 * 3 + 2, // PUBLISH, QoS: 1, No Retain, No Dup
16 * 3 + 2 + 1, // PUBLISH, QoS: 1, Retain, No Dup
16 * 3 + 2 + 8, // PUBLISH, QoS: 1, No Retain, Dup
16 * 3 + 2 + 1 + 8, // PUBLISH, QoS: 1, Retain, Dup
16 * 3 + 4, // PUBLISH, QoS: 2, No Retain, No Dup
16 * 3 + 4 + 1, // PUBLISH, QoS: 2, Retain, No Dup
16 * 3 + 4 + 8, // PUBLISH, QoS: 2, No Retain, Dup
16 * 3 + 4 + 1 + 8, // PUBLISH, QoS: 2, Retain, Dup
16 * 4, // PUBACK
16 * 5, // PUBREC
16 * 6, // PUBREL
16 * 7, // PUBCOMP
16 * 8, // SUBSCRIBE
16 * 9, // SUBACK
16 * 10, // UNSUBSCRIBE
16 * 11, // UNSUBACK
16 * 12, // PINGREQ
16 * 13, // PINGRESP
16 * 14, // DISCONNECT
16 * 15 // RESERVED
]
function doParse () {
const parser = mqtt.parser()
parser.on('error', onError)
parser.on('packet', onPacket)
randomPacket = crypto.randomBytes(Math.floor(Math.random() * 512))
// Increase probability to have a valid first byte in order to at least
// enter the parser
if (Math.random() > 0.2 && randomPacket.length > 0) randomPacket.writeUInt8(firstBytes[Math.floor(Math.random() * firstBytes.length)], 0)
parser.parse(randomPacket)
}
try {
console.log('Starting benchmark')
for (let i = 0; i < max; i++) {
doParse()
}
} catch (e) {
console.log('Exception occurred at packet')
console.log(randomPacket)
console.log(e.message)
console.log(e.stack)
}
function onError () {
errors++
}
function onPacket () {
packets++
}
const delta = Math.abs(max - packets - errors)
const time = Date.now() / 1000 - start
console.log('Benchmark complete')
console.log('==========================')
console.log('Sent packets:', max)
console.log('Total time:', Math.round(time * 100) / 100, 'seconds', '\r\n')
console.log('Valid packets:', packets)
console.log('Erroneous packets:', errors)
if ((max - packets - errors) < 0) console.log('Excess packets:', delta, '\r\n')
else console.log('Missing packets:', delta, '\r\n')
console.log('Total packets:', packets + errors)
console.log('Total errors:', errors + delta)
console.log('Error rate:', `${((errors + delta) / max * 100).toFixed(2)}%`)
console.log('==========================')
mqtt-packet-9.0.2/types/000077500000000000000000000000001476155302400151275ustar00rootroot00000000000000mqtt-packet-9.0.2/types/index.d.ts000066400000000000000000000132121476155302400170270ustar00rootroot00000000000000import EventEmitter = NodeJS.EventEmitter
import WritableStream = NodeJS.WritableStream
export declare type QoS = 0 | 1 | 2
export declare type PacketCmd = 'auth' |
'connack' |
'connect' |
'disconnect' |
'pingreq' |
'pingresp' |
'puback' |
'pubcomp' |
'publish' |
'pubrel' |
'pubrec' |
'suback' |
'subscribe' |
'unsuback' |
'unsubscribe'
export declare type UserProperties = {[index: string]: string | string[]}
export interface IPacket {
cmd: PacketCmd
messageId?: number
length?: number
}
export interface IAuthPacket extends IPacket {
cmd: 'auth'
reasonCode: number,
properties?: {
authenticationMethod?: string,
authenticationData?: Buffer,
reasonString?: string,
userProperties?: UserProperties,
}
}
export interface IConnectPacket extends IPacket {
cmd: 'connect'
clientId: string
protocolVersion?: 4 | 5 | 3
protocolId?: 'MQTT' | 'MQIsdp'
clean?: boolean
keepalive?: number
username?: string
password?: Buffer
will?: {
topic: string
payload: Buffer | string
qos?: QoS
retain?: boolean
properties?: {
willDelayInterval?: number,
payloadFormatIndicator?: boolean,
messageExpiryInterval?: number,
contentType?: string,
responseTopic?: string,
correlationData?: Buffer,
userProperties?: UserProperties
}
}
properties?: {
sessionExpiryInterval?: number,
receiveMaximum?: number,
maximumPacketSize?: number,
topicAliasMaximum?: number,
requestResponseInformation?: boolean,
requestProblemInformation?: boolean,
userProperties?: UserProperties,
authenticationMethod?: string,
authenticationData?: Buffer
}
}
export interface IPublishPacket extends IPacket {
cmd: 'publish'
qos: QoS
dup: boolean
retain: boolean
topic: string
payload: string | Buffer
properties?: {
payloadFormatIndicator?: boolean,
messageExpiryInterval?: number,
topicAlias?: number,
responseTopic?: string,
correlationData?: Buffer,
userProperties?: UserProperties,
subscriptionIdentifier?: number | number[],
contentType?: string
}
}
export interface IConnackPacket extends IPacket {
cmd: 'connack'
returnCode?: number,
reasonCode?: number,
sessionPresent: boolean
properties?: {
sessionExpiryInterval?: number,
receiveMaximum?: number,
maximumQoS?: number,
retainAvailable?: boolean,
maximumPacketSize?: number,
assignedClientIdentifier?: string,
topicAliasMaximum?: number,
reasonString?: string,
userProperties?: UserProperties,
wildcardSubscriptionAvailable?: boolean,
subscriptionIdentifiersAvailable?: boolean,
sharedSubscriptionAvailable?: boolean,
serverKeepAlive?: number,
responseInformation?: string,
serverReference?: string,
authenticationMethod?: string,
authenticationData?: Buffer
}
}
export interface ISubscription {
topic: string
qos: QoS,
nl?: boolean,
rap?: boolean,
rh?: number
}
export interface ISubscribePacket extends IPacket {
cmd: 'subscribe'
subscriptions: ISubscription[],
properties?: {
reasonString?: string,
subscriptionIdentifier?: number,
userProperties?: UserProperties
}
}
export interface ISubackPacket extends IPacket {
cmd: 'suback',
reasonCode?: number,
properties?: {
reasonString?: string,
userProperties?: UserProperties
},
granted: number[] | Object[]
}
export interface IUnsubscribePacket extends IPacket {
cmd: 'unsubscribe',
properties?: {
reasonString?: string,
userProperties?: UserProperties
},
unsubscriptions: string[]
}
export interface IUnsubackPacket extends IPacket {
cmd: 'unsuback',
reasonCode?: number,
properties?: {
reasonString?: string,
userProperties?: UserProperties
},
granted: number[]
}
export interface IPubackPacket extends IPacket {
cmd: 'puback',
reasonCode?: number,
properties?: {
reasonString?: string,
userProperties?: UserProperties
}
}
export interface IPubcompPacket extends IPacket {
cmd: 'pubcomp',
reasonCode?: number,
properties?: {
reasonString?: string,
userProperties?: UserProperties
}
}
export interface IPubrelPacket extends IPacket {
cmd: 'pubrel',
reasonCode?: number,
properties?: {
reasonString?: string,
userProperties?: UserProperties
}
}
export interface IPubrecPacket extends IPacket {
cmd: 'pubrec',
reasonCode?: number,
properties?: {
reasonString?: string,
userProperties?: UserProperties
}
}
export interface IPingreqPacket extends IPacket {
cmd: 'pingreq'
}
export interface IPingrespPacket extends IPacket {
cmd: 'pingresp'
}
export interface IDisconnectPacket extends IPacket {
cmd: 'disconnect',
reasonCode?: number,
properties?: {
sessionExpiryInterval?: number,
reasonString?: string,
userProperties?: UserProperties,
serverReference?: string
}
}
export declare type Packet = IConnectPacket |
IPublishPacket |
IConnackPacket |
ISubscribePacket |
ISubackPacket |
IUnsubscribePacket |
IUnsubackPacket |
IPubackPacket |
IPubcompPacket |
IPubrelPacket |
IPingreqPacket |
IPingrespPacket |
IDisconnectPacket |
IPubrecPacket |
IAuthPacket
export interface Parser extends EventEmitter {
on(event: 'packet', callback: (packet: Packet) => void): this
on(event: 'error', callback: (error: any) => void): this
parse(buffer: Buffer, opts?: Object): number
}
export declare function parser(opts?: Object): Parser
export declare function generate(packet: Packet, opts?: Object): Buffer
export declare function writeToStream(object: Packet, stream: WritableStream, opts?: Object): boolean
export declare namespace writeToStream {
let cacheNumbers: boolean
}
mqtt-packet-9.0.2/writeToStream.js000066400000000000000000000751271476155302400171460ustar00rootroot00000000000000const protocol = require('./constants')
const { Buffer } = require('buffer')
const empty = Buffer.allocUnsafe(0)
const zeroBuf = Buffer.from([0])
const numbers = require('./numbers')
const nextTick = require('process-nextick-args').nextTick
const debug = require('debug')('mqtt-packet:writeToStream')
const numCache = numbers.cache
const generateNumber = numbers.generateNumber
const generateCache = numbers.generateCache
const genBufVariableByteInt = numbers.genBufVariableByteInt
const generate4ByteBuffer = numbers.generate4ByteBuffer
let writeNumber = writeNumberCached
let toGenerate = true
function generate (packet, stream, opts) {
debug('generate called')
if (stream.cork) {
stream.cork()
nextTick(uncork, stream)
}
if (toGenerate) {
toGenerate = false
generateCache()
}
debug('generate: packet.cmd: %s', packet.cmd)
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.destroy(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 () {
return writeNumber === writeNumberCached
},
set (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) {
const settings = packet || {}
const protocolId = settings.protocolId || 'MQTT'
let protocolVersion = settings.protocolVersion || 4
const will = settings.will
let clean = settings.clean
const keepalive = settings.keepalive || 0
const clientId = settings.clientId || ''
const username = settings.username
const password = settings.password
/* mqtt5 new oprions */
const properties = settings.properties
if (clean === undefined) clean = true
let length = 0
// Must be a string and non-falsy
if (!protocolId ||
(typeof protocolId !== 'string' && !Buffer.isBuffer(protocolId))) {
stream.destroy(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.destroy(new Error('Invalid protocol version'))
return false
} else length += 1
// ClientId might be omitted in 3.1.1 and 5, but only if cleanSession is set to 1
if ((typeof clientId === 'string' || Buffer.isBuffer(clientId)) &&
(clientId || protocolVersion >= 4) && (clientId || clean)) {
length += Buffer.byteLength(clientId) + 2
} else {
if (protocolVersion < 4) {
stream.destroy(new Error('clientId must be supplied before 3.1.1'))
return false
}
if ((clean * 1) === 0) {
stream.destroy(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.destroy(new Error('Invalid keepalive'))
return false
} else length += 2
// Connect flags
length += 1
let propertiesData
let willProperties
// Properties
if (protocolVersion === 5) {
propertiesData = getProperties(stream, properties)
if (!propertiesData) { return false }
length += propertiesData.length
}
// If will exists...
if (will) {
// It must be an object
if (typeof will !== 'object') {
stream.destroy(new Error('Invalid will'))
return false
}
// It must have topic typeof string
if (!will.topic || typeof will.topic !== 'string') {
stream.destroy(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.destroy(new Error('Invalid will payload'))
return false
}
}
// will properties
willProperties = {}
if (protocolVersion === 5) {
willProperties = getProperties(stream, will.properties)
if (!willProperties) { return false }
length += willProperties.length
}
}
// Username
let providedUsername = false
if (username != null) {
if (isStringOrBuffer(username)) {
providedUsername = true
length += Buffer.byteLength(username) + 2
} else {
stream.destroy(new Error('Invalid username'))
return false
}
}
// Password
if (password != null) {
if (!providedUsername) {
stream.destroy(new Error('Username is required to use password'))
return false
}
if (isStringOrBuffer(password)) {
length += byteLength(password) + 2
} else {
stream.destroy(new Error('Invalid password'))
return false
}
}
// Generate header
stream.write(protocol.CONNECT_HEADER)
// Generate length
writeVarByteInt(stream, length)
// Generate protocol ID
writeStringOrBuffer(stream, protocolId)
if (settings.bridgeMode) {
protocolVersion += 128
}
stream.write(
protocolVersion === 131
? protocol.VERSION131
: protocolVersion === 132
? protocol.VERSION132
: protocolVersion === 4
? protocol.VERSION4
: protocolVersion === 5
? protocol.VERSION5
: protocol.VERSION3
)
// Connect flags
let 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) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const rc = version === 5 ? settings.reasonCode : settings.returnCode
const properties = settings.properties
let length = 2 // length of rc and sessionHeader
// Check return code
if (typeof rc !== 'number') {
stream.destroy(new Error('Invalid return code'))
return false
}
// mqtt5 properties
let propertiesData = null
if (version === 5) {
propertiesData = getProperties(stream, properties)
if (!propertiesData) { return false }
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) {
debug('publish: packet: %o', packet)
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const qos = settings.qos || 0
const retain = settings.retain ? protocol.RETAIN_MASK : 0
const topic = settings.topic
const payload = settings.payload || empty
const id = settings.messageId
const properties = settings.properties
let 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.destroy(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.destroy(new Error('Invalid messageId'))
return false
} else if (qos) length += 2
// mqtt5 properties
let propertiesData = null
if (version === 5) {
propertiesData = getProperties(stream, properties)
if (!propertiesData) { return false }
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
debug('publish: payload: %o', payload)
return stream.write(payload)
}
/* Puback, pubrec, pubrel and pubcomp */
function confirmation (packet, stream, opts) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const type = settings.cmd || 'puback'
const id = settings.messageId
const dup = (settings.dup && type === 'pubrel') ? protocol.DUP_MASK : 0
let qos = 0
const reasonCode = settings.reasonCode
const properties = settings.properties
let length = version === 5 ? 3 : 2
if (type === 'pubrel') qos = 1
// Check message ID
if (typeof id !== 'number') {
stream.destroy(new Error('Invalid messageId'))
return false
}
// properies mqtt 5
let propertiesData = null
if (version === 5) {
// Confirm should not add empty property length with no properties (rfc 3.4.2.2.1)
if (typeof properties === 'object') {
propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length)
if (!propertiesData) { return false }
length += propertiesData.length
}
}
// Header
stream.write(protocol.ACKS[type][qos][dup][0])
// Length === 3 is only true of version === 5 and no properties; therefore if reasonCode === 0 we are allowed to skip both bytes - but if we write the reason code we also have to write property length [MQTT-3.4.2-1].
if (length === 3) length += reasonCode !== 0 ? 1 : -1
writeVarByteInt(stream, length)
// Message ID
writeNumber(stream, id)
// reason code in header - but only if it couldn't be omitted - indicated by length !== 2.
if (version === 5 && length !== 2) {
stream.write(Buffer.from([reasonCode]))
}
// properties mqtt 5
if (propertiesData !== null) {
propertiesData.write()
} else {
if (length === 4) {
// we have no properties but have written a reason code - so we need to indicate empty properties by filling in a zero.
stream.write(Buffer.from([0]))
}
}
return true
}
function subscribe (packet, stream, opts) {
debug('subscribe: packet: ')
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const dup = settings.dup ? protocol.DUP_MASK : 0
const id = settings.messageId
const subs = settings.subscriptions
const properties = settings.properties
let length = 0
// Check message ID
if (typeof id !== 'number') {
stream.destroy(new Error('Invalid messageId'))
return false
} else length += 2
// properies mqtt 5
let propertiesData = null
if (version === 5) {
propertiesData = getProperties(stream, properties)
if (!propertiesData) { return false }
length += propertiesData.length
}
// Check subscriptions
if (typeof subs === 'object' && subs.length) {
for (let i = 0; i < subs.length; i += 1) {
const itopic = subs[i].topic
const iqos = subs[i].qos
if (typeof itopic !== 'string') {
stream.destroy(new Error('Invalid subscriptions - invalid topic'))
return false
}
if (typeof iqos !== 'number') {
stream.destroy(new Error('Invalid subscriptions - invalid qos'))
return false
}
if (version === 5) {
const nl = subs[i].nl || false
if (typeof nl !== 'boolean') {
stream.destroy(new Error('Invalid subscriptions - invalid No Local'))
return false
}
const rap = subs[i].rap || false
if (typeof rap !== 'boolean') {
stream.destroy(new Error('Invalid subscriptions - invalid Retain as Published'))
return false
}
const rh = subs[i].rh || 0
if (typeof rh !== 'number' || rh > 2) {
stream.destroy(new Error('Invalid subscriptions - invalid Retain Handling'))
return false
}
}
length += Buffer.byteLength(itopic) + 2 + 1
}
} else {
stream.destroy(new Error('Invalid subscriptions'))
return false
}
// Generate header
debug('subscribe: writing to stream: %o', protocol.SUBSCRIBE_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()
}
let result = true
// Generate subs
for (const sub of subs) {
const jtopic = sub.topic
const jqos = sub.qos
const jnl = +sub.nl
const jrap = +sub.rap
const jrh = sub.rh
let 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) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const id = settings.messageId
const granted = settings.granted
const properties = settings.properties
let length = 0
// Check message ID
if (typeof id !== 'number') {
stream.destroy(new Error('Invalid messageId'))
return false
} else length += 2
// Check granted qos vector
if (typeof granted === 'object' && granted.length) {
for (let i = 0; i < granted.length; i += 1) {
if (typeof granted[i] !== 'number') {
stream.destroy(new Error('Invalid qos vector'))
return false
}
length += 1
}
} else {
stream.destroy(new Error('Invalid qos vector'))
return false
}
// properies mqtt 5
let 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) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const id = settings.messageId
const dup = settings.dup ? protocol.DUP_MASK : 0
const unsubs = settings.unsubscriptions
const properties = settings.properties
let length = 0
// Check message ID
if (typeof id !== 'number') {
stream.destroy(new Error('Invalid messageId'))
return false
} else {
length += 2
}
// Check unsubs
if (typeof unsubs === 'object' && unsubs.length) {
for (let i = 0; i < unsubs.length; i += 1) {
if (typeof unsubs[i] !== 'string') {
stream.destroy(new Error('Invalid unsubscriptions'))
return false
}
length += Buffer.byteLength(unsubs[i]) + 2
}
} else {
stream.destroy(new Error('Invalid unsubscriptions'))
return false
}
// properies mqtt 5
let propertiesData = null
if (version === 5) {
propertiesData = getProperties(stream, properties)
if (!propertiesData) { return false }
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
let result = true
for (let j = 0; j < unsubs.length; j++) {
result = writeString(stream, unsubs[j])
}
return result
}
function unsuback (packet, stream, opts) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const id = settings.messageId
const dup = settings.dup ? protocol.DUP_MASK : 0
const granted = settings.granted
const properties = settings.properties
const type = settings.cmd
const qos = 0
let length = 2
// Check message ID
if (typeof id !== 'number') {
stream.destroy(new Error('Invalid messageId'))
return false
}
// Check granted
if (version === 5) {
if (typeof granted === 'object' && granted.length) {
for (let i = 0; i < granted.length; i += 1) {
if (typeof granted[i] !== 'number') {
stream.destroy(new Error('Invalid qos vector'))
return false
}
length += 1
}
} else {
stream.destroy(new Error('Invalid qos vector'))
return false
}
}
// properies mqtt 5
let 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) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const reasonCode = settings.reasonCode
const properties = settings.properties
let length = version === 5 ? 1 : 0
// properies mqtt 5
let 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) {
const version = opts ? opts.protocolVersion : 4
const settings = packet || {}
const reasonCode = settings.reasonCode
const properties = settings.properties
let length = version === 5 ? 1 : 0
if (version !== 5) stream.destroy(new Error('Invalid mqtt version for auth packet'))
// properies mqtt 5
const 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 - destination
* @param pos - offset
* @param length - length (>0)
* @returns number of bytes written
*
* @api private
*/
const varByteIntCache = {}
function writeVarByteInt (stream, num) {
if (num > protocol.VARBYTEINT_MAX) {
stream.destroy(new Error(`Invalid variable byte integer: ${num}`))
return false
}
let buffer = varByteIntCache[num]
if (!buffer) {
buffer = genBufVariableByteInt(num)
if (num < 16384) varByteIntCache[num] = buffer
}
debug('writeVarByteInt: writing to stream: %o', buffer)
return stream.write(buffer)
}
/**
* writeString - write a utf8 string to the buffer
*
* @param buffer - destination
* @param pos - offset
* @param string - string to write
* @return number of bytes written
*
* @api private
*/
function writeString (stream, string) {
const strlen = Buffer.byteLength(string)
writeNumber(stream, strlen)
debug('writeString: %s', string)
return stream.write(string, 'utf8')
}
/**
* writeStringPair - write a utf8 string pairs to the buffer
*
* @param buffer - destination
* @param name - string name to write
* @param value - string value to write
* @return 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 - destination
* @param pos - offset
* @param number - number to write
* @return number of bytes written
*
* @api private
*/
function writeNumberCached (stream, number) {
debug('writeNumberCached: number: %d', number)
debug('writeNumberCached: %o', numCache[number])
return stream.write(numCache[number])
}
function writeNumberGenerated (stream, number) {
const generatedNumber = generateNumber(number)
debug('writeNumberGenerated: %o', generatedNumber)
return stream.write(generatedNumber)
}
function write4ByteNumber (stream, number) {
const generated4ByteBuffer = generate4ByteBuffer(number)
debug('write4ByteNumber: %o', generated4ByteBuffer)
return stream.write(generated4ByteBuffer)
}
/**
* writeStringOrBuffer - write a String or Buffer with the its length prefix
*
* @param buffer - destination
* @param pos - offset
* @param toWrite - String or Buffer
* @return 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 () {
writeProperties(stream, {}, 0)
}
}
}
let propertiesLength = 0
function getLengthProperty (name, value) {
const type = protocol.propertiesTypes[name]
let length = 0
switch (type) {
case 'byte': {
if (typeof value !== 'boolean') {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + 1
break
}
case 'int8': {
if (typeof value !== 'number' || value < 0 || value > 0xff) {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + 1
break
}
case 'binary': {
if (value && value === null) {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + Buffer.byteLength(value) + 2
break
}
case 'int16': {
if (typeof value !== 'number' || value < 0 || value > 0xffff) {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + 2
break
}
case 'int32': {
if (typeof value !== 'number' || value < 0 || value > 0xffffffff) {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + 4
break
}
case 'var': {
// var byte integer is max 24 bits packed in 32 bits
if (typeof value !== 'number' || value < 0 || value > 0x0fffffff) {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + Buffer.byteLength(genBufVariableByteInt(value))
break
}
case 'string': {
if (typeof value !== 'string') {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += 1 + 2 + Buffer.byteLength(value.toString())
break
}
case 'pair': {
if (typeof value !== 'object') {
stream.destroy(new Error(`Invalid ${name}: ${value}`))
return false
}
length += Object.getOwnPropertyNames(value).reduce((result, name) => {
const currentValue = value[name]
if (Array.isArray(currentValue)) {
result += currentValue.reduce((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.destroy(new Error(`Invalid property ${name}: ${value}`))
return false
}
}
return length
}
if (properties) {
for (const propName in properties) {
let propLength = 0
let propValueLength = 0
const propValue = properties[propName]
if (propValue === undefined) {
continue
} else if (Array.isArray(propValue)) {
for (let valueIndex = 0; valueIndex < propValue.length; valueIndex++) {
propValueLength = getLengthProperty(propName, propValue[valueIndex])
if (!propValueLength) { return false }
propLength += propValueLength
}
} else {
propValueLength = getLengthProperty(propName, propValue)
if (!propValueLength) { return false }
propLength = propValueLength
}
if (!propLength) return false
propertiesLength += propLength
}
}
const propertiesLengthLength = Buffer.byteLength(genBufVariableByteInt(propertiesLength))
return {
length: propertiesLengthLength + propertiesLength,
write () {
writeProperties(stream, properties, propertiesLength)
}
}
}
function getPropertiesByMaximumPacketSize (stream, properties, opts, length) {
const mayEmptyProps = ['reasonString', 'userProperties']
const maximumPacketSize = opts && opts.properties && opts.properties.maximumPacketSize ? opts.properties.maximumPacketSize : 0
let propertiesData = getProperties(stream, properties)
if (maximumPacketSize) {
while (length + propertiesData.length > maximumPacketSize) {
const currentMayEmptyProp = mayEmptyProps.shift()
if (currentMayEmptyProp && properties[currentMayEmptyProp]) {
delete properties[currentMayEmptyProp]
propertiesData = getProperties(stream, properties)
} else {
return false
}
}
}
return propertiesData
}
function writeProperty (stream, propName, value) {
const 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(name => {
const currentValue = value[name]
if (Array.isArray(currentValue)) {
currentValue.forEach(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.destroy(new Error(`Invalid property ${propName} value: ${value}`))
return false
}
}
}
function writeProperties (stream, properties, propertiesLength) {
/* write properties to stream */
writeVarByteInt(stream, propertiesLength)
for (const propName in properties) {
if (Object.prototype.hasOwnProperty.call(properties, propName) && properties[propName] != null) {
const value = properties[propName]
if (Array.isArray(value)) {
for (let valueIndex = 0; valueIndex < value.length; valueIndex++) {
writeProperty(stream, propName, value[valueIndex])
}
} else {
writeProperty(stream, propName, value)
}
}
}
}
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