Skip to content

net: add Asymcute, an asynchronous MQTT-SN client implementation#9464

Merged
miri64 merged 5 commits intoRIOT-OS:masterfrom
haukepetersen:add_asymcute
Jul 5, 2018
Merged

net: add Asymcute, an asynchronous MQTT-SN client implementation#9464
miri64 merged 5 commits intoRIOT-OS:masterfrom
haukepetersen:add_asymcute

Conversation

@haukepetersen
Copy link
Copy Markdown
Contributor

Contribution description

This PR adds a new MQTT-SN client implementation called Asymcute. In contrary to the exising emCute implementation, this one is completely asynchronous, thus allowing for a higher throughput and for more flexibility to the cost of increased memory usage (both RAM and ROM).

Asymcute is not only able to handle multiple pending requests concurrently, it is also able to connect to more than one gateway at a time.

The motivation for implementing a second MQTT-SN client comes mainly from some application layer protocol experiments that we were running earlier this year in the context of our 'I3' research project, where we were measuring certain metrics for MQTT-SN, CoAP and some ICN-based approaches. For this, the existing synchronous emCute library was not well suited, so hence the need for a fully asynchronous MQTT-SN client library.

Testing

Easiest way (using native):

  • run a local mqtt-sn broker using the build in rsmb make target (see tools: add mosquitto.rsmb MQTT-SN broker #9459)
  • run the examples/asymcute_mqttsn application under native
  • play, also hard-kill (kill -9) the rsmb instance to see the effect due to time out on keep-alive messages. For this it makes sense though to reduce the keep alive interval (override ASYMCUTE_KEEPALIVE).

Issues/PRs references

rebased/depending on #9459

@haukepetersen haukepetersen added Area: network Area: Networking Type: new feature The issue requests / The PR implemements a new feature for RIOT State: waiting for other PR State: The PR requires another PR to be merged first CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Jun 29, 2018
@waehlisch
Copy link
Copy Markdown
Member

waehlisch commented Jun 29, 2018

Just to clarify the motivation for this PR: The reason of having asynchronous MQTT-SN support is not because of a research project but because RIOT should support MQTT-SN 1.2 more completely.

The MQTT spec,, which is the base for MQTT-SN, says explicitly in Section 4.3.2: Note that a Sender is permitted to send further PUBLISH Packets with different Packet Identifiers while it is waiting to receive acknowledgements.

@waehlisch waehlisch requested a review from miri64 June 29, 2018 15:57
@miri64
Copy link
Copy Markdown
Member

miri64 commented Jul 2, 2018

I wait with my proper review until #9459 is merged, but please fix the typo in a33f95c (s/clien/client/) until then

@haukepetersen
Copy link
Copy Markdown
Contributor Author

The MQTT-SN spec says explicitly in Section 4.3.2: Note that a Sender is permitted to send further PUBLISH Packets with different Packet Identifiers while it is waiting to receive acknowledgements.

But to underline the beauty of the 'standard': Section 6.6 also states At any point in time a client may have only one QoS level 1 or 2 PUBLISH message outstanding, i.e. it has to wait for the termination of this PUBLISH message exchange before it could start a new level 1 or 2 transaction

So with the two implementations now available, we are complying to either statement and have it all covered :-)

@haukepetersen haukepetersen changed the title Add asymcute net: add Asymcute, an asynchronous MQTT-SN client implementation Jul 4, 2018
@haukepetersen
Copy link
Copy Markdown
Contributor Author

fixed the PR title...

@miri64
Copy link
Copy Markdown
Member

miri64 commented Jul 4, 2018

Needs rebase.

@haukepetersen
Copy link
Copy Markdown
Contributor Author

rebased (and fixed typo in commit title)

Copy link
Copy Markdown
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

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

