Skip to content
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

Refined Topic Alias support. (Implement #1300) #1301

Merged
merged 5 commits into from
Oct 4, 2021

Conversation

redboltz
Copy link
Contributor

Add automatic topic alias management functionality.

  • On PUBLISH sending, the client can automatic using/assin Topic
    Alias (optional).
  • On PUBLISH receiving, the topic parameter of on message handler is
    automatically complemented but the packet.topic preserves the original
    topic.

Fix invalid tests.

Add automatic topic alias management functionality.
- On PUBLISH sending, the client can automatic using/assin Topic
Alias (optional).
- On PUBLISH receiving, the topic parameter of on message handler is
automatically complemented but the packet.topic preserves the original
topic.

Fix invalid tests.
@YoDaMa
Copy link
Contributor

YoDaMa commented Oct 4, 2021

@redboltz my next question, do you think this is changing the behavior of the existing API at all?

README.md Outdated
@@ -206,7 +206,41 @@ the final connection when it drops.
The default value is 1000 ms which means it will try to reconnect 1 second
after losing the connection.

<a name="topicalias"></a>
## About Topic Alias management
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: caps on Management

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I will fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@redboltz
Copy link
Contributor Author

redboltz commented Oct 4, 2021

@redboltz my next question, do you think this is changing the behavior of the existing API at all?

No. I don't think so.

Interface

This PR adds options. Not contains breaking changes for existing APIs.

Behavior

Auto Use/Assign Topic Alias functionality are enabled only if the user set options autoAssignTopicAlias or autoUseTopicAlias.
They are not set by default. So existing APIs behave the same as before.

There is one exception. When QoS1/2 packets that use Topic Alias are inserted into the store, Topic Alias is removed and Topic Name is recovered. It is expected behavior by MQTT spec. So I think this behavior change is a bug fix.

lib/client.js Outdated
}

function removeTopicAlias (client, packet) {
// remove topic alias because it shouldn't be used on re-sending
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a bit of a better comment here. What's the use for this function? Is it removing a topic alias because some other topic has overridden the topic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a kind of utility function for internal use.
I think that the commend should be removed here.
This function removes Topic Alias property and recover Topic Name from the Topic Alias.
Perhaps the function name should be removeTopicAliasAndRecoverTopicName. Is this too long?
I think that the comment should be written the caller side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

lib/client.js Show resolved Hide resolved
@@ -73,6 +74,7 @@
"number-allocator": "^1.0.7",
"pump": "^3.0.0",
"readable-stream": "^3.6.0",
"rfdc": "^1.3.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always hesitant to add more dependencies. Could you explain the purpose of rfdc and collections, and why there isn't a suitable Node.js / JS builtin that can perform their tasks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rfdc is for clone that I mentioned at #1301 (comment)
It is efficient and straightforward notation. It seems that the library is well maintained and the big number of weekly downloads. See Benchmarks https://www.npmjs.com/package/rfdc

AFAIK, Node.js/JS there is no straightforward way.
One of the built-in way is as follows:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

https://stackoverflow.com/questions/6089058/nodejs-how-to-clone-an-object

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to have created alot of issues with collections...

see there :
#homebridge-plugins/homebridge-camera-ffmpeg#1240
#NorthernMan54/homebridge-alexa#481
#dgreif/ring#844
and probably many more...

qos: 0,
properties: { topicAlias: 1 }
})
// overwrite register topicAlias
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "overwrite registered topicAlias"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I will fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

// use topicAlias
client.publish('', 'Message', { properties: { topicAlias: 1 } })
// use topicAlias by autoApplyTopicAlias
client.publish('test1', 'Message')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so when you enable autoApplyTopicAlias it will substitute the alias for the message, and existing behaviour without it set to true is that on client.publish('test1', 'Message'), the topicAlias already registered wouldn't be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. If autoApplyTopicAlias is true, and the user wants to use Topic Alias, the user needs to set the empty TopicName and add Topic Alias property manually.

client.publish('test3', 'Message')

// renew LRU topicAlias
client.publish('test4', 'Message')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so here the topic alias will be updated dynamically because autoAssignTopicAlias is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor

@YoDaMa YoDaMa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens on reconnect? or client.end? is there persistence of the topicAliases? Not 100% on the MQTT spec for this scenario...

@YoDaMa
Copy link
Contributor

YoDaMa commented Oct 4, 2021

also, great PR. Thanks for providing this along with tests. @redboltz great job.

if (entry) {
delete this.topicToAlias[entry.topic]
}
this.aliasToTopic.set(alias, {'topic': topic, 'alias': alias})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this redundant to have the set method on aliasToTopic and topicToAlias as a general JS object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    this.aliasToTopic = new LruMap()
    this.topicToAlias = {}

aliasToTopic is an instance of LruMap(). aliasToTopic requires LRU for overwriting. So it cannot be a simple JS Object.
topicToAlias is JS Object. It doesn't require LRU.

Could you elaborate your question?
Do you mean add set method to topicToAlias ?
And update the code using this/topicToAlias.set(topic, alias) instead of this.topicToAlias[topic] = alias ?
I personally think it is redundant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that makes sense. Ignore my initial question. New question: why {topic: topic, alias: alias}, when the information for the alias is the key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for

  return this.aliasToTopic.min().alias

See https://github.com/mqttjs/MQTT.js/pull/1301/files/f8178aab47846ba7f2f34e436bc1ff3d5f23de59#diff-ae7ca6f8f9c9488b9789056d3b1e4aee44861bb0f86c3000c8361bbad8c7bb28R90

lru-map only provide least value not key. I tried to find getting key-value entry way but it seems that it doesn't exist. https://www.collectionsjs.com/lru-map

That's why I added alias not only as key but also as value.

}

/**
* Get LRU topic alias
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand LRU to "Get Least Recently Used (LRU) topic alias"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will fix it.

@redboltz
Copy link
Contributor Author

redboltz commented Oct 4, 2021

what happens on reconnect? or client.end? is there persistence of the topicAliases? Not 100% on the MQTT spec for this scenario...

See #1300 (comment) Topic Alias lifetime and Impact for implementation.

Topic Alias lifetime is during connection, not session. So the client/server cannot use Topic Aliases that is used by the previous connection. This is the spec.
In order to satisfy the spec, the client library needs to manage store carefully. So this is the reason for removeTopicAlias().

@YoDaMa
Copy link
Contributor

YoDaMa commented Oct 4, 2021

pending nits on comments, this PR looks good to me. Will approve once changes are pushed.

…AndRecoverTopicName`.

Add comments for caller side of `removeTopicAliasAndRecoverTopicName`.
@redboltz
Copy link
Contributor Author

redboltz commented Oct 4, 2021

pending nits on comments, this PR looks good to me. Will approve once changes are pushed.

I think that I've done all requested fixes. Please check them.

Copy link
Contributor

@YoDaMa YoDaMa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@YoDaMa
Copy link
Contributor

YoDaMa commented Oct 4, 2021

thank you @redboltz!

@YoDaMa YoDaMa merged commit c92b877 into mqttjs:master Oct 4, 2021
@redboltz
Copy link
Contributor Author

redboltz commented Oct 5, 2021

@YoDaMa Thank you for merging !!

I noticed one minor behavior change.

client.on('message', function (topic, messagee, packet) {
switch (publishCount++) {
case 0:
assert.strictEqual(topic, 'test1')
assert.strictEqual(packet.topic, 'test1')
assert.strictEqual(packet.properties.topicAlias, 1)
break
case 1:
assert.strictEqual(topic, 'test1')
assert.strictEqual(packet.topic, '')
assert.strictEqual(packet.properties.topicAlias, 1)
break

On message handler, when broker sends an empty TopicName and registered TopicAliasProperty, the first parameter topic is automatically overwritten as registered TopicName. But the third parameter packet preserve the actual received packet.

It always happens even if TopicAlias related options are not set. But I believe that it is expected behavior for users.
Users can know both registered TopicName and actual received packet.

@YoDaMa
Copy link
Contributor

YoDaMa commented Dec 28, 2021 via email

@redboltz
Copy link
Contributor Author

I think that collections should be replaced some other library.
The requirement is https://stackoverflow.com/questions/65948823/sorted-ordered-collection-for-javascript
I started finding the library. If someone has information, it is very welcome.

@redboltz
Copy link
Contributor Author

I think that collections should be replaced some other library. The requirement is https://stackoverflow.com/questions/65948823/sorted-ordered-collection-for-javascript I started finding the library. If someone has information, it is very welcome.

Sorry I looked over montagejs/collections#241 (comment). I said "replacing" but it was my overreaction.
I wait the fix and release.

Just in case, I continue to look for altenative one.

@redboltz
Copy link
Contributor Author

I analyzed the issue and commected montagejs/collections#241 (comment)
When the next version of collections (5.1.13?) would be released, I will update it. (on number-allocator and MQTT.js)
It would be a partial fix of #1385 . "partial" means if other out of MQTT.js package or application use 5.1.12 (current release) or older collections.js, the user still has the problem.

collections.js updates Array.
I personally think that updating (overwriting) intrinsic Array.from() is not good way. I guess that there is some historycal reason (older browser support?).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants