net: lwm2m: Fix separate response handling

Separate response handling implemented in the engine was faulty. The
separate response was not acknowledged by the client, resulting in
spurious retransmissions from the server side.

Also, the pending CON message was retransmitted by the client even after
it was acknowledged by an empty ACK, but the respnse haven't arrived
yet. Fix this by adding a new `acknowledged` flag to the `lwm2m_message`
structure. Once acknowledged, the flag is set and the confirmable
message is no longer retransmitted. We keep the message on the pending
list in order to timeout properly in case separate response does not
arrive in time.

Finally, prevent the reply callback from being called twice in case
the response is transmitted separately from ACk. The callback should
only be called on the actual reply, not the empty ACK.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Robert Lubos 2020-10-13 14:10:55 +02:00 committed by Carles Cufí
commit 37681a7bef
2 changed files with 41 additions and 27 deletions

View file

@ -3710,14 +3710,30 @@ static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx,
tkl = coap_header_get_token(&response, token);
pending = coap_pending_received(&response, client_ctx->pendings,
CONFIG_LWM2M_ENGINE_MAX_PENDING);
/*
* Clear pending pointer because coap_pending_received() calls
* coap_pending_clear, and later when we call lwm2m_reset_message()
* it will try and call coap_pending_clear() again if msg->pending
* is != NULL.
*/
if (pending) {
if (pending && coap_header_get_type(&response) == COAP_TYPE_ACK) {
msg = find_msg(pending, NULL);
if (msg == NULL) {
LOG_DBG("Orphaned pending %p.", pending);
return;
}
msg->acknowledged = true;
if (msg->reply == NULL) {
/* No response expected, release the message. */
lwm2m_reset_message(msg, true);
return;
}
/* If the original message was a request and an empty
* ACK was received, expect separate response later.
*/
if ((msg->code >= COAP_METHOD_GET) &&
(msg->code <= COAP_METHOD_DELETE) &&
(coap_header_get_code(&response) == COAP_CODE_EMPTY)) {
LOG_DBG("Empty ACK, expect separate response.");
return;
}
}
LOG_DBG("checking for reply from [%s]",
@ -3726,28 +3742,16 @@ static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx,
client_ctx->replies,
CONFIG_LWM2M_ENGINE_MAX_REPLIES);
if (reply) {
/*
* Separate response is composed of 2 messages, empty ACK with
* no token and an additional message with a matching token id
* (based on the token used by the CON request).
*
* Since the ACK received by the notify CON messages are also
* empty with no token (consequence of always using the same
* token id for all notifications), we have to use an
* additional flag to decide when to clear the reply callback.
*/
if (client_ctx->handle_separate_response && !tkl &&
coap_header_get_type(&response) == COAP_TYPE_ACK) {
LOG_DBG("separated response, not removing reply");
return;
msg = find_msg(NULL, reply);
if (coap_header_get_type(&response) == COAP_TYPE_CON) {
r = lwm2m_send_empty_ack(client_ctx,
coap_header_get_id(&response));
if (r < 0) {
LOG_ERR("Error transmitting ACK");
}
}
if (!msg) {
msg = find_msg(pending, reply);
}
}
if (reply || pending) {
/* skip release if reply->user_data has error condition */
if (reply && reply->user_data != COAP_REPLY_STATUS_NONE) {
/* reset reply->user_data for next time */
@ -3845,6 +3849,13 @@ static void retransmit_request(struct k_work *work)
goto next;
}
if (msg->acknowledged) {
/* No need to retransmit, just keep the timer running to
* timeout in case no response arrives.
*/
goto next;
}
LOG_INF("Resending message: %p", msg);
msg->send_attempts++;