Somehow GitHub dropped a lot of my review comments (after 4 hours of intense review :(), so here's what's left for now. Will look at it tomorrow again. Mh now they all here again...

```

This will download, build, and run the Eclipse Mosquitto.rsmb `Really Small
Message Broker` [(found here)](https://github.com/eclipse/mosquitto.rsmb).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit-picky: Any reason for the monospace format for "Really Small Message Broker"?

#include "net/ipv6/addr.h"

#ifndef REG_CTX_NUMOF
#define REG_CTX_NUMOF (8U)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

REQ?

* @brief Asymcute connection context
*/
struct asymcute_con {
uint8_t state; /**< connection state */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you group this with last_id and keepalive_retry_cnt please, to save some RAM by packing the struct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IMO this would be premature optimization, as the size of this struct is quite significant anyway (and contains 'wasted' fill bytes depending on the configuration)... In this case I actually think that there is a better gain by logically grouping the fields in this struct.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At what point becomes sensible optimization premature. I actually don't see the logical connection of state to lock or last_id (message ID) to client_id (except for the fact that they are both called ID). The only field I agree with is keepalive_retry_cnt, but if you move state below and last_id below that, you still have a logical grouping and waste-less packing ;-).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok ok

* @param[out] topic topic to initialize
* @param[in] topic_name topic name (ASCII), may be NULL if topic should use
* a pre-shared topic ID
* @param[in] topic_id pre-shared topic ID, or don't care if @p topic_name
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At other places you use "pre-defined topic ID". Is this the same concept? If yes, please change here and in the line above.

/**
* @brief Start a listener thread
*
* @note Must have higher priority then the handler thread
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since asymcute_handler_run() does not receive a priority, it would be really helpful if it would be stated here what the priority of the handler thread is. So please reference ASYMCUTE_HANDLER_PRIO here.

}
if (argc >= 4) {
ep.port = (uint16_t)atoi(argv[3]);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use sock_udp_str2ep() in net/sock/util.h instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

could, if the sock_util module would be compiling and working...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fix in #9498


static uint16_t _topic_parse_pre(const char *name, size_t len)
{
if ((len > 4) && memcmp(name, "pre_", 4) == 0) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

strncmp()


static void _topics_clear(void)
{
memset(_topics, 0, (sizeof(asymcute_topic_t) * TOPIC_BUF_NUMOF));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use sizeof(_topics) instead.

return 1;
}
if (_topic_init(t, argv[1]) != 0) {
puts("error: unable to parse topic");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since _topic_init() returns 1 when asymcute_topic_init() returns an error, this error message seems to be misleading.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not sure I understand.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just use the same output as in _cmd_reg(). An error in _topic_init() not only can mean a parsing error.

for (; i < TOPIC_BUF_NUMOF; i++) {
if (strcmp(argv[1], _topics[i].name) == 0) {
for (unsigned s = 0; s < SUB_CTX_NUMOF; s++) {
printf("cmp: %p vs %p\n", (void *)_subscriptions[i].topic, (void *)&_topics[i]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Debug output?

if (con->subscriptions == sub) {
con->subscriptions = sub->next;
}
for (asymcute_sub_t *e = con->subscriptions; e->next; e = e->next) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Getting a segmentation fault when trying to unsubscribe from a (subscribed to) topic in the example application, since e == NULL, when e->next is checked.

@haukepetersen
Copy link
Copy Markdown
Contributor Author

addressed most comments, but did not have to really test my fixes again... Will look into this first thing tomorrow morning once more.

@miri64
Copy link
Copy Markdown
Member

miri64 commented Jul 5, 2018

Both #9497 and #9498 are merged. Please rebase.

}

/* get sock ep */
sock_udp_ep_t ep = { .family = AF_INET6, .port = MQTTSN_DEFAULT_PORT};;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing space before }, double ;


/* compare family and port */
if ((a->family != b->family) || (a->port != b->port)) {
return 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

false

case AF_INET:
return (memcmp(a->addr.ipv4, b->addr.ipv4, 4) == 0);
default:
return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this returns true if a->family is unknown.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

what should it return? My take was that on Family unknown we simply ignore the address field. Maybe not the smartest thing?!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

false, otherwise it returns true for different addresses of not compiled-in family.

best would be -ENOTSUP.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

could go for: int sock_udp_ep_equal(...)
@return 0 -> equal
@return -ENOTSUP if family unknown

but what for not equal?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hm.

if this is a three-state (equal, nonequal, error), "equal" is probably best represented as zero, because otherwise people write if (sock_udp_ep_equal(a, b)) and never catch the error. In that case, renaming to "sock_udp_ep_cmp()" (as strcmp()) would be most ideomatic C, and only >0 (or 1) is left for non-equal...

Why not simply "raise AddressFamilyNotSupportedException()"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just played with it, going back to an int return value with a potential negative return value is not very practical, considering the most common use case for this function is probably something like

if (sock_udp_ep_equal(a, b)) {
....

-> I now changed the return value for the default path in the switch statement to false and added a half-sentence on this to the return value desc in the doc

miri64 added a commit to miri64/RIOT that referenced this pull request Jul 5, 2018
This helped me a lot while testing RIOT-OS#9464 to interact with the broker
using the mosquitto shell command clients.
#include "mutex.h"
#include "thread.h"
#include "net/asymcute.h"
#include "net/ipv6/addr.h"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Include of net/sock/udp.h an net/sock/util.h missing? I think in an example this is important to have.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

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

Some minor stuff left. I've tested this already and it works! :-)

{
if (argc < 3) {
printf("usage: %s <topic> <data> [QoS level]\n", argv[0]);
puts(" topic can be\n"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Alignments weird here (I don't mind having it this way, but then please be consistent and do it like this in _cmd_reg() as well.

{
if (argc < 3) {
printf("usage: %s <topic> <data> [QoS level]\n", argv[0]);
puts(" topic can be\n"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this explanation is also used in _cmd_reg and _cmd_sub, maybe have a function _topic_help() that prints that?

uint8_t keepalive_retry_cnt; /**< keep alive transmission counter */
uint8_t state; /**< connection state */
uint8_t rxbuf[ASYMCUTE_BUFSIZE]; /**< connection specific receive buf */
char cli_id[ASYMCUTE_ID_MAXLEN + 1]; /**< buffer to store client ID */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment alignment in this struct is pretty wild ;-).

/**
* @brief Reset the given topic
*
* @note Make sure that the given topic is not used by any subscription or
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit-picky: I would rather make this a @warning

static inline bool asymcute_topic_is_init(const asymcute_topic_t *topic)
{
assert(topic);
return (strlen(topic->name) > 0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can safe a few byte of ROM (on nrf52dk and samr21-xpro I've got 16 bytes, 22 bytes on iotlab-m3) and the <string.h> include by doing topic->name[0] != '\0' here.

(void)con;

/* reset the subscription context */
asymcute_sub_t *sub = (asymcute_sub_t *)req->arg;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok.

for (asymcute_sub_t *cur = con->subscriptions; cur; cur = cur->next) {
if (cur->topic->id == topic_id) {
sub = cur;
break;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

✔️

}
if ((topic_name && ((len == 0) || (len > ASYMCUTE_TOPIC_MAXLEN))) ||
(!topic_name && ((topic_id == 0) || (topic_id == UINT16_MAX)))) {
return ASYMCUTE_OVERFLOW;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still confusing, but ok. As long as it's documented.

*/

#include <errno.h>
#include <string.h>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can remove these changes now.

return NULL;
}

static int _qos_parse(int argc, char **argv, int pos, unsigned *flags)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any reason why not just hand over the argument string here instead of the whole array?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, because I 'outsource' the detection if enough parameters are passed to the shell command to this function. Else I would have to check for argc > something always before calling this function.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, okay. Missed that part

@haukepetersen
Copy link
Copy Markdown
Contributor Author

addressed comments and rebased.

*
* @author Martine Lenders <[email protected]>
* @author Martine Lenders <[email protected]>
* @author Hauke Petersen <[email protected]>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can be removed ;-).

Copy link
Copy Markdown
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

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

Apart from the minor change to gnrc_sock_udp.c I'm fine with the state of this PR. Please squash

/**
* @brief Buffer size used for emCute's transmit and receive buffers
*
* The overall buffer size used by Asymcute is this value time two (Rx + Tx).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

times two.

@haukepetersen
Copy link
Copy Markdown
Contributor Author

addressed last comments and squashed.

@miri64 miri64 added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed State: waiting for other PR State: The PR requires another PR to be merged first CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Jul 5, 2018
Copy link
Copy Markdown
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

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

ACK. Let's give Murdock a run.

return ASYMCUTE_REGERR;
}
if ((topic_name && ((len == 0) || (len > ASYMCUTE_TOPIC_MAXLEN))) ||
(!topic_name && ((topic_id == 0) || (topic_id == UINT16_MAX)))) {
Copy link
Copy Markdown
Member

@miri64 miri64 Jul 5, 2018

Choose a reason for hiding this comment

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

cppcheck reports the check for !topic_name to be redundant, and since you can, due to lazy evaluation only land here when topic_name is true in the line above, I say its right. So please remove. bullshit, let me think about it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@haukepetersen
Copy link
Copy Markdown
Contributor Author

blacklisted boards for missing memory and fixed a NULL pointer issue in asymcute_topic_init that cppcheck pointed out.

Copy link
Copy Markdown
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

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

Re-ACK


if (topic_name == NULL) {
if ((topic_id == 0) || (topic_id == UINT16_MAX)) {
return ASYMCUTE_OVERFLOW;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess now you can have a return value for that equates to invalid, but let's not split hairs over that.

@miri64 miri64 merged commit 2e717b2 into RIOT-OS:master Jul 5, 2018
@miri64 miri64 deleted the add_asymcute branch July 5, 2018 14:01
@kb2ma
Copy link
Copy Markdown
Member

kb2ma commented Jul 5, 2018

@haukepetersen, @kaspar030, @miri64

Thanks for sock_udp_ep_equals(), I can reuse that in gcoap. 👍

@cladmi cladmi added this to the Release 2018.07 milestone Jul 10, 2018
pekkanikander pushed a commit to AaltoNEPPI/RIOT that referenced this pull request Aug 11, 2018
This helped me a lot while testing RIOT-OS#9464 to interact with the broker
using the mosquitto shell command clients.
pekkanikander pushed a commit to AaltoNEPPI/RIOT that referenced this pull request Aug 28, 2018
This helped me a lot while testing RIOT-OS#9464 to interact with the broker
using the mosquitto shell command clients.
danpetry pushed a commit to danpetry/RIOT that referenced this pull request Sep 5, 2018
This helped me a lot while testing RIOT-OS#9464 to interact with the broker
using the mosquitto shell command clients.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: network Area: Networking CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants