├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── connection.js ├── lib ├── generateStream.js ├── parseStream.js └── writeToStream.js ├── package.json └── test ├── connection-v5.js ├── connection.js ├── connection.parse.js ├── connection.transmit-v5.js ├── connection.transmit.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | - 8 5 | - 6 6 | script: 7 | - npm run test 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # mqtt-packet is an OPEN Open Source Project 2 | 3 | ----------------------------------------- 4 | 5 | ## What? 6 | 7 | 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. 8 | 9 | ## Rules 10 | 11 | There are a few basic ground-rules for contributors: 12 | 13 | 1. **No `--force` pushes** or modifying the Git history in any way. 14 | 1. **Non-master branches** ought to be used for ongoing work. 15 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 16 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 17 | 1. Contributors should attempt to adhere to the prevailing code-style. 18 | 19 | ## Releases 20 | 21 | Declaring formal releases remains the prerogative of the project maintainer. 22 | 23 | ## Changes to this arrangement 24 | 25 | 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. 26 | 27 | ----------------------------------------- 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2014-2015 mqtt-connection contributors 5 | --------------------------------------- 6 | 7 | *mqtt-connection contributors listed at * 8 | 9 | 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: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | 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. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mqtt-connection   [![Build Status](https://travis-ci.org/mqttjs/mqtt-connection.png)](https://travis-ci.org/mqttjs/mqtt-connection) 2 | =============== 3 | 4 | Barebone Connection object for MQTT. 5 | Works over any kind of binary Streams, TCP, TLS, WebSocket, ... 6 | 7 | [![JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 8 | 9 | It uses [mqtt-packet](http://npm.im/mqtt-packet) for generating and 10 | parsing MQTT packets. See it for the full documentations on the 11 | packet types. 12 | 13 | * Installation 14 | * Usage 15 | * API 16 | * Contributing 17 | * License & copyright 18 | 19 | This library is tested with node v4, v6 and v7. The last version to support 20 | older versions of node was mqtt-connection@2.1.1. 21 | 22 | Installation 23 | ------- 24 | 25 | ```sh 26 | npm install mqtt-connection --save 27 | ``` 28 | 29 | Usage 30 | ----- 31 | 32 | As a client: 33 | 34 | ```js 35 | var net = require('net') 36 | var mqttCon = require('mqtt-connection') 37 | var stream = net.createConnection(1883, 'localhost') 38 | var conn = mqttCon(stream) 39 | 40 | // conn is your MQTT connection! 41 | ``` 42 | 43 | As a server: 44 | ```js 45 | var net = require('net') 46 | var mqttCon = require('mqtt-connection') 47 | var server = new net.Server() 48 | 49 | server.on('connection', function (stream) { 50 | var client = mqttCon(stream) 51 | 52 | // client connected 53 | client.on('connect', function (packet) { 54 | // acknowledge the connect packet 55 | client.connack({ returnCode: 0 }); 56 | }) 57 | 58 | // client published 59 | client.on('publish', function (packet) { 60 | // send a puback with messageId (for QoS > 0) 61 | client.puback({ messageId: packet.messageId }) 62 | }) 63 | 64 | // client pinged 65 | client.on('pingreq', function () { 66 | // send a pingresp 67 | client.pingresp() 68 | }); 69 | 70 | // client subscribed 71 | client.on('subscribe', function (packet) { 72 | // send a suback with messageId and granted QoS level 73 | client.suback({ granted: [packet.qos], messageId: packet.messageId }) 74 | }) 75 | 76 | // timeout idle streams after 5 minutes 77 | stream.setTimeout(1000 * 60 * 5) 78 | 79 | // connection error handling 80 | client.on('close', function () { client.destroy() }) 81 | client.on('error', function () { client.destroy() }) 82 | client.on('disconnect', function () { client.destroy() }) 83 | 84 | // stream timeout 85 | stream.on('timeout', function () { client.destroy(); }) 86 | }) 87 | 88 | // listen on port 1883 89 | server.listen(1883) 90 | ``` 91 | 92 | As a websocket server: 93 | 94 | ```js 95 | var websocket = require('websocket-stream') 96 | var WebSocketServer = require('ws').Server 97 | var Connection = require('mqtt-connection') 98 | var server = http.createServer() 99 | 100 | var wss = new WebSocketServer({server: server}) 101 | 102 | if (handler) { 103 | server.on('client', handler) 104 | } 105 | 106 | wss.on('connection', function (ws) { 107 | var stream = websocket(ws) 108 | var connection = new Connection(stream) 109 | 110 | handle(connection) 111 | }) 112 | 113 | function handle (conn) { 114 | // handle the MQTT connection like 115 | // the net example 116 | } 117 | ``` 118 | 119 | API 120 | --- 121 | 122 | * mqtt.Connection() 123 | * mqtt.parseStream() 124 | * mqtt.generateStream() 125 | 126 | --------------------------------- 127 | 128 | 129 | ### new mqtt.Connection([options]) 130 | 131 | Creates a new MQTT `Connection`. 132 | 133 | Options: 134 | 135 | * `notData`: do not listen to the `'data'` event, so that it can 136 | respect backpressure. Pipe the `Connection` to another stream to 137 | consume the packets. If this option is passed `true` the object will 138 | emit no packet-related events. 139 | 140 | #### Connection#connect(options, [callback]) 141 | 142 | Send a MQTT connect packet. 143 | 144 | `options` supports the following properties: 145 | 146 | * `protocolId`: Protocol ID, usually `MQIsdp`. `string` 147 | * `protocolVersion`: Protocol version, usually 3. `number` 148 | * `keepalive`: keepalive period in seconds. `number` 149 | * `clientId`: client ID. `string` 150 | * `will`: the client's will message options. 151 | `object` that supports the following properties: 152 | * `topic`: the will topic. `string` 153 | * `payload`: the will payload. `string` 154 | * `qos`: will qos level. `number` 155 | * `retain`: will retain flag. `boolean` 156 | * `properties`: properties of will by MQTT 5.0: 157 | * `willDelayInterval`: representing the Will Delay Interval in seconds `number`, 158 | * `payloadFormatIndicator`: Will Message is UTF-8 Encoded Character Data or not `boolean`, 159 | * `messageExpiryInterval`: value is the lifetime of the Will Message in seconds and is sent as the Publication Expiry Interval when the Server publishes the Will Message `number`, 160 | * `contentType`: describing the content of the Will Message `string`, 161 | * `responseTopic`: String which is used as the Topic Name for a response message `string`, 162 | * `correlationData`: The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received `binary`, 163 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 164 | * `properties`: properties MQTT 5.0. 165 | `object` that supports the following properties: 166 | * `sessionExpiryInterval`: representing the Session Expiry Interval in seconds `number`, 167 | * `receiveMaximum`: representing the Receive Maximum value `number`, 168 | * `maximumPacketSize`: representing the Maximum Packet Size the Client is willing to accept `number`, 169 | * `topicAliasMaximum`: representing the Topic Alias Maximum value indicates the highest value that the Client will accept as a Topic Alias sent by the Server `number`, 170 | * `requestResponseInformation`: The Client uses this value to request the Server to return Response Information in the CONNACK `boolean`, 171 | * `requestProblemInformation`: The Client uses this value to indicate whether the Reason String or User Properties are sent in the case of failures `boolean`, 172 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`, 173 | * `authenticationMethod`: the name of the authentication method used for extended authentication `string`, 174 | * `authenticationData`: Binary Data containing authentication data `binary` 175 | * `clean`: the 'clean start' flag. `boolean` 176 | * `username`: username for protocol v3.1. `string` 177 | * `password`: password for protocol v3.1. `string` 178 | 179 | #### Connection#connack(options, [callback]) 180 | Send a MQTT connack packet. 181 | 182 | `options` supports the following properties: 183 | 184 | * `returnCode`: the return code of the connack, success is for MQTT < 5.0 185 | * `reasonCode`: suback Reason Code `number` MQTT 5.0 186 | * `properties`: properties MQTT 5.0. 187 | `object` that supports the following properties: 188 | * `sessionExpiryInterval`: representing the Session Expiry Interval in seconds `number`, 189 | * `receiveMaximum`: representing the Receive Maximum value `number`, 190 | * `maximumQoS`: maximum qos supported by server `number`, 191 | * `retainAvailable`: declares whether the Server supports retained messages `boolean`, 192 | * `maximumPacketSize`: Maximum Packet Size the Server is willing to accept `number`, 193 | * `assignedClientIdentifier`: Assigned Client Identifier `string`, 194 | * `topicAliasMaximum`: representing the Topic Alias Maximum value indicates the highest value that the Client will accept as a Topic Alias sent by the Server `number`, 195 | * `reasonString`: representing the reason associated with this response `string`, 196 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`, 197 | * `wildcardSubscriptionAvailable`: this byte declares whether the Server supports Wildcard Subscriptions `boolean` 198 | * `subscriptionIdentifiersAvailable`: declares whether the Server supports Subscription Identifiers `boolean`, 199 | * `sharedSubscriptionAvailable`: declares whether the Server supports Shared Subscriptions `boolean`, 200 | * `serverKeepAlive`: Keep Alive time assigned by the Server `number`, 201 | * `responseInformation`: String which is used as the basis for creating a Response Topic `string`, 202 | * `serverReference`: String which can be used by the Client to identify another Server to use `string`, 203 | * `authenticationMethod`: the name of the authentication method used for extended authentication `string`, 204 | * `authenticationData`: Binary Data containing authentication data `binary` 205 | 206 | #### Connection#publish(options, [callback]) 207 | Send a MQTT publish packet. 208 | 209 | `options` supports the following properties: 210 | 211 | * `topic`: the topic to publish to. `string` 212 | * `payload`: the payload to publish, defaults to an empty buffer. 213 | `string` or `buffer` 214 | * `qos`: the quality of service level to publish on. `number` 215 | * `messageId`: the message ID of the packet, 216 | required if qos > 0. `number` 217 | * `retain`: retain flag. `boolean` 218 | * `properties`: `object` 219 | * `payloadFormatIndicator`: Payload is UTF-8 Encoded Character Data or not `boolean`, 220 | * `messageExpiryInterval`: the lifetime of the Application Message in seconds `number`, 221 | * `topicAlias`: value that is used to identify the Topic instead of using the Topic Name `number`, 222 | * `responseTopic`: String which is used as the Topic Name for a response message `string`, 223 | * `correlationData`: used by the sender of the Request Message to identify which request the Response Message is for when it is received `binary`, 224 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`, 225 | * `subscriptionIdentifier`: representing the identifier of the subscription `number`, 226 | * `contentType`: String describing the content of the Application Message `string` 227 | 228 | #### Connection#puback #pubrec #pubcomp #unsuback(options, [callback]) 229 | Send a MQTT `[puback, pubrec, pubcomp, unsuback]` packet. 230 | 231 | `options` supports the following properties: 232 | 233 | * `messageId`: the ID of the packet 234 | * `reasonCode`: Reason Code by packet `number` 235 | * `properties`: `object` 236 | * `reasonString`: representing the reason associated with this response `string`, 237 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 238 | 239 | #### Connection#pubrel(options, [callback]) 240 | Send a MQTT pubrel packet. 241 | 242 | `options` supports the following properties: 243 | 244 | * `dup`: duplicate message flag 245 | * `reasonCode`: pubrel Reason Code `number` 246 | * `messageId`: the ID of the packet 247 | * `properties`: `object` 248 | * `reasonString`: representing the reason associated with this response `string`, 249 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 250 | 251 | #### Connection#subscribe(options, [callback]) 252 | Send a MQTT subscribe packet. 253 | 254 | `options` supports the following properties: 255 | 256 | * `dup`: duplicate message flag 257 | * `messageId`: the ID of the packet 258 | * `properties`: `object` 259 | * `subscriptionIdentifier`: representing the identifier of the subscription `number`, 260 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 261 | * `subscriptions`: a list of subscriptions of the form 262 | `[{topic: a, qos: 0}, {topic: b, qos: 1}]` 263 | `[{topic: a, qos: 0, nl: false, rap: true, rh: 15 }, {topic: b, qos: 1, nl: false, rap: false, rh: 100 }]` MQTT 5.0 Example 264 | 265 | #### Connection#suback(options, [callback]) 266 | Send a MQTT suback packet. 267 | 268 | `options` supports the following properties: 269 | 270 | * `granted`: a vector of granted QoS levels, 271 | of the form `[0, 1, 2]` 272 | * `messageId`: the ID of the packet 273 | * `reasonCode`: suback Reason Code `number` 274 | * `properties`: `object` 275 | * `reasonString`: representing the reason associated with this response `string`, 276 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 277 | 278 | #### Connection#unsubscribe(options, [callback]) 279 | Send a MQTT unsubscribe packet. 280 | 281 | `options` supports the following properties: 282 | 283 | * `messageId`: the ID of the packet 284 | * `reasonCode`: unsubscribe Reason Code MQTT 5.0 `number` 285 | * `dup`: duplicate message flag 286 | * `properties`: `object` 287 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 288 | * `unsubscriptions`: a list of topics to unsubscribe from, 289 | of the form `["topic1", "topic2"]` 290 | 291 | #### Connection#pingreq #pingresp #disconnect(options, [callback]) 292 | Send a MQTT `[pingreq, pingresp]` packet. 293 | 294 | #### Connection#disconnect(options, [callback]) 295 | Send a MQTT `disconnect` packet. 296 | 297 | `options` supports the following properties only MQTT 5.0: 298 | 299 | * `reasonCode`: Disconnect Reason Code `number` 300 | * `properties`: `object` 301 | * `sessionExpiryInterval`: representing the Session Expiry Interval in seconds `number`, 302 | * `reasonString`: representing the reason for the disconnect `string`, 303 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`, 304 | * `serverReference`: String which can be used by the Client to identify another Server to use `string` 305 | 306 | #### Connection#auth(options, [callback]) 307 | Send a MQTT `auth` packet. Only MQTT 5.0 308 | 309 | `options` supports the following properties only MQTT 5.0: 310 | 311 | * `reasonCode`: Auth Reason Code `number` 312 | * `properties`: `object` 313 | * `authenticationMethod`: the name of the authentication method used for extended authentication `string`, 314 | * `authenticationData`: Binary Data containing authentication data `binary`, 315 | * `reasonString`: representing the reason for the disconnect `string`, 316 | * `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object` 317 | 318 | #### Event: 'connect' 319 | `function(packet) {}` 320 | 321 | Emitted when a MQTT connect packet is received by the client. 322 | 323 | `packet` is an object that may have the following properties: 324 | 325 | * `version`: the protocol version string 326 | * `versionNum`: the protocol version number 327 | * `keepalive`: the client's keepalive period 328 | * `clientId`: the client's ID 329 | * `will`: an object with the following keys: 330 | * `topic`: the client's will topic 331 | * `payload`: the will message 332 | * `retain`: will retain flag 333 | * `qos`: will qos level 334 | * `properties`: properties of will 335 | * `properties`: properties of packet 336 | * `clean`: clean start flag 337 | * `username`: v3.1 username 338 | * `password`: v3.1 password 339 | 340 | #### Event: 'connack' 341 | `function(packet) {}` 342 | 343 | Emitted when a MQTT connack packet is received by the client. 344 | 345 | `packet` is an object that may have the following properties: 346 | 347 | * `returnCode`: the return code of the connack packet 348 | * `properties`: properties of packet 349 | 350 | #### Event: 'publish' 351 | `function(packet) {}` 352 | 353 | Emitted when a MQTT publish packet is received by the client. 354 | 355 | `packet` is an object that may have the following properties: 356 | 357 | * `topic`: the topic the message is published on 358 | * `payload`: the payload of the message 359 | * `messageId`: the ID of the packet 360 | * `properties`: properties of packet 361 | * `qos`: the QoS level to publish at 362 | 363 | #### Events: \<'puback', 'pubrec', 'pubrel', 'pubcomp', 'unsuback'\> 364 | `function(packet) {}` 365 | 366 | Emitted when a MQTT `[puback, pubrec, pubrel, pubcomp, unsuback]` 367 | packet is received by the client. 368 | 369 | `packet` is an object that may contain the property: 370 | 371 | * `messageId`: the ID of the packet 372 | * `properties`: properties of packet 373 | 374 | #### Event: 'subscribe' 375 | `function(packet) {}` 376 | 377 | Emitted when a MQTT subscribe packet is received. 378 | 379 | `packet` is an object that may contain the properties: 380 | 381 | * `messageId`: the ID of the packet 382 | * `properties`: properties of packet 383 | * `subscriptions`: an array of objects 384 | representing the subscribed topics, containing the following keys 385 | * `topic`: the topic subscribed to 386 | * `qos`: the qos level of the subscription 387 | 388 | 389 | #### Event: 'suback' 390 | `function(packet) {}` 391 | 392 | Emitted when a MQTT suback packet is received. 393 | 394 | `packet` is an object that may contain the properties: 395 | 396 | * `messageId`: the ID of the packet 397 | * `properties`: properties of packet 398 | * `granted`: a vector of granted QoS levels 399 | 400 | #### Event: 'unsubscribe' 401 | `function(packet) {}` 402 | 403 | Emitted when a MQTT unsubscribe packet is received. 404 | 405 | `packet` is an object that may contain the properties: 406 | 407 | * `messageId`: the ID of the packet 408 | * `properties`: properties of packet 409 | * `unsubscriptions`: a list of topics the client is 410 | unsubscribing from, of the form `[topic1, topic2, ...]` 411 | 412 | #### Events: \<'pingreq', 'pingresp'\> 413 | `function(packet){}` 414 | 415 | Emitted when a MQTT `[pingreq, pingresp, disconnect]` packet is received. 416 | 417 | `packet` only includes static header information and can be ignored. 418 | 419 | #### Event: 'disconnect' 420 | `function(packet) {}` 421 | 422 | Emitted when a MQTT disconnect packet is received. 423 | 424 | `packet` only includes static header information and can be ignored for MQTT < 5.0. 425 | 426 | `packet` is an object that may contain the properties for MQTT 5.0: 427 | 428 | * `reasonCode`: disconnect Reason Code 429 | * `properties`: properties of packet 430 | 431 | #### Event: 'auth' 432 | `function(packet) {}` 433 | 434 | Emitted when a MQTT auth packet is received. 435 | 436 | `packet` is an object that may contain the properties: 437 | 438 | * `reasonCode`: Auth Reason Code 439 | * `properties`: properties of packet 440 | ------------------------------------- 441 | 442 | 443 | 444 | ### mqtt.generateStream() 445 | 446 | Returns a `Transform` stream that calls [`generate()`](https://github.com/mqttjs/mqtt-packet#generate). 447 | The stream is configured into object mode. 448 | 449 | 450 | 451 | ### mqtt.parseStream(opts) 452 | 453 | Returns a `Transform` stream that embeds a [`Parser`](https://github.com/mqttjs/mqtt-packet#mqttparser) and calls [`Parser.parse()`](https://github.com/mqttjs/mqtt-packet#parserparsebuffer) for each new `Buffer`. The stream is configured into object mode. It accepts the same options of [`parser(opts)`](#parser). 454 | 455 | 456 | Contributing 457 | ------------ 458 | 459 | mqtt-connection is an **OPEN Open Source Project**. This means that: 460 | 461 | > 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. 462 | 463 | See the [CONTRIBUTING.md](https://github.com/mqttjs/mqtt-connection/blob/master/CONTRIBUTING.md) file for more details. 464 | 465 | ### Contributors 466 | 467 | mqtt-connection is only possible due to the excellent work of the following contributors: 468 | 469 | 470 | 471 | 472 | 473 |
Matteo CollinaGitHub/mcollinaTwitter/@matteocollina
Adam RuddGitHub/adamvrTwitter/@adam_vr
Siarhei BuntsevichGitHub/scarry1992
474 | 475 | License 476 | ------- 477 | 478 | MIT 479 | -------------------------------------------------------------------------------- /connection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var generateStream = require('./lib/generateStream') 4 | var parseStream = require('./lib/parseStream') 5 | var writeToStream = require('./lib/writeToStream') 6 | var Duplexify = require('duplexify') 7 | var inherits = require('inherits') 8 | 9 | function emitPacket (packet) { 10 | this.emit(packet.cmd, packet) 11 | } 12 | 13 | function Connection (duplex, opts, cb) { 14 | if (!(this instanceof Connection)) { 15 | return new Connection(duplex, opts) 16 | } 17 | 18 | if (typeof opts === 'function') { 19 | cb = opts 20 | opts = {} 21 | } 22 | 23 | opts = opts || {} 24 | 25 | this._generator = writeToStream(duplex, opts) 26 | this._parser = parseStream(opts) 27 | 28 | // defer piping, so consumer can attach event listeners 29 | // otherwise we might lose events 30 | process.nextTick(() => { 31 | duplex.pipe(this._parser) 32 | }) 33 | 34 | this._generator.on('error', this.emit.bind(this, 'error')) 35 | this._parser.on('error', this.emit.bind(this, 'error')) 36 | 37 | this.stream = duplex 38 | 39 | duplex.on('error', this.emit.bind(this, 'error')) 40 | duplex.on('close', this.emit.bind(this, 'close')) 41 | 42 | Duplexify.call(this, this._generator, this._parser, { objectMode: true }) 43 | 44 | // MQTT.js basic default 45 | if (opts.notData !== true) { 46 | var that = this 47 | this.once('data', function (connectPacket) { 48 | that.setOptions(connectPacket, opts) 49 | that.on('data', emitPacket) 50 | if (cb) { 51 | cb() 52 | } 53 | that.emit('data', connectPacket) 54 | }) 55 | } 56 | } 57 | 58 | inherits(Connection, Duplexify) 59 | 60 | ;['connect', 61 | 'connack', 62 | 'publish', 63 | 'puback', 64 | 'pubrec', 65 | 'pubrel', 66 | 'pubcomp', 67 | 'subscribe', 68 | 'suback', 69 | 'unsubscribe', 70 | 'unsuback', 71 | 'pingreq', 72 | 'pingresp', 73 | 'disconnect', 74 | 'auth' 75 | ].forEach(function (cmd) { 76 | Connection.prototype[cmd] = function (opts, cb) { 77 | opts = opts || {} 78 | opts.cmd = cmd 79 | 80 | // Flush the buffer if needed 81 | // UGLY hack, we should listen for the 'drain' event 82 | // and start writing again, but this works too 83 | this.write(opts) 84 | if (cb) setImmediate(cb) 85 | } 86 | }) 87 | 88 | Connection.prototype.destroy = function () { 89 | if (this.stream.destroy) this.stream.destroy() 90 | else this.stream.end() 91 | } 92 | 93 | Connection.prototype.setOptions = function (packet, opts) { 94 | let options = {} 95 | Object.assign(options, packet) 96 | // Specifically set the protocol version for client connections 97 | if (options.cmd === 'connack') { 98 | options.protocolVersion = opts && opts.protocolVersion ? opts.protocolVersion : 4 99 | } 100 | this.options = options 101 | this._parser.setOptions(options) 102 | this._generator.setOptions(options) 103 | } 104 | 105 | module.exports = Connection 106 | module.exports.parseStream = parseStream 107 | module.exports.generateStream = generateStream 108 | -------------------------------------------------------------------------------- /lib/generateStream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Buffer = require('safe-buffer').Buffer 4 | var through = require('through2') 5 | var generate = require('mqtt-packet').generate 6 | var empty = Buffer.allocUnsafe(0) 7 | 8 | function generateStream (opts) { 9 | var stream = through.obj(process) 10 | 11 | function process (chunk, enc, cb) { 12 | var packet = empty 13 | 14 | try { 15 | packet = generate(chunk, opts) 16 | } catch (err) { 17 | this.emit('error', err) 18 | return 19 | } 20 | 21 | this.push(packet) 22 | cb() 23 | } 24 | 25 | return stream 26 | } 27 | 28 | module.exports = generateStream 29 | -------------------------------------------------------------------------------- /lib/parseStream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var through = require('through2') 4 | var build = require('mqtt-packet').parser 5 | 6 | function StreamParser (opts) { 7 | if (!(this instanceof StreamParser)) return new StreamParser(opts) 8 | var that = this 9 | var stream = through.obj(process) 10 | this.stream = stream 11 | createParser(opts) 12 | 13 | function process (chunk, enc, cb) { 14 | that.parser.parse(chunk) 15 | cb() 16 | } 17 | 18 | function push (packet) { 19 | stream.push(packet) 20 | } 21 | 22 | function createParser (opts) { 23 | that.parser = build(opts) 24 | that.parser.on('packet', push) 25 | that.parser.on('error', that.stream.emit.bind(that.stream, 'error')) 26 | } 27 | 28 | stream.setOptions = createParser 29 | 30 | return stream 31 | } 32 | 33 | module.exports = StreamParser 34 | -------------------------------------------------------------------------------- /lib/writeToStream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var stream = require('stream') 4 | var writeToStream = require('mqtt-packet').writeToStream 5 | 6 | function StreamGenerator (output, opts) { 7 | if (!(this instanceof StreamGenerator)) return new StreamGenerator(output, opts) 8 | var that = this 9 | this.opts = opts || {} 10 | var input = new stream.Writable({ objectMode: true, write: write }) 11 | 12 | function write (chunk, enc, cb) { 13 | if (writeToStream(chunk, output, that.opts)) { 14 | cb() 15 | } else { 16 | output.once('drain', cb) 17 | } 18 | } 19 | 20 | input.setOptions = function (opts) { 21 | that.opts = opts 22 | } 23 | 24 | return input 25 | } 26 | 27 | module.exports = StreamGenerator 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-connection", 3 | "version": "4.1.0", 4 | "description": "Stream-based Connection object for MQTT, extracted from MQTT.js", 5 | "main": "connection.js", 6 | "contributors": [ 7 | "Matteo Collina (https://github.com/mcollina)", 8 | "Adam Rudd ", 9 | "Siarhei Buntsevich (https://github.com/scarry1992)" 10 | ], 11 | "scripts": { 12 | "test": "mocha test/ && standard" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/mqttjs/mqtt-connection.git" 17 | }, 18 | "keywords": [ 19 | "mqtt", 20 | "connection", 21 | "server" 22 | ], 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/mqttjs/mqtt-connection/issues" 26 | }, 27 | "homepage": "https://github.com/mqttjs/mqtt-connection", 28 | "dependencies": { 29 | "duplexify": "^3.5.1", 30 | "inherits": "^2.0.3", 31 | "mqtt-packet": "^6.0.0", 32 | "safe-buffer": "^5.1.1", 33 | "through2": "^2.0.1" 34 | }, 35 | "devDependencies": { 36 | "mocha": "^4.0.0", 37 | "should": "^13.0.0", 38 | "standard": "^11.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/connection-v5.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | 3 | /** 4 | * Testing requires 5 | */ 6 | 7 | var stream = require('./util').testStream 8 | var should = require('should') 9 | 10 | /** 11 | * Units under test 12 | */ 13 | var Connection = require('../connection') 14 | 15 | describe('Connection-v5', function () { 16 | beforeEach(function () { 17 | this.stream = stream() 18 | this.conn = new Connection(this.stream, { protocolVersion: 5 }) 19 | this.readFromStream = (stream, length, cb) => { 20 | var buf, done 21 | stream.on('data', data => { 22 | if (done) return 23 | buf = buf ? Buffer.concat([ buf, data ]) : data 24 | if (buf.length >= length) { 25 | cb(buf.slice(0, length)) 26 | done = true 27 | } 28 | }) 29 | } 30 | }) 31 | 32 | it('should start piping in the next tick', function (done) { 33 | should(this.stream._readableState.flowing).eql(null) 34 | process.nextTick(() => { 35 | this.stream._readableState.flowing.should.eql(true) 36 | done() 37 | }) 38 | }) 39 | 40 | describe('transmission-v5', require('./connection.transmit-v5.js')) 41 | }) 42 | -------------------------------------------------------------------------------- /test/connection.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | 3 | /** 4 | * Testing requires 5 | */ 6 | 7 | var stream = require('./util').testStream 8 | var should = require('should') 9 | 10 | /** 11 | * Units under test 12 | */ 13 | var Connection = require('../connection') 14 | 15 | describe('Connection', function () { 16 | beforeEach(function () { 17 | this.stream = stream() 18 | this.conn = new Connection(this.stream) 19 | this.readFromStream = (stream, length, cb) => { 20 | var buf, done 21 | stream.on('data', data => { 22 | if (done) return 23 | buf = buf ? Buffer.concat([ buf, data ]) : data 24 | if (buf.length >= length) { 25 | cb(buf.slice(0, length)) 26 | done = true 27 | } 28 | }) 29 | } 30 | }) 31 | 32 | it('should start piping in the next tick', function (done) { 33 | should(this.stream._readableState.flowing).eql(null) 34 | process.nextTick(() => { 35 | this.stream._readableState.flowing.should.eql(true) 36 | done() 37 | }) 38 | }) 39 | 40 | describe('parsing', require('./connection.parse.js')) 41 | describe('transmission', require('./connection.transmit.js')) 42 | }) 43 | -------------------------------------------------------------------------------- /test/connection.parse.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | /** 4 | * Testing requires 5 | */ 6 | var Buffer = require('safe-buffer').Buffer 7 | var should = require('should') 8 | var stream = require('./util').testStream 9 | 10 | // This is so we can use eql to compare Packet objects with plain objects: 11 | should.config.checkProtoEql = false 12 | 13 | /** 14 | * Units under test 15 | */ 16 | 17 | var Connection = require('../connection') 18 | 19 | module.exports = function () { 20 | describe('connect', function () { 21 | it('should fire a connect event (minimal)', function (done) { 22 | var expected = { 23 | cmd: 'connect', 24 | retain: false, 25 | qos: 0, 26 | dup: false, 27 | length: 18, 28 | protocolId: 'MQIsdp', 29 | protocolVersion: 3, 30 | clean: false, 31 | keepalive: 30, 32 | clientId: 'test', 33 | topic: null, 34 | payload: null 35 | } 36 | 37 | var fixture = [ 38 | 16, 18, // Header 39 | 0, 6, // Protocol id length 40 | 77, 81, 73, 115, 100, 112, // Protocol id 41 | 3, // Protocol version 42 | 0, // Connect flags 43 | 0, 30, // Keepalive 44 | 0, 4, // Client id length 45 | 116, 101, 115, 116 // Client id 46 | ] 47 | 48 | this.stream.write(Buffer.from(fixture)) 49 | 50 | this.conn.once('connect', function (packet) { 51 | packet.should.eql(expected) 52 | done() 53 | }) 54 | }) 55 | 56 | it('should fire a connect event (maximal)', function (done) { 57 | var expected = { 58 | cmd: 'connect', 59 | retain: false, 60 | qos: 0, 61 | dup: false, 62 | length: 54, 63 | protocolId: 'MQIsdp', 64 | protocolVersion: 3, 65 | will: { 66 | retain: true, 67 | qos: 2, 68 | topic: 'topic', 69 | payload: Buffer.from('payload') 70 | }, 71 | clean: true, 72 | keepalive: 30, 73 | clientId: 'test', 74 | username: 'username', 75 | password: Buffer.from('password'), 76 | topic: null, 77 | payload: null 78 | } 79 | var fixture = [ 80 | 16, 54, // Header 81 | 0, 6, // Protocol id length 82 | 77, 81, 73, 115, 100, 112, // Protocol id 83 | 3, // Protocol version 84 | 246, // Connect flags 85 | 0, 30, // Keepalive 86 | 0, 4, // Client id length 87 | 116, 101, 115, 116, // Client id 88 | 0, 5, // Will topic length 89 | 116, 111, 112, 105, 99, // Will topic 90 | 0, 7, // Will payload length 91 | 112, 97, 121, 108, 111, 97, 100, // Will payload 92 | 0, 8, // Username length 93 | 117, 115, 101, 114, 110, 97, 109, 101, // Username 94 | 0, 8, // Password length 95 | 112, 97, 115, 115, 119, 111, 114, 100 // Password 96 | ] 97 | 98 | this.stream.write(Buffer.from(fixture)) 99 | 100 | this.conn.once('connect', function (packet) { 101 | packet.should.eql(expected) 102 | done() 103 | }) 104 | }) 105 | 106 | describe('parse errors', function () { 107 | it('should say protocol not parseable', function (done) { 108 | var fixture = [ 109 | 16, 4, 110 | 0, 6, 111 | 77, 81 112 | ] 113 | 114 | this.stream.write(Buffer.from(fixture)) 115 | this.conn.once('error', function (err) { 116 | err.message.should.match(/cannot parse protocolId/i) 117 | done() 118 | }) 119 | }) 120 | }) 121 | }) 122 | 123 | describe('connack', function () { 124 | it('should fire a connack event (rc = 0)', function (done) { 125 | var expected = { 126 | cmd: 'connack', 127 | retain: false, 128 | qos: 0, 129 | dup: false, 130 | length: 2, 131 | sessionPresent: false, 132 | returnCode: 0, 133 | topic: null, 134 | payload: null 135 | } 136 | 137 | var fixture = [32, 2, 0, 0] 138 | 139 | this.stream.write(Buffer.from(fixture)) 140 | 141 | this.conn.once('connack', function (packet) { 142 | packet.should.eql(expected) 143 | done() 144 | }) 145 | }) 146 | 147 | it('should fire a connack event (rc = 5)', function (done) { 148 | var expected = { 149 | cmd: 'connack', 150 | retain: false, 151 | qos: 0, 152 | dup: false, 153 | length: 2, 154 | sessionPresent: false, 155 | returnCode: 5, 156 | topic: null, 157 | payload: null 158 | } 159 | 160 | var fixture = [32, 2, 0, 5] 161 | 162 | this.stream.write(Buffer.from(fixture)) 163 | 164 | this.conn.once('connack', function (packet) { 165 | packet.should.eql(expected) 166 | done() 167 | }) 168 | }) 169 | }) 170 | 171 | describe('publish', function () { 172 | it('should fire a publish event (minimal)', function (done) { 173 | var expected = { 174 | cmd: 'publish', 175 | retain: false, 176 | qos: 0, 177 | dup: false, 178 | length: 10, 179 | topic: 'test', 180 | payload: Buffer.from('test') 181 | } 182 | 183 | var fixture = [ 184 | 48, 10, // Header 185 | 0, 4, // Topic length 186 | 116, 101, 115, 116, // Topic (test) 187 | 116, 101, 115, 116 // Payload (test) 188 | ] 189 | 190 | this.stream.write(Buffer.from(fixture)) 191 | 192 | this.conn.once('publish', function (packet) { 193 | packet.should.eql(expected) 194 | done() 195 | }) 196 | }) 197 | 198 | it('should fire a publish event with 2KB payload', function (done) { 199 | var expected = { 200 | cmd: 'publish', 201 | retain: false, 202 | qos: 0, 203 | dup: false, 204 | length: 2054, 205 | topic: 'test', 206 | payload: Buffer.allocUnsafe(2048) 207 | } 208 | 209 | var fixture = Buffer.from([ 210 | 48, 134, 16, // Header 211 | 0, 4, // Topic length 212 | 116, 101, 115, 116 // Topic (test) 213 | ]) 214 | 215 | fixture = Buffer.concat([fixture, expected.payload]) 216 | 217 | var s = stream() 218 | var c = new Connection(s) 219 | 220 | s.write(fixture) 221 | 222 | c.once('publish', function (packet) { 223 | packet.should.eql(expected) 224 | done() 225 | }) 226 | }) 227 | 228 | it('should fire a publish event with 2MB payload', function (done) { 229 | var expected = { 230 | cmd: 'publish', 231 | retain: false, 232 | qos: 0, 233 | dup: false, 234 | length: 6 + 2 * 1024 * 1024, 235 | topic: 'test', 236 | payload: Buffer.allocUnsafe(2 * 1024 * 1024) 237 | } 238 | 239 | var fixture = Buffer.from([ 240 | 48, 134, 128, 128, 1, // Header 241 | 0, 4, // Topic length 242 | 116, 101, 115, 116 // Topic (test) 243 | ]) 244 | 245 | fixture = Buffer.concat([fixture, expected.payload]) 246 | 247 | var s = stream() 248 | var c = new Connection(s) 249 | 250 | s.write(fixture) 251 | 252 | c.once('publish', function (packet) { 253 | // Comparing the whole 2MB buffer is very slow so only check the length 254 | packet.length.should.eql(expected.length) 255 | done() 256 | }) 257 | }) 258 | 259 | it('should fire a publish event (maximal)', function (done) { 260 | var expected = { 261 | cmd: 'publish', 262 | retain: true, 263 | qos: 2, 264 | length: 12, 265 | dup: true, 266 | topic: 'test', 267 | messageId: 10, 268 | payload: Buffer.from('test') 269 | } 270 | 271 | var fixture = [ 272 | 61, 12, // Header 273 | 0, 4, // Topic length 274 | 116, 101, 115, 116, // Topic 275 | 0, 10, // Message id 276 | 116, 101, 115, 116 // Payload 277 | ] 278 | 279 | this.stream.write(Buffer.from(fixture)) 280 | 281 | this.conn.once('publish', function (packet) { 282 | packet.should.eql(expected) 283 | done() 284 | }) 285 | }) 286 | 287 | it('should fire an empty publish', function (done) { 288 | var expected = { 289 | cmd: 'publish', 290 | retain: false, 291 | qos: 0, 292 | dup: false, 293 | length: 6, 294 | topic: 'test', 295 | payload: Buffer.allocUnsafe(0) 296 | } 297 | 298 | var fixture = [ 299 | 48, 6, // Header 300 | 0, 4, // Topic length 301 | 116, 101, 115, 116 // Topic 302 | // Empty payload 303 | ] 304 | 305 | this.stream.write(Buffer.from(fixture)) 306 | 307 | this.conn.once('publish', function (packet) { 308 | packet.should.eql(expected) 309 | done() 310 | }) 311 | }) 312 | 313 | it('should parse a splitted publish', function (done) { 314 | var expected = { 315 | cmd: 'publish', 316 | retain: false, 317 | qos: 0, 318 | dup: false, 319 | length: 10, 320 | topic: 'test', 321 | payload: Buffer.from('test') 322 | } 323 | 324 | var fixture1 = [ 325 | 48, 10, // Header 326 | 0, 4, // Topic length 327 | 116, 101, 115, 116 // Topic (test) 328 | ] 329 | 330 | var fixture2 = [ 331 | 116, 101, 115, 116 // Payload (test) 332 | ] 333 | 334 | this.stream.write(Buffer.from(fixture1)) 335 | this.stream.write(Buffer.from(fixture2)) 336 | 337 | this.conn.once('publish', function (packet) { 338 | packet.should.eql(expected) 339 | done() 340 | }) 341 | }) 342 | }) 343 | 344 | describe('puback', function () { 345 | it('should fire a puback event', function (done) { 346 | var expected = { 347 | cmd: 'puback', 348 | retain: false, 349 | qos: 0, 350 | dup: false, 351 | length: 2, 352 | messageId: 2, 353 | topic: null, 354 | payload: null 355 | } 356 | 357 | var fixture = [ 358 | 64, 2, // Header 359 | 0, 2 // Message id 360 | ] 361 | 362 | this.stream.write(Buffer.from(fixture)) 363 | 364 | this.conn.once('puback', function (packet) { 365 | packet.should.eql(expected) 366 | done() 367 | }) 368 | }) 369 | }) 370 | 371 | describe('pubrec', function () { 372 | it('should fire a pubrec event', function (done) { 373 | var expected = { 374 | cmd: 'pubrec', 375 | retain: false, 376 | qos: 0, 377 | dup: false, 378 | length: 2, 379 | messageId: 3, 380 | topic: null, 381 | payload: null 382 | } 383 | 384 | var fixture = [ 385 | 80, 2, // Header 386 | 0, 3 // Message id 387 | ] 388 | 389 | this.stream.write(Buffer.from(fixture)) 390 | 391 | this.conn.once('pubrec', function (packet) { 392 | packet.should.eql(expected) 393 | done() 394 | }) 395 | }) 396 | }) 397 | 398 | describe('pubrel', function () { 399 | it('should fire a pubrel event', function (done) { 400 | var expected = { 401 | cmd: 'pubrel', 402 | retain: false, 403 | qos: 0, 404 | dup: false, 405 | length: 2, 406 | messageId: 4, 407 | topic: null, 408 | payload: null 409 | } 410 | 411 | var fixture = [ 412 | 96, 2, // Header 413 | 0, 4 // Message id 414 | ] 415 | 416 | this.stream.write(Buffer.from(fixture)) 417 | 418 | this.conn.once('pubrel', function (packet) { 419 | packet.should.eql(expected) 420 | done() 421 | }) 422 | }) 423 | }) 424 | 425 | describe('pubcomp', function () { 426 | it('should fire a pubcomp event', function (done) { 427 | var expected = { 428 | cmd: 'pubcomp', 429 | retain: false, 430 | qos: 0, 431 | dup: false, 432 | length: 2, 433 | messageId: 5, 434 | topic: null, 435 | payload: null 436 | } 437 | 438 | var fixture = [ 439 | 112, 2, // Header 440 | 0, 5 // Message id 441 | ] 442 | 443 | this.stream.write(Buffer.from(fixture)) 444 | 445 | this.conn.once('pubcomp', function (packet) { 446 | packet.should.eql(expected) 447 | done() 448 | }) 449 | }) 450 | }) 451 | 452 | describe('subscribe', function () { 453 | it('should fire a subscribe event (1 topic)', function (done) { 454 | var expected = { 455 | cmd: 'subscribe', 456 | retain: false, 457 | qos: 1, 458 | dup: false, 459 | length: 9, 460 | subscriptions: [ 461 | { 462 | topic: 'test', 463 | qos: 0 464 | } 465 | ], 466 | messageId: 6, 467 | topic: null, 468 | payload: null 469 | } 470 | 471 | var fixture = [ 472 | 130, 9, // Header (publish, qos=1, length=9) 473 | 0, 6, // Message id (6) 474 | 0, 4, // Topic length, 475 | 116, 101, 115, 116, // Topic (test) 476 | 0 // Qos (0) 477 | ] 478 | this.stream.write(Buffer.from(fixture)) 479 | 480 | this.conn.once('subscribe', function (packet) { 481 | packet.should.eql(expected) 482 | done() 483 | }) 484 | }) 485 | 486 | it('should fire a subscribe event (3 topic)', function (done) { 487 | var expected = { 488 | cmd: 'subscribe', 489 | retain: false, 490 | qos: 1, 491 | dup: false, 492 | length: 23, 493 | subscriptions: [ 494 | { 495 | topic: 'test', 496 | qos: 0 497 | }, { 498 | topic: 'uest', 499 | qos: 1 500 | }, { 501 | topic: 'tfst', 502 | qos: 2 503 | } 504 | ], 505 | messageId: 6, 506 | topic: null, 507 | payload: null 508 | } 509 | 510 | var fixture = [ 511 | 130, 23, // Header (publish, qos=1, length=9) 512 | 0, 6, // Message id (6) 513 | 0, 4, // Topic length, 514 | 116, 101, 115, 116, // Topic (test) 515 | 0, // Qos (0) 516 | 0, 4, // Topic length 517 | 117, 101, 115, 116, // Topic (uest) 518 | 1, // Qos (1) 519 | 0, 4, // Topic length 520 | 116, 102, 115, 116, // Topic (tfst) 521 | 2 // Qos (2) 522 | ] 523 | 524 | this.stream.write(Buffer.from(fixture)) 525 | 526 | this.conn.once('subscribe', function (packet) { 527 | packet.should.eql(expected) 528 | done() 529 | }) 530 | }) 531 | }) 532 | 533 | describe('suback', function () { 534 | it('should fire a suback event', function (done) { 535 | var expected = { 536 | cmd: 'suback', 537 | retain: false, 538 | qos: 0, 539 | dup: false, 540 | length: 6, 541 | granted: [0, 1, 2, 128], 542 | messageId: 6, 543 | topic: null, 544 | payload: null 545 | } 546 | 547 | var fixture = [ 548 | 144, 6, // Header 549 | 0, 6, // Message id 550 | 0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80 551 | ] 552 | 553 | this.stream.write(Buffer.from(fixture)) 554 | 555 | this.conn.once('suback', function (packet) { 556 | packet.should.eql(expected) 557 | done() 558 | }) 559 | }) 560 | }) 561 | 562 | describe('unsubscribe', function () { 563 | it('should fire an unsubscribe event', function (done) { 564 | var expected = { 565 | cmd: 'unsubscribe', 566 | retain: false, 567 | qos: 1, 568 | dup: false, 569 | length: 14, 570 | unsubscriptions: [ 571 | 'tfst', 572 | 'test' 573 | ], 574 | messageId: 7, 575 | topic: null, 576 | payload: null 577 | } 578 | 579 | var fixture = [ 580 | 162, 14, 581 | 0, 7, // Message id (7) 582 | 0, 4, // Topic length 583 | 116, 102, 115, 116, // Topic (tfst) 584 | 0, 4, // Topic length, 585 | 116, 101, 115, 116 // Topic (test) 586 | ] 587 | 588 | this.stream.write(Buffer.from(fixture)) 589 | 590 | this.conn.once('unsubscribe', function (packet) { 591 | packet.should.eql(expected) 592 | done() 593 | }) 594 | }) 595 | }) 596 | 597 | describe('unsuback', function () { 598 | it('should fire a unsuback event', function (done) { 599 | var expected = { 600 | cmd: 'unsuback', 601 | retain: false, 602 | qos: 0, 603 | dup: false, 604 | length: 2, 605 | messageId: 8, 606 | topic: null, 607 | payload: null 608 | } 609 | 610 | var fixture = [ 611 | 176, 2, // Header 612 | 0, 8 // Message id 613 | ] 614 | 615 | this.stream.write(Buffer.from(fixture)) 616 | 617 | this.conn.once('unsuback', function (packet) { 618 | packet.should.eql(expected) 619 | done() 620 | }) 621 | }) 622 | }) 623 | 624 | describe('pingreq', function () { 625 | it('should fire a pingreq event', function (done) { 626 | var expected = { 627 | cmd: 'pingreq', 628 | retain: false, 629 | qos: 0, 630 | dup: false, 631 | length: 0, 632 | topic: null, 633 | payload: null 634 | } 635 | 636 | var fixture = [ 637 | 192, 0 // Header 638 | ] 639 | 640 | this.stream.write(Buffer.from(fixture)) 641 | 642 | this.conn.once('pingreq', function (packet) { 643 | packet.should.eql(expected) 644 | done() 645 | }) 646 | }) 647 | }) 648 | 649 | describe('pingresp', function () { 650 | it('should fire a pingresp event', function (done) { 651 | var expected = { 652 | cmd: 'pingresp', 653 | retain: false, 654 | qos: 0, 655 | dup: false, 656 | length: 0, 657 | topic: null, 658 | payload: null 659 | } 660 | 661 | var fixture = [ 662 | 208, 0 // Header 663 | ] 664 | 665 | this.stream.write(Buffer.from(fixture)) 666 | 667 | this.conn.once('pingresp', function (packet) { 668 | packet.should.eql(expected) 669 | done() 670 | }) 671 | }) 672 | }) 673 | 674 | describe('disconnect', function () { 675 | it('should fire a disconnect event', function (done) { 676 | var expected = { 677 | cmd: 'disconnect', 678 | retain: false, 679 | qos: 0, 680 | dup: false, 681 | length: 0, 682 | topic: null, 683 | payload: null 684 | } 685 | 686 | var fixture = [ 687 | 224, 0 // Header 688 | ] 689 | 690 | this.stream.write(Buffer.from(fixture)) 691 | 692 | this.conn.once('disconnect', function (packet) { 693 | packet.should.eql(expected) 694 | done() 695 | }) 696 | }) 697 | }) 698 | 699 | describe('reserverd (15)', function () { 700 | it('should emit an error', function (done) { 701 | var fixture = [ 702 | 240, 0 // Header 703 | ] 704 | 705 | this.stream.write(Buffer.from(fixture)) 706 | 707 | this.conn.once('error', function () { 708 | done() 709 | }) 710 | }) 711 | }) 712 | 713 | describe('reserverd (0)', function () { 714 | it('should emit an error', function (done) { 715 | var fixture = [ 716 | 0, 0 // Header 717 | ] 718 | 719 | this.stream.write(Buffer.from(fixture)) 720 | 721 | this.conn.once('error', function () { 722 | done() 723 | }) 724 | }) 725 | }) 726 | } 727 | -------------------------------------------------------------------------------- /test/connection.transmit-v5.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | /** 4 | * Testing requires 5 | */ 6 | 7 | /** 8 | * Unit under test 9 | */ 10 | 11 | module.exports = function () { 12 | describe('#subscribe-5.0', function () { 13 | it('should send a 5.0 subscribe packet (single)', function (done) { 14 | var expected = Buffer.from([ 15 | 130, 10, // Header 16 | 0, 7, // Message id 17 | 0, // Properties 18 | 0, 4, // Topic length 19 | 116, 101, 115, 116, // Topic 20 | 0 // Qos=0 21 | ]) 22 | 23 | var fixture = { 24 | messageId: 7, 25 | subscriptions: [ 26 | { 27 | topic: 'test', 28 | qos: 0 29 | } 30 | ] 31 | } 32 | 33 | this.conn.subscribe(fixture) 34 | 35 | this.readFromStream(this.stream, expected.length, data => { 36 | data.should.eql(expected) 37 | done() 38 | }) 39 | }) 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /test/connection.transmit.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | /** 4 | * Testing requires 5 | */ 6 | 7 | var stream = require('./util').testStream 8 | 9 | /** 10 | * Unit under test 11 | */ 12 | 13 | var Connection = require('../connection') 14 | 15 | module.exports = function () { 16 | describe('#connect', function () { 17 | it('should send a connect packet (minimal)', function (done) { 18 | var expected = Buffer.from([ 19 | 16, 18, // Header 20 | 0, 6, 77, 81, 73, 115, 100, 112, // Protocol Id 21 | 3, // Protocol version 22 | 0, // Connect flags 23 | 0, 30, // Keepalive 24 | 0, 4, // Client id length 25 | 116, 101, 115, 116 // Client Id 26 | ]) 27 | 28 | var fixture = { 29 | protocolId: 'MQIsdp', 30 | protocolVersion: 3, 31 | clientId: 'test', 32 | keepalive: 30, 33 | clean: false 34 | } 35 | 36 | this.conn.connect(fixture) 37 | 38 | this.readFromStream(this.stream, expected.length, data => { 39 | data.should.eql(expected) 40 | done() 41 | }) 42 | }) 43 | 44 | it('should send a connect packet (maximal)', function (done) { 45 | var expected = Buffer.from([ 46 | 16, 54, // Header 47 | 0, 6, 77, 81, 73, 115, 100, 112, // Protocol Id 48 | 3, // Protocol version 49 | 246, // Connect flags (u=1,p=1,wr=1,wq=2,wf=1,c=1) 50 | 0, 30, // Keepalive (30) 51 | 0, 4, // Client id length 52 | 116, 101, 115, 116, // Client Id 53 | 0, 5, // Will topic length 54 | 116, 111, 112, 105, 99, // Will topic ('topic') 55 | 0, 7, // Will payload length 56 | 112, 97, 121, 108, 111, 97, 100, // ('payload') 57 | 0, 8, // Username length 58 | 117, 115, 101, 114, 110, 97, 109, 101, // ('username') 59 | 0, 8, // Password length 60 | 112, 97, 115, 115, 119, 111, 114, 100 // ('password') 61 | ]) 62 | 63 | var fixture = { 64 | protocolId: 'MQIsdp', 65 | protocolVersion: 3, 66 | clientId: 'test', 67 | keepalive: 30, 68 | will: { 69 | topic: 'topic', 70 | payload: 'payload', 71 | qos: 2, 72 | retain: true 73 | }, 74 | clean: true, 75 | username: 'username', 76 | password: 'password' 77 | } 78 | 79 | this.conn.connect(fixture) 80 | 81 | this.readFromStream(this.stream, expected.length, data => { 82 | data.should.eql(expected) 83 | done() 84 | }) 85 | }) 86 | 87 | it('should send a connect packet with binary username/password', function (done) { 88 | var expected = Buffer.from([ 89 | 16, 28, // Header 90 | 0, 6, 77, 81, 73, 115, 100, 112, // Protocol Id 91 | 3, // Protocol version 92 | 0x40 | 0x80, // Connect flags 93 | 0, 30, // Keepalive 94 | 0, 4, // Client id length 95 | 116, 101, 115, 116, // Client Id 96 | 0, 3, // Username length 97 | 12, 13, 14, // Username 98 | 0, 3, // Password length 99 | 15, 16, 17 // Password 100 | ]) 101 | 102 | var fixture = { 103 | protocolId: 'MQIsdp', 104 | protocolVersion: 3, 105 | clientId: 'test', 106 | keepalive: 30, 107 | clean: false, 108 | username: Buffer.from([12, 13, 14]), 109 | password: Buffer.from([15, 16, 17]) 110 | } 111 | 112 | var s = stream() 113 | var c = new Connection(s, { encoding: 'binary' }) 114 | 115 | s.removeAllListeners() 116 | c.connect(fixture) 117 | 118 | this.readFromStream(s, expected.length, data => { 119 | data.should.eql(expected) 120 | done() 121 | }) 122 | }) 123 | 124 | it('should send a connect packet with binary will payload', function (done) { 125 | var expected = Buffer.from([ 126 | 16, 50, // Header 127 | 0, 6, 77, 81, 73, 115, 100, 112, // Protocol Id 128 | 3, // Protocol version 129 | 246, // Connect flags 130 | 0, 30, // Keepalive 131 | 0, 4, // Client id length 132 | 116, 101, 115, 116, // Client Id 133 | 0, 5, // Will topic length 134 | 116, 111, 112, 105, 99, // Will topic ('topic') 135 | 0, 3, // Will payload length 136 | 18, 19, 20, // Will payload 137 | 0, 8, // Username length 138 | 117, 115, 101, 114, 110, 97, 109, 101, // ('username') 139 | 0, 8, // Password length 140 | 112, 97, 115, 115, 119, 111, 114, 100 // ('password') 141 | ]) 142 | 143 | var fixture = { 144 | protocolId: 'MQIsdp', 145 | protocolVersion: 3, 146 | clientId: 'test', 147 | keepalive: 30, 148 | will: { 149 | topic: 'topic', 150 | payload: Buffer.from([18, 19, 20]), 151 | qos: 2, 152 | retain: true 153 | }, 154 | clean: true, 155 | username: 'username', 156 | password: 'password' 157 | } 158 | 159 | var s = stream() 160 | var c = new Connection(s, { encoding: 'binary' }) 161 | 162 | s.removeAllListeners() 163 | c.connect(fixture) 164 | 165 | this.readFromStream(s, expected.length, data => { 166 | data.should.eql(expected) 167 | done() 168 | }) 169 | }) 170 | 171 | it('should send a connect packet with unicode will payload', function (done) { 172 | var expected = Buffer.from([ 173 | 16, 49, // Header 174 | 0, 6, 77, 81, 73, 115, 100, 112, // Protocol Id 175 | 3, // Protocol version 176 | 246, // Connect flags 177 | 0, 30, // Keepalive 178 | 0, 4, // Client id length 179 | 116, 101, 115, 116, // Client Id 180 | 0, 5, // Will topic length 181 | 116, 111, 112, 105, 99, // Will topic ('topic') 182 | 0, 2, // Will payload length 183 | 194, 167, // Will payload - '§' 184 | 0, 8, // Username length 185 | 117, 115, 101, 114, 110, 97, 109, 101, // ('username') 186 | 0, 8, // Password length 187 | 112, 97, 115, 115, 119, 111, 114, 100 // ('password') 188 | ]) 189 | 190 | var fixture = { 191 | protocolId: 'MQIsdp', 192 | protocolVersion: 3, 193 | clientId: 'test', 194 | keepalive: 30, 195 | will: { 196 | topic: 'topic', 197 | payload: '§', 198 | qos: 2, 199 | retain: true 200 | }, 201 | clean: true, 202 | username: 'username', 203 | password: 'password' 204 | } 205 | 206 | var s = stream() 207 | var c = new Connection(s, { encoding: 'binary' }) 208 | 209 | s.removeAllListeners() 210 | c.connect(fixture) 211 | 212 | this.readFromStream(s, expected.length, data => { 213 | data.should.eql(expected) 214 | done() 215 | }) 216 | }) 217 | 218 | describe('invalid options', function () { 219 | describe('protocol id', function () { 220 | it('should reject non-string', function (done) { 221 | var fixture = { 222 | protocolId: 42, 223 | protocolVersion: 3, 224 | clientId: 'test', 225 | keepalive: 30 226 | } 227 | 228 | var expectedErr = 'Invalid protocolId' 229 | 230 | this.conn.once('error', function (error) { 231 | error.message.should.equal(expectedErr) 232 | done() 233 | }) 234 | 235 | this.conn.connect(fixture) 236 | }) 237 | }) 238 | 239 | describe('protocol version', function () { 240 | it('should reject non-number', function (done) { 241 | var fixture = { 242 | protocolId: 'MQIsdp', 243 | protocolVersion: [], 244 | clientId: 'test', 245 | keepalive: 30 246 | } 247 | 248 | var expectedErr = 'Invalid protocol version' 249 | 250 | this.conn.once('error', function (error) { 251 | error.message.should.equal(expectedErr) 252 | done() 253 | }) 254 | 255 | this.conn.connect(fixture) 256 | }) 257 | 258 | it('should reject >255', function (done) { 259 | var fixture = { 260 | protocolId: 'MQIsdp', 261 | protocolVersion: 300, 262 | clientId: 'test', 263 | keepalive: 30 264 | } 265 | 266 | var expectedErr = 'Invalid protocol version' 267 | 268 | this.conn.once('error', function (error) { 269 | error.message.should.equal(expectedErr) 270 | done() 271 | }) 272 | 273 | this.conn.connect(fixture) 274 | }) 275 | 276 | it('should reject <0', function (done) { 277 | var fixture = { 278 | protocolId: 'MQIsdp', 279 | protocolVersion: -20, 280 | clientId: 'test', 281 | keepalive: 30 282 | } 283 | 284 | var expectedErr = 'Invalid protocol version' 285 | 286 | this.conn.once('error', function (error) { 287 | error.message.should.equal(expectedErr) 288 | done() 289 | }) 290 | 291 | this.conn.connect(fixture) 292 | }) 293 | }) 294 | 295 | describe('client id', function () { 296 | it('should reject non-present', function (done) { 297 | var fixture = { 298 | protocolId: 'MQIsdp', 299 | protocolVersion: 3, 300 | keepalive: 30 301 | } 302 | 303 | var expectedErr = 'clientId must be supplied before 3.1.1' 304 | 305 | this.conn.once('error', function (error) { 306 | error.message.should.equal(expectedErr) 307 | done() 308 | }) 309 | 310 | this.conn.connect(fixture) 311 | }) 312 | 313 | it('should reject empty', function (done) { 314 | var fixture = { 315 | protocolId: 'MQIsdp', 316 | protocolVersion: 3, 317 | clientId: '', 318 | keepalive: 30 319 | } 320 | 321 | var expectedErr = 'clientId must be supplied before 3.1.1' 322 | 323 | this.conn.once('error', function (error) { 324 | error.message.should.equal(expectedErr) 325 | done() 326 | }) 327 | 328 | this.conn.connect(fixture) 329 | }) 330 | 331 | it('should reject non-string', function (done) { 332 | var fixture = { 333 | protocolId: 'MQIsdp', 334 | protocolVersion: 3, 335 | clientId: {}, 336 | keepalive: 30 337 | } 338 | 339 | var expectedErr = 'clientId must be supplied before 3.1.1' 340 | 341 | this.conn.once('error', function (error) { 342 | error.message.should.equal(expectedErr) 343 | done() 344 | }) 345 | 346 | this.conn.connect(fixture) 347 | }) 348 | }) 349 | 350 | describe('keepalive', function () { 351 | it('should reject non-number', function (done) { 352 | var fixture = { 353 | protocolId: 'MQIsdp', 354 | protocolVersion: 3, 355 | clientId: 'test', 356 | keepalive: 'blah' 357 | } 358 | 359 | var expectedErr = 'Invalid keepalive' 360 | 361 | this.conn.once('error', function (error) { 362 | error.message.should.equal(expectedErr) 363 | done() 364 | }) 365 | 366 | this.conn.connect(fixture) 367 | }) 368 | 369 | it('should reject < 0', function (done) { 370 | var fixture = { 371 | protocolId: 'MQIsdp', 372 | protocolVersion: 3, 373 | clientId: 'test', 374 | keepalive: -2 375 | } 376 | 377 | var expectedErr = 'Invalid keepalive' 378 | 379 | this.conn.once('error', function (error) { 380 | error.message.should.equal(expectedErr) 381 | done() 382 | }) 383 | 384 | this.conn.connect(fixture) 385 | }) 386 | 387 | it('should reject > 65535', function (done) { 388 | var fixture = { 389 | protocolId: 'MQIsdp', 390 | protocolVersion: 3, 391 | clientId: 'test', 392 | keepalive: 65536 393 | } 394 | 395 | var expectedErr = 'Invalid keepalive' 396 | 397 | this.conn.once('error', function (error) { 398 | error.message.should.equal(expectedErr) 399 | done() 400 | }) 401 | 402 | this.conn.connect(fixture) 403 | }) 404 | }) 405 | 406 | describe('will', function () { 407 | it('should reject non-object', function (done) { 408 | var fixture = { 409 | protocolId: 'MQIsdp', 410 | protocolVersion: 3, 411 | clientId: 'test', 412 | keepalive: 30, 413 | will: 'test' 414 | } 415 | 416 | var expectedErr = 'Invalid will' 417 | 418 | this.conn.once('error', function (error) { 419 | error.message.should.equal(expectedErr) 420 | done() 421 | }) 422 | 423 | this.conn.connect(fixture) 424 | }) 425 | 426 | it('should reject will without valid topic', 427 | function (done) { 428 | var fixture = { 429 | protocolId: 'MQIsdp', 430 | protocolVersion: 3, 431 | clientId: 'test', 432 | keepalive: 30, 433 | will: { 434 | topic: 0, 435 | payload: 'test', 436 | qos: 0, 437 | retain: false 438 | } 439 | } 440 | 441 | var expectedErr = 'Invalid will topic' 442 | 443 | this.conn.once('error', function (error) { 444 | error.message.should.equal(expectedErr) 445 | done() 446 | }) 447 | 448 | this.conn.connect(fixture) 449 | }) 450 | 451 | it('should reject will without valid payload', 452 | function (done) { 453 | var fixture = { 454 | protocolId: 'MQIsdp', 455 | protocolVersion: 3, 456 | clientId: 'test', 457 | keepalive: 30, 458 | will: { 459 | topic: 'test', 460 | payload: 42, 461 | qos: 0, 462 | retain: false 463 | } 464 | } 465 | 466 | var expectedErr = 'Invalid will payload' 467 | 468 | this.conn.once('error', function (error) { 469 | error.message.should.equal(expectedErr) 470 | done() 471 | }) 472 | 473 | this.conn.connect(fixture) 474 | }) 475 | 476 | it.skip('should reject will with invalid qos', function (done) { 477 | var fixture = { 478 | protocolId: 'MQIsdp', 479 | protocolVersion: 3, 480 | clientId: 'test', 481 | keepalive: 30, 482 | will: { 483 | topic: 'test', 484 | payload: 'test', 485 | qos: '', 486 | retain: false 487 | } 488 | } 489 | 490 | var expectedErr = 'Invalid will qos' 491 | 492 | this.conn.once('error', function (error) { 493 | error.message.should.equal(expectedErr) 494 | done() 495 | }) 496 | 497 | this.conn.connect(fixture) 498 | }) 499 | }) 500 | 501 | describe('username', function () { 502 | it('should reject invalid username', function (done) { 503 | var fixture = { 504 | protocolId: 'MQIsdp', 505 | protocolVersion: 3, 506 | clientId: 'test', 507 | keepalive: 30, 508 | username: 30 509 | } 510 | 511 | var expectedErr = 'Invalid username' 512 | 513 | this.conn.once('error', function (error) { 514 | error.message.should.equal(expectedErr) 515 | done() 516 | }) 517 | 518 | this.conn.connect(fixture) 519 | }) 520 | }) 521 | 522 | describe('password', function () { 523 | it('should reject invalid password', function (done) { 524 | var fixture = { 525 | protocolId: 'MQIsdp', 526 | protocolVersion: 3, 527 | clientId: 'test', 528 | keepalive: 30, 529 | password: 30 530 | } 531 | 532 | var expectedErr = 'Username is required to use password' 533 | 534 | this.conn.once('error', function (error) { 535 | error.message.should.equal(expectedErr) 536 | done() 537 | }) 538 | 539 | this.conn.connect(fixture) 540 | }) 541 | }) 542 | }) 543 | }) 544 | 545 | describe('#connack', function () { 546 | it('should send a connack packet (rc = 0)', function (done) { 547 | var expected = Buffer.from([ 548 | 32, 2, // Header 549 | 0, 0 // Rc=0 550 | ]) 551 | 552 | var fixture = { 553 | returnCode: 0 554 | } 555 | 556 | this.conn.connack(fixture) 557 | 558 | this.readFromStream(this.stream, expected.length, data => { 559 | data.should.eql(expected) 560 | done() 561 | }) 562 | }) 563 | 564 | it('should send a connack packet (rc = 4)', function (done) { 565 | var expected = Buffer.from([ 566 | 32, 2, // Header 567 | 0, 4 // Rc=0 568 | ]) 569 | 570 | var fixture = { 571 | returnCode: 4 572 | } 573 | 574 | this.conn.connack(fixture) 575 | 576 | this.readFromStream(this.stream, expected.length, data => { 577 | data.should.eql(expected) 578 | done() 579 | }) 580 | }) 581 | 582 | it('should reject invalid rc', function (done) { 583 | this.conn.once('error', function (error) { 584 | error.message.should.equal('Invalid return code') 585 | done() 586 | }) 587 | this.conn.connack({returnCode: 'asdf'}) 588 | }) 589 | }) 590 | 591 | describe('#publish', function () { 592 | it('should send a publish packet (minimal)', function (done) { 593 | var expected = Buffer.from([ 594 | 48, 10, // Header 595 | 0, 4, // Topic length 596 | 116, 101, 115, 116, // Topic ('test') 597 | 116, 101, 115, 116 // Payload ('test') 598 | ]) 599 | 600 | var fixture = { 601 | topic: 'test', 602 | payload: 'test' 603 | } 604 | 605 | this.conn.publish(fixture) 606 | 607 | this.readFromStream(this.stream, expected.length, data => { 608 | data.should.eql(expected) 609 | done() 610 | }) 611 | }) 612 | 613 | it('should send a publish packet (maximal)', function (done) { 614 | var expected = Buffer.from([ 615 | 61, 12, // Header 616 | 0, 4, // Topic length 617 | 116, 101, 115, 116, // Topic ('test') 618 | 0, 7, // Message id (7) 619 | 116, 101, 115, 116 // Payload ('test') 620 | ]) 621 | 622 | var fixture = { 623 | topic: 'test', 624 | payload: 'test', 625 | qos: 2, 626 | retain: true, 627 | dup: true, 628 | messageId: 7 629 | } 630 | 631 | this.conn.publish(fixture) 632 | 633 | this.readFromStream(this.stream, expected.length, data => { 634 | data.should.eql(expected) 635 | done() 636 | }) 637 | }) 638 | 639 | it('should send a publish packet (empty)', function (done) { 640 | var expected = Buffer.from([ 641 | 48, 6, // Header 642 | 0, 4, // Topic length 643 | 116, 101, 115, 116 // Topic ('test') 644 | // Empty payload 645 | ]) 646 | 647 | var fixture = { 648 | topic: 'test' 649 | } 650 | 651 | this.conn.publish(fixture) 652 | 653 | this.readFromStream(this.stream, expected.length, data => { 654 | data.should.eql(expected) 655 | done() 656 | }) 657 | }) 658 | 659 | it('should send a publish packet (buffer)', function (done) { 660 | var expected = Buffer.from([ 661 | 48, 10, // Header 662 | 0, 4, // Topic length 663 | 116, 101, 115, 116, // Topic ('test') 664 | 0, 0, 0, 0 // Payload 665 | ]) 666 | var buf = Buffer.allocUnsafe(4) 667 | buf.fill(0) 668 | 669 | var fixture = { 670 | topic: 'test', 671 | payload: buf 672 | } 673 | 674 | this.conn.publish(fixture) 675 | 676 | this.readFromStream(this.stream, expected.length, data => { 677 | data.should.eql(expected) 678 | done() 679 | }) 680 | }) 681 | 682 | it('should send a publish packet of 2KB', function (done) { 683 | var expected = Buffer.from([ 684 | 48, 134, 16, // Header 685 | 0, 4, // Topic length 686 | 116, 101, 115, 116 // Topic ('test') 687 | ]) 688 | var payload = Buffer.allocUnsafe(2048) 689 | 690 | expected = Buffer.concat([expected, payload]) 691 | 692 | var fixture = { 693 | topic: 'test', 694 | payload: payload 695 | } 696 | 697 | this.readFromStream(this.stream, expected.length, data => { 698 | data.should.eql(expected) 699 | done() 700 | }) 701 | 702 | this.conn.publish(fixture) 703 | this.conn.end() 704 | }) 705 | 706 | it('should send a publish packet of 2MB', function (done) { 707 | var expected = Buffer.from([ 708 | 48, 134, 128, 128, 1, // Header 709 | 0, 4, // Topic length 710 | 116, 101, 115, 116 // Topic ('test') 711 | ]) 712 | var payload = Buffer.allocUnsafe(2 * 1024 * 1024) 713 | 714 | expected = Buffer.concat([expected, payload]) 715 | 716 | var fixture = { 717 | topic: 'test', 718 | payload: payload 719 | } 720 | 721 | this.conn.publish(fixture) 722 | 723 | this.readFromStream(this.stream, expected.length, data => { 724 | // Comparing the whole 2MB buffer is very slow so only check the length 725 | data.length.should.eql(expected.length) 726 | done() 727 | }) 728 | }) 729 | 730 | it('should reject invalid topic', function (done) { 731 | var error = 'Invalid topic' 732 | 733 | this.conn.once('error', function (err) { 734 | err.message.should.equal(error) 735 | done() 736 | }) 737 | this.conn.publish({topic: 0}) 738 | }) 739 | it('should reject invalid payloads, maybe') 740 | it('should reject invalid mid', function (done) { 741 | this.conn.once('error', function (err) { 742 | err.message.should.equal('Invalid messageId') 743 | done() 744 | }) 745 | this.conn.publish({topic: 'test', messageId: '', qos: 1}) 746 | }) 747 | }) 748 | 749 | describe('#puback', function () { 750 | it('should send a puback packet', function (done) { 751 | var expected = Buffer.from([ 752 | 64, 2, // Header 753 | 0, 30 // Mid=30 754 | ]) 755 | 756 | var fixture = { 757 | messageId: 30 758 | } 759 | 760 | this.conn.puback(fixture) 761 | 762 | this.readFromStream(this.stream, expected.length, data => { 763 | data.should.eql(expected) 764 | done() 765 | }) 766 | }) 767 | 768 | it('should reject invalid mid', function (done) { 769 | this.conn.once('error', function (error) { 770 | error.message.should.equal('Invalid messageId') 771 | done() 772 | }) 773 | this.conn.puback({messageId: ''}) 774 | }) 775 | }) 776 | 777 | describe('#pubrec', function () { 778 | it('should send a pubrec packet', function (done) { 779 | var expected = Buffer.from([ 780 | 80, 2, // Header 781 | 0, 3 // Mid=3 782 | ]) 783 | 784 | var fixture = { 785 | messageId: 3 786 | } 787 | 788 | this.conn.pubrec(fixture) 789 | 790 | this.readFromStream(this.stream, expected.length, data => { 791 | data.should.eql(expected) 792 | done() 793 | }) 794 | }) 795 | 796 | it('should reject invalid mid') 797 | }) 798 | 799 | describe('#pubrel', function () { 800 | it('should send a pubrel packet', function (done) { 801 | var expected = Buffer.from([ 802 | 98, 2, // Header 803 | 0, 6 // Mid=6 804 | ]) 805 | 806 | var fixture = { 807 | messageId: 6 808 | } 809 | 810 | this.conn.pubrel(fixture) 811 | 812 | this.readFromStream(this.stream, expected.length, data => { 813 | data.should.eql(expected) 814 | done() 815 | }) 816 | }) 817 | 818 | it('should reject invalid mid') 819 | }) 820 | 821 | describe('#pubcomp', function () { 822 | it('should send a pubcomp packet', function (done) { 823 | var expected = Buffer.from([ 824 | 112, 2, // Header 825 | 0, 9 // Mid=9 826 | ]) 827 | 828 | var fixture = { 829 | messageId: 9 830 | } 831 | 832 | this.conn.pubcomp(fixture) 833 | 834 | this.readFromStream(this.stream, expected.length, data => { 835 | data.should.eql(expected) 836 | done() 837 | }) 838 | }) 839 | 840 | it('should reject invalid mid') 841 | }) 842 | 843 | describe('#subscribe', function () { 844 | it('should send a subscribe packet (single)', function (done) { 845 | var expected = Buffer.from([ 846 | 130, 9, // Header 847 | 0, 7, // Message id 848 | 0, 4, // Topic length 849 | 116, 101, 115, 116, // Topic 850 | 0 // Qos=0 851 | ]) 852 | 853 | var fixture = { 854 | messageId: 7, 855 | subscriptions: [ 856 | { 857 | topic: 'test', 858 | qos: 0 859 | } 860 | ] 861 | } 862 | 863 | this.conn.subscribe(fixture) 864 | 865 | this.readFromStream(this.stream, expected.length, data => { 866 | data.should.eql(expected) 867 | done() 868 | }) 869 | }) 870 | 871 | it('should send a subscribe packet (multiple)', function (done) { 872 | var expected = Buffer.from([ 873 | 130, 23, // Header 874 | 0, 8, // Message id 875 | 0, 4, // Topic length 876 | 116, 101, 115, 116, // Topic ('test') 877 | 0, // Qos=0 878 | 0, 4, // Topic length 879 | 117, 101, 115, 116, // Topic ('uest') 880 | 1, // Qos=1 881 | 0, 4, // Topic length 882 | 116, 101, 115, 115, // Topic ('tess') 883 | 2 // Qos=2 884 | ]) 885 | 886 | var fixture = { 887 | messageId: 8, 888 | subscriptions: [ 889 | { 890 | topic: 'test', 891 | qos: 0 892 | }, { 893 | topic: 'uest', 894 | qos: 1 895 | }, { 896 | topic: 'tess', 897 | qos: 2 898 | } 899 | ] 900 | } 901 | 902 | this.conn.subscribe(fixture) 903 | 904 | this.readFromStream(this.stream, expected.length, data => { 905 | data.should.eql(expected) 906 | done() 907 | }) 908 | }) 909 | it('should reject invalid subscriptions', function (done) { 910 | this.conn.once('error', function (error) { 911 | error.message.should.equal('Invalid subscriptions') 912 | done() 913 | }) 914 | this.conn.subscribe({ 915 | messageId: 1, subscriptions: '' 916 | }) 917 | }) 918 | 919 | it('should reject invalid subscription objects') 920 | it('should reject invalid mid', function (done) { 921 | this.conn.once('error', function (error) { 922 | error.message.should.equal('Invalid messageId') 923 | done() 924 | }) 925 | this.conn.subscribe({ 926 | messageId: '', subscriptions: [{topic: 'test', qos: 1}] 927 | }) 928 | }) 929 | }) 930 | 931 | describe('#suback', function () { 932 | it('should send a suback packet', function (done) { 933 | var expected = Buffer.from([ 934 | 144, 5, // Length 935 | 0, 4, // Mid=4 936 | 0, // Qos=0 937 | 1, // Qos=1 938 | 2 // Qos=2 939 | ]) 940 | 941 | var fixture = { 942 | granted: [0, 1, 2], 943 | messageId: 4 944 | } 945 | 946 | this.conn.suback(fixture) 947 | 948 | this.readFromStream(this.stream, expected.length, data => { 949 | data.should.eql(expected) 950 | done() 951 | }) 952 | }) 953 | 954 | it('should reject invalid mid') 955 | it('should reject invalid qos vector', function (done) { 956 | this.conn.on('error', function (error) { 957 | error.message.should.equal('Invalid qos vector') 958 | done() 959 | }) 960 | this.conn.suback({granted: '', messageId: 1}) 961 | }) 962 | }) 963 | 964 | describe('#unsubscribe', function () { 965 | it('should send an unsubscribe packet', function (done) { 966 | var expected = Buffer.from([ 967 | 162, 14, // Header 968 | 0, 6, // Mid=6 969 | 0, 4, // Topic length 970 | 116, 101, 115, 116, // Topic ('test') 971 | 0, 4, // Topic length 972 | 116, 115, 101, 116 // Topic ('tset') 973 | ]) 974 | 975 | var fixture = { 976 | messageId: 6, 977 | unsubscriptions: [ 978 | 'test', 'tset' 979 | ] 980 | } 981 | 982 | this.conn.unsubscribe(fixture) 983 | 984 | this.readFromStream(this.stream, expected.length, data => { 985 | data.should.eql(expected) 986 | done() 987 | }) 988 | }) 989 | 990 | it('should reject invalid unsubs', function (done) { 991 | this.conn.once('error', function (error) { 992 | error.message.should.equal('Invalid unsubscriptions') 993 | done() 994 | }) 995 | this.conn.unsubscribe({ 996 | messageId: 1, 997 | unsubscriptions: '' 998 | }) 999 | }) 1000 | it('should reject invalid mids') 1001 | }) 1002 | 1003 | describe('#unsuback', function () { 1004 | it('should send a unsuback packet', function (done) { 1005 | var expected = Buffer.from([ 1006 | 176, 2, // Header 1007 | 0, 8 // Mid=8 1008 | ]) 1009 | 1010 | var fixture = { 1011 | messageId: 8 1012 | } 1013 | 1014 | this.conn.unsuback(fixture) 1015 | 1016 | this.readFromStream(this.stream, expected.length, data => { 1017 | data.should.eql(expected) 1018 | done() 1019 | }) 1020 | }) 1021 | 1022 | it('should reject invalid mid') 1023 | }) 1024 | 1025 | describe('#pingreq', function () { 1026 | it('should send a pingreq packet', function (done) { 1027 | var expected = Buffer.from([ 1028 | 192, 0 // Header 1029 | ]) 1030 | 1031 | var fixture = { 1032 | } 1033 | 1034 | this.conn.pingreq(fixture) 1035 | 1036 | this.readFromStream(this.stream, expected.length, data => { 1037 | data.should.eql(expected) 1038 | done() 1039 | }) 1040 | }) 1041 | }) 1042 | 1043 | describe('#pingresp', function () { 1044 | it('should send a pingresp packet', function (done) { 1045 | var expected = Buffer.from([ 1046 | 208, 0 // Header 1047 | ]) 1048 | 1049 | var fixture = { 1050 | } 1051 | 1052 | this.conn.pingresp(fixture) 1053 | 1054 | this.readFromStream(this.stream, expected.length, data => { 1055 | data.should.eql(expected) 1056 | done() 1057 | }) 1058 | }) 1059 | }) 1060 | 1061 | describe('#disconnect', function () { 1062 | it('should send a disconnect packet', function (done) { 1063 | var expected = Buffer.from([ 1064 | 224, 0 // Header 1065 | ]) 1066 | 1067 | var fixture = { 1068 | } 1069 | 1070 | this.conn.disconnect(fixture) 1071 | 1072 | this.readFromStream(this.stream, expected.length, data => { 1073 | data.should.eql(expected) 1074 | done() 1075 | }) 1076 | }) 1077 | 1078 | it('should send a null disconnect packet', function (done) { 1079 | var expected = Buffer.from([ 1080 | 224, 0 // Header 1081 | ]) 1082 | 1083 | this.conn.disconnect() 1084 | 1085 | this.readFromStream(this.stream, expected.length, data => { 1086 | data.should.eql(expected) 1087 | done() 1088 | }) 1089 | }) 1090 | }) 1091 | } 1092 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | 2 | var through = require('through2') 3 | var setImmediate = global.setImmediate 4 | 5 | setImmediate = setImmediate || function (func) { 6 | setTimeout(func, 0) 7 | } 8 | 9 | module.exports.testStream = function () { 10 | return through(function (buf, enc, cb) { 11 | var that = this 12 | setImmediate(function () { 13 | that.push(buf) 14 | cb() 15 | }) 16 | }) 17 | } 18 | --------------------------------------------------------------------------------