New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature mqtt 5 #827
Feature mqtt 5 #827
Conversation
Good work, I’ll wait to land this until mqtt-packet and mqtt-connection has been updated and release. Do you think this is a semver-minor change? |
HI, thanks for approving my commit. (the main answer in mitt-packet) |
Hello, can you publish new version of mqtt-connect to npm? And we could update this package as well. |
@scarry1992 can you please update this so that tests are passing? Please do not bump the version or merge this. Thanks. |
@mcollina yes, I can, but I needed in updated version of mqtt-connection. After updating mqtt-connection packet in npm, I will update MQTT.js and all test will be OK. I'm sorry for mqtt-connection merge. |
It is now. |
Codecov Report
@@ Coverage Diff @@
## master #827 +/- ##
==========================================
+ Coverage 94.13% 94.39% +0.26%
==========================================
Files 8 8
Lines 767 874 +107
Branches 190 231 +41
==========================================
+ Hits 722 825 +103
- Misses 45 49 +4
Continue to review full report at Codecov.
|
@mcollina Do I need to write more tests? |
I think so, yes. Look at the areas that are not covered in codecov. |
// auth | ||
if (this.options.properties) { | ||
if (!this.options.properties.authenticationMethod && this.options.properties.authenticationData) { | ||
this.emit('error', new Error('Packet has no Authentication Method')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we might want to return after this? Emitting 'error'
is a distructive action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I`ll add returning 'this' after emitting error
var options = this.options | ||
|
||
if (options.protocolVersion === 5 && options.properties && options.properties.maximumPacketSize && options.properties.maximumPacketSize < packet.length) { | ||
this.emit('error', new Error('exceeding packets size ' + packet.cmd)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we are emitting error, we should really destroy the client or fully disconnect (and reconnect).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ill add end for connection with reason code, but don
t add reconnect, because I get loop, if do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please explain it a bit more? I think our goal with this client is to keep operating. If we receive such a bad packet (and we keep receiving it), the client will not be able to operate anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From spec: If a Client receives a packet whose size exceeds this limit, this is a Protocol Error, the Client uses DISCONNECT with Reason Code 0x95 (Packet too large). If you think, that bad idea, I can remove emitting error, but I think thats right. Just tell me what do you think. I think user must proccess that error himself.
lib/client.js
Outdated
// 160: 'Maximum connect time', | ||
// 161: 'Subscription Identifiers not supported', | ||
// 162: 'Wildcard Subscriptions not supported' | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this block commented here? If it's not needed, remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolve
lib/client.js
Outdated
// 162: 'Wildcard Subscriptions not supported' | ||
// } | ||
|
||
var args = Array.prototype.slice.call(arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't do this. It's super slow in older versions of Node.js (< 6), and not really needed in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I`ll remove it
lib/client.js
Outdated
156: 'Use another server', | ||
157: 'Server moved', | ||
159: 'Connection rate exceeded' | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should really go to the top of the file. There is no need to declare this here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolve
lib/client.js
Outdated
if (pubackRC && pubackRC > 0 && pubackRC !== 16) { | ||
err = new Error('Publish error: ' + errors[pubackRC]) | ||
err.code = pubackRC | ||
this.emit('error', err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should call the callback with the error, not emitting one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rebase to call callback
lib/client.js
Outdated
if (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) { | ||
err = new Error('Publish error: ' + errors[pubrecRC]) | ||
err.code = pubrecRC | ||
this.emit('error', err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rebase to call callback
var that = this | ||
var connection = new Connection(duplex, function () { | ||
that.emit('client', connection) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this changed because server`s initialization logic was been changed. Server process packets by version in connect packet
Hi @scarry1992 thanks for the great effort! I have a question regarding your design choices. I understand that most of the publish property fields (like However I believe the Currently - if I didn't miss anything in your implementation - I understand that the application must give the Other question: could you state whether your implementation implements the full set of MQTT5 features or is there anything still to do? |
I am also thinking about other convenience features that could reflect some MQTT5 ideas in the client API, e.g.:
might be more.. but that's just for now. |
@psorowka I’ve got your point and it does make sense. However, I still think that this logic should be left at the discretion of the MQTT.js user. We can also ask @mcollina opinion and if he believes that this functionality should be implemented, I will do that. @mcollina , MQTT 5 enables communication between the broker and the client using reason codes, which, in my understanding, means that there should be the opportunity to send puback/pubrel packets manually by a user. Did I get it right? More details here: pubac specs in reason codes section (Implementation specific error). Please also check my fixes based on the comments in the review. |
@scarry1992 what are the arguments towards leaving the alias logic to the user? My strong opinion is that such kind of communication logic is what a user expects to be handled by a well tested client library instead of heaving to implement that manually in each application. |
@scarry1992 as for the PUBACK. As far as I understand, that does not mean that a PUBACK packet should be triggered manually, it's still always the direct response to a PUBLISH packet. As the usual way to receive messages is the event listener, I think it's not possible to set a response from the callback. The only way I can think of would be to offer the ability to add an "interceptor" function that's called before the message event is triggered and may set a reason code. On the other hand I am really wondering if it's really the intention of the spec to have the client set other reason codes than 0x00 or 0x80.. what should the broker do with the responses... |
I'm sorry for the long delay in reviewing this. I've been traveling for most of the last month, with more traveling next month. @scarry1992 Good work!
I agree with you. @psorowka I would like to keep this implementation minimal, so we can cut a new major and then improve on that one. We can even land it and start cutting -rc releases, and iron bugs along the way. @scarry1992 @psorowka can you please point me to some docs about topic alias? |
I believe that it is necessary to leave the topicAlias property on the user side because, at the moment, this makes the use of this flag not transparent, and can lead to incorrect behavior with different brokers. Most brokers currently do not support or support partially 5 standard and there may not be implemented this feature, and there is no need to completely stop working with this broker. I myself like the idea of bringing this logic to the library, but so far, it seems to me, you can not do it. |
Vernemq is already almost completely supporting everyithing of mqtt5, just for testing. IMHO topic aliases are one of the coolest features as it reduces the package size significantly. When releasing this to major we must be very careful that topicaliases are disabled by default (topicAliasMaximum 0) because otherwise a user might receive onMessage events with empty topic strings unexpectedly |
Hi. I did custom handling reason codes feature. |
@mcollina Do I need to implement something else? |
var options = this.options | ||
|
||
if (options.protocolVersion === 5 && options.properties && options.properties.maximumPacketSize && options.properties.maximumPacketSize < packet.length) { | ||
this.emit('error', new Error('exceeding packets size ' + packet.cmd)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please explain it a bit more? I think our goal with this client is to keep operating. If we receive such a bad packet (and we keep receiving it), the client will not be able to operate anymore.
lib/client.js
Outdated
@@ -93,6 +138,8 @@ function MqttClient (streamBuilder, options) { | |||
|
|||
this.options.clientId = (typeof this.options.clientId === 'string') ? this.options.clientId : defaultId() | |||
|
|||
this.options.customHandleAcks = this.options.protocolVersion === 5 ? this.options.customHandleAcks : false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems we are manipulating this.options
a lot in this function. Would you mind extracting it into a variable var options = this.options
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
lib/client.js
Outdated
(!this.options.properties.topicAliasMaximum && opts.properties.topicAlias)))) { | ||
delete packet.properties.topicAlias | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a comment explaining this block?
Also, can you extract var options = this.options
for readability?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -427,7 +503,7 @@ MqttClient.prototype.publish = function (topic, message, opts, callback) { | |||
* @api public | |||
* @example client.subscribe('topic'); | |||
* @example client.subscribe('topic', {qos: 1}); | |||
* @example client.subscribe({'topic': 0, 'topic2': 1}, console.log); | |||
* @example client.subscribe({'topic': {qos: 0}, 'topic2': {qos: 1}}, console.log); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this previous form now removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right
lib/client.js
Outdated
that._resubscribeTopics[sub.topic].nl = sub.nl || false | ||
that._resubscribeTopics[sub.topic].rap = sub.rap || false | ||
that._resubscribeTopics[sub.topic].rh = sub.rh || 0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are manipulating a lot _resubscribeTopics
. Can you please extract it into a separate variable?
Can you also add the docs for nl
, rap
and rh
? If these are not exposed, can you please add a comment somewhere explaining them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed, added info in readme
lib/client.js
Outdated
var packet = { | ||
cmd: 'unsubscribe', | ||
qos: 1, | ||
messageId: this._nextId() | ||
} | ||
var that = this | ||
var args = Array.prototype.slice.call(arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is better implemented with:
var args = new Array(arguments.length)
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i]
}
because using Array.prototype.slice
is extremely unperformant in older Node.js versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implemented
lib/client.js
Outdated
if (!this.options.properties) { this.options.properties = {} } | ||
this.options.properties.maximumPacketSize = packet.properties.maximumPacketSize | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you extract var options = this.options
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
lib/client.js
Outdated
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this function becomes very complicated with the customHandleHack
function. Can you please refactor so that there is always a default for that, and we avoid all those ifs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored, check pls
lib/client.js
Outdated
that._sendPacket({cmd: 'puback', messageId: mid}, done) | ||
}) | ||
if (this.options.customHandleAcks) { | ||
this.options.customHandleAcks(topic, message, packet, function (code) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the signature for the callback of customHandleAcks
must be function (err, code) {}
. We should also validate that code
is valid, or is this done when generating a PUBACK`?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code validation added
I fixed all issues and give answers. Can You check it and release new version? |
@mcollina, After update my branch, I get error in CI in nodes 4,6,9 "The command "sudo -E apt-add-repository -y "ppa:ubuntu-toolchain-r/test"" failed and exited with 1 during ." |
Travis is a bit flaky these days |
@mcollina, so when can we realese mqtt 5.0? |
In 4.3.2, 7, 8 nodes tests passed. |
Do you know which brokers are currently compatible with this? I would like to do some manual testing. My goto broker for this type of things is Mosquitto, and it's not supporting MQTT v5, but it should be ready sooner rather than later https://mosquitto.org/blog/2018/05/press-release/. Regarding the CI, could you: a) drop testing on Node 4, 4.3.2 (lambda), 5, 7, and 9? Also, add Node 10. |
@mcollina VerneMQ has a MQTTv5 preview release see https://vernemq.com/mqttv5.html |
@mcollina, I tested on flespi broker with mqtt 5.0 and that branch in production in my projects allredy. |
@psorowka, can you testing that branch on VerneMQ youself please? |
Could you add a note in the README that MQTT v5 support is experimental as it has not been implemented by brokers yet? |
@mcollina yes sure |
@scarry1992 can you please rebase your changes or merge master? It's conflicting now. |
@mcollina done) |
Hi. We've implemented the MQTT 5.0 support. Please have a look and merge if it's OK. We will also make similar pull-requests to mqtt-connection and mqtt-packet repos.