net: add Asymcute, an asynchronous MQTT-SN client implementation#9464
net: add Asymcute, an asynchronous MQTT-SN client implementation#9464miri64 merged 5 commits intoRIOT-OS:masterfrom
Conversation
|
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: |
|
I wait with my proper review until #9459 is merged, but please fix the typo in a33f95c ( |
But to underline the beauty of the 'standard': Section 6.6 also states So with the two implementations now available, we are complying to either statement and have it all covered :-) |
|
fixed the PR title... |
|
Needs rebase. |
a07124b to
08f04a2
Compare
|
rebased (and fixed typo in commit title) |
examples/asymcute_mqttsn/README.md
Outdated
| ``` | ||
|
|
||
| This will download, build, and run the Eclipse Mosquitto.rsmb `Really Small | ||
| Message Broker` [(found here)](https://github.com/eclipse/mosquitto.rsmb). |
There was a problem hiding this comment.
Nit-picky: Any reason for the monospace format for "Really Small Message Broker"?
examples/asymcute_mqttsn/main.c
Outdated
| #include "net/ipv6/addr.h" | ||
|
|
||
| #ifndef REG_CTX_NUMOF | ||
| #define REG_CTX_NUMOF (8U) |
sys/include/net/asymcute.h
Outdated
| * @brief Asymcute connection context | ||
| */ | ||
| struct asymcute_con { | ||
| uint8_t state; /**< connection state */ |
There was a problem hiding this comment.
Can you group this with last_id and keepalive_retry_cnt please, to save some RAM by packing the struct?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ;-).
sys/include/net/asymcute.h
Outdated
| * @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 |
There was a problem hiding this comment.
At other places you use "pre-defined topic ID". Is this the same concept? If yes, please change here and in the line above.
sys/include/net/asymcute.h
Outdated
| /** | ||
| * @brief Start a listener thread | ||
| * | ||
| * @note Must have higher priority then the handler thread |
There was a problem hiding this comment.
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]); | ||
| } |
There was a problem hiding this comment.
Use sock_udp_str2ep() in net/sock/util.h instead?
There was a problem hiding this comment.
could, if the sock_util module would be compiling and working...
examples/asymcute_mqttsn/main.c
Outdated
|
|
||
| static uint16_t _topic_parse_pre(const char *name, size_t len) | ||
| { | ||
| if ((len > 4) && memcmp(name, "pre_", 4) == 0) { |
examples/asymcute_mqttsn/main.c
Outdated
|
|
||
| static void _topics_clear(void) | ||
| { | ||
| memset(_topics, 0, (sizeof(asymcute_topic_t) * TOPIC_BUF_NUMOF)); |
examples/asymcute_mqttsn/main.c
Outdated
| return 1; | ||
| } | ||
| if (_topic_init(t, argv[1]) != 0) { | ||
| puts("error: unable to parse topic"); |
There was a problem hiding this comment.
Since _topic_init() returns 1 when asymcute_topic_init() returns an error, this error message seems to be misleading.
There was a problem hiding this comment.
not sure I understand.
There was a problem hiding this comment.
Just use the same output as in _cmd_reg(). An error in _topic_init() not only can mean a parsing error.
examples/asymcute_mqttsn/main.c
Outdated
| 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]); |
| if (con->subscriptions == sub) { | ||
| con->subscriptions = sub->next; | ||
| } | ||
| for (asymcute_sub_t *e = con->subscriptions; e->next; e = e->next) { |
There was a problem hiding this comment.
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.
08f04a2 to
4b9e9fe
Compare
|
addressed most comments, but did not have to really test my fixes again... Will look into this first thing tomorrow morning once more. |
18e8c8a to
e76a812
Compare
examples/asymcute_mqttsn/main.c
Outdated
| } | ||
|
|
||
| /* get sock ep */ | ||
| sock_udp_ep_t ep = { .family = AF_INET6, .port = MQTTSN_DEFAULT_PORT};; |
sys/net/sock/sock_util.c
Outdated
|
|
||
| /* compare family and port */ | ||
| if ((a->family != b->family) || (a->port != b->port)) { | ||
| return 0; |
sys/net/sock/sock_util.c
Outdated
| case AF_INET: | ||
| return (memcmp(a->addr.ipv4, b->addr.ipv4, 4) == 0); | ||
| default: | ||
| return true; |
There was a problem hiding this comment.
this returns true if a->family is unknown.
There was a problem hiding this comment.
what should it return? My take was that on Family unknown we simply ignore the address field. Maybe not the smartest thing?!
There was a problem hiding this comment.
false, otherwise it returns true for different addresses of not compiled-in family.
best would be -ENOTSUP.
There was a problem hiding this comment.
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()"?
There was a problem hiding this comment.
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
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" |
There was a problem hiding this comment.
Include of net/sock/udp.h an net/sock/util.h missing? I think in an example this is important to have.
miri64
left a comment
There was a problem hiding this comment.
Some minor stuff left. I've tested this already and it works! :-)
examples/asymcute_mqttsn/main.c
Outdated
| { | ||
| if (argc < 3) { | ||
| printf("usage: %s <topic> <data> [QoS level]\n", argv[0]); | ||
| puts(" topic can be\n" |
There was a problem hiding this comment.
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.
examples/asymcute_mqttsn/main.c
Outdated
| { | ||
| if (argc < 3) { | ||
| printf("usage: %s <topic> <data> [QoS level]\n", argv[0]); | ||
| puts(" topic can be\n" |
There was a problem hiding this comment.
Since this explanation is also used in _cmd_reg and _cmd_sub, maybe have a function _topic_help() that prints that?
sys/include/net/asymcute.h
Outdated
| 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 */ |
There was a problem hiding this comment.
Comment alignment in this struct is pretty wild ;-).
sys/include/net/asymcute.h
Outdated
| /** | ||
| * @brief Reset the given topic | ||
| * | ||
| * @note Make sure that the given topic is not used by any subscription or |
There was a problem hiding this comment.
Nit-picky: I would rather make this a @warning
sys/include/net/asymcute.h
Outdated
| static inline bool asymcute_topic_is_init(const asymcute_topic_t *topic) | ||
| { | ||
| assert(topic); | ||
| return (strlen(topic->name) > 0); |
There was a problem hiding this comment.
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; |
| for (asymcute_sub_t *cur = con->subscriptions; cur; cur = cur->next) { | ||
| if (cur->topic->id == topic_id) { | ||
| sub = cur; | ||
| break; |
| } | ||
| if ((topic_name && ((len == 0) || (len > ASYMCUTE_TOPIC_MAXLEN))) || | ||
| (!topic_name && ((topic_id == 0) || (topic_id == UINT16_MAX)))) { | ||
| return ASYMCUTE_OVERFLOW; |
There was a problem hiding this comment.
Still confusing, but ok. As long as it's documented.
| */ | ||
|
|
||
| #include <errno.h> | ||
| #include <string.h> |
There was a problem hiding this comment.
You can remove these changes now.
| return NULL; | ||
| } | ||
|
|
||
| static int _qos_parse(int argc, char **argv, int pos, unsigned *flags) |
There was a problem hiding this comment.
Any reason why not just hand over the argument string here instead of the whole array?
There was a problem hiding this comment.
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.
33f96ed to
b67ecc5
Compare
|
addressed comments and rebased. |
| * | ||
| * @author Martine Lenders <[email protected]> | ||
| * @author Martine Lenders <[email protected]> | ||
| * @author Hauke Petersen <[email protected]> |
miri64
left a comment
There was a problem hiding this comment.
Apart from the minor change to gnrc_sock_udp.c I'm fine with the state of this PR. Please squash
sys/include/net/asymcute.h
Outdated
| /** | ||
| * @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). |
b67ecc5 to
d1b4194
Compare
|
addressed last comments and squashed. |
miri64
left a comment
There was a problem hiding this comment.
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)))) { |
There was a problem hiding this comment.
bullshit, let me think about it.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.
There was a problem hiding this comment.
Ah, it's your usage of strlen(topic_name) in https://github.com/RIOT-OS/RIOT/pull/9464/files#diff-640646a2b9d2e083e6a60251fdfd4d99R652.
d1b4194 to
901ec82
Compare
|
blacklisted boards for missing memory and fixed a NULL pointer issue in |
|
|
||
| if (topic_name == NULL) { | ||
| if ((topic_id == 0) || (topic_id == UINT16_MAX)) { | ||
| return ASYMCUTE_OVERFLOW; |
There was a problem hiding this comment.
I guess now you can have a return value for that equates to invalid, but let's not split hairs over that.
|
@haukepetersen, @kaspar030, @miri64 Thanks for sock_udp_ep_equals(), I can reuse that in gcoap. 👍 |
This helped me a lot while testing RIOT-OS#9464 to interact with the broker using the mosquitto shell command clients.
This helped me a lot while testing RIOT-OS#9464 to interact with the broker using the mosquitto shell command clients.
This helped me a lot while testing RIOT-OS#9464 to interact with the broker using the mosquitto shell command clients.
Contribution description
This PR adds a new
MQTT-SNclient implementation calledAsymcute. In contrary to the exisingemCuteimplementation, 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).Asymcuteis 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
emCutelibrary was not well suited, so hence the need for a fully asynchronous MQTT-SN client library.Testing
Easiest way (using native):
rsmbmake target (see tools: add mosquitto.rsmb MQTT-SN broker #9459)examples/asymcute_mqttsnapplication undernativekill -9) thersmbinstance 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