Skip to content

Commit 9de07f5

Browse files
andreas-chmielewski-gcxcarlescufi
authored andcommitted
net: lib: download_client: implement CoAP backoff mechanism
Use CoAP backoff mechanism to set CoAP timeouts for recv/send according to CoAP specification. Signed-off-by: Andreas Chmielewski <[email protected]>
1 parent b676d4b commit 9de07f5

File tree

4 files changed

+125
-20
lines changed

4 files changed

+125
-20
lines changed

include/net/download_client.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ struct download_client {
162162
struct {
163163
/** CoAP block context. */
164164
struct coap_block_context block_ctx;
165+
166+
/** CoAP pending object. */
167+
struct coap_pending pending;
165168
} coap;
166169

167170
/** Internal thread ID. */

subsys/net/lib/download_client/Kconfig

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,14 @@ config DOWNLOAD_CLIENT_TCP_SOCK_TIMEO_MS
116116
when the server is not responding and client should give up.
117117
Set to -1 disable.
118118

119-
config DOWNLOAD_CLIENT_UDP_SOCK_TIMEO_MS
120-
int "Receive timeout on UDP sockets, in milliseconds"
121-
default 4000
122-
range -1 30000
119+
config DOWNLOAD_CLIENT_COAP_MAX_RETRANSMIT_REQUEST_COUNT
120+
int "Number of CoAP request retransmissions"
121+
default 4
122+
range 1 10
123123
help
124-
Socket timeout for recv() calls, in milliseconds.
125-
When using CoAP, set a timeout to be able to detect
126-
when a retrasmission is necessary.
127-
Set to -1 disable.
124+
As part of CoAP exponential backoff mechanism this is the number
125+
of retransmissions of a request. If the retransmissions exceeds,
126+
the download will be stopped.
128127

129128
config DOWNLOAD_CLIENT_RANGE_REQUESTS
130129
bool "Always use HTTP Range requests"

subsys/net/lib/download_client/src/coap.c

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ static int coap_get_current_from_response_pkt(const struct coap_packet *cpkt)
3636
return GET_BLOCK_NUM(block) << (GET_BLOCK_SIZE(block) + 4);
3737
}
3838

39+
static bool has_pending(struct download_client *client)
40+
{
41+
return client->coap.pending.timeout > 0;
42+
}
43+
3944
int coap_block_init(struct download_client *client, size_t from)
4045
{
4146
coap_block_transfer_init(&client->coap.block_ctx,
@@ -44,8 +49,47 @@ int coap_block_init(struct download_client *client, size_t from)
4449
return 0;
4550
}
4651

47-
int coap_block_update(struct download_client *client, struct coap_packet *pkt,
48-
size_t *blk_off)
52+
int coap_get_recv_timeout(struct download_client *dl)
53+
{
54+
int timeout;
55+
56+
if (!has_pending(dl)) {
57+
LOG_ERR("Must have coap pending");
58+
return -1;
59+
}
60+
61+
/* Retransmission is cycled in case recv() times out. In case sending request
62+
* blocks, the time that is used for sending request must be substracted next time
63+
* recv() is called.
64+
*/
65+
timeout = dl->coap.pending.t0 + dl->coap.pending.timeout - k_uptime_get_32();
66+
if (timeout < 0) {
67+
/* All time is spent when sending request and time this
68+
* method is called, there is no time left for receiving;
69+
* skip over recv() and initiate retransmission on next
70+
* cycle
71+
*/
72+
return 0;
73+
}
74+
75+
return timeout;
76+
}
77+
78+
int coap_initiate_retransmission(struct download_client *dl)
79+
{
80+
if (dl->coap.pending.timeout == 0) {
81+
return -EINVAL;
82+
}
83+
84+
if (!coap_pending_cycle(&dl->coap.pending)) {
85+
LOG_ERR("CoAP max-retransmissions exceeded");
86+
return -1;
87+
}
88+
89+
return 0;
90+
}
91+
92+
int coap_block_update(struct download_client *client, struct coap_packet *pkt, size_t *blk_off)
4993
{
5094
int err, new_current;
5195
bool more;
@@ -100,17 +144,33 @@ int coap_parse(struct download_client *client, size_t len)
100144
const uint8_t *payload;
101145
struct coap_packet response;
102146

147+
/* TODO: currently we stop download on every error, but this is mostly not necessary
148+
* and we can just request the same block again using retry mechanism
149+
*/
150+
103151
err = coap_packet_parse(&response, client->buf, len, NULL, 0);
104152
if (err) {
105153
LOG_ERR("Failed to parse CoAP packet, err %d", err);
106154
return -1;
107155
}
108156

157+
coap_pending_clear(&client->coap.pending);
158+
109159
err = coap_block_update(client, &response, &blk_off);
110160
if (err) {
111161
return err;
112162
}
113163

164+
if (coap_header_get_id(&response) != client->coap.pending.id) {
165+
LOG_ERR("Response is not pending");
166+
return -1;
167+
}
168+
169+
if (coap_header_get_type(&response) != COAP_TYPE_ACK) {
170+
LOG_ERR("Response must be of coap type ACK");
171+
return -1;
172+
}
173+
114174
response_code = coap_header_get_code(&response);
115175
if (response_code != COAP_RESPONSE_CODE_OK &&
116176
response_code != COAP_RESPONSE_CODE_CONTENT) {
@@ -143,23 +203,28 @@ int coap_parse(struct download_client *client, size_t len)
143203
int coap_request_send(struct download_client *client)
144204
{
145205
int err;
206+
uint16_t id;
146207
char file[FILENAME_SIZE];
147208
char *path_elem;
148209
char *path_elem_saveptr;
149210
struct coap_packet request;
150211

151-
err = coap_packet_init(
152-
&request, client->buf, CONFIG_DOWNLOAD_CLIENT_BUF_SIZE,
153-
COAP_VER, COAP_TYPE_CON, 8, coap_next_token(),
154-
COAP_METHOD_GET, coap_next_id()
155-
);
212+
if (has_pending(client)) {
213+
id = client->coap.pending.id;
214+
} else {
215+
id = coap_next_id();
216+
}
217+
218+
err = coap_packet_init(&request, client->buf, CONFIG_DOWNLOAD_CLIENT_BUF_SIZE, COAP_VER,
219+
COAP_TYPE_CON, 8, coap_next_token(), COAP_METHOD_GET, id);
156220
if (err) {
157221
LOG_ERR("Failed to init CoAP message, err %d", err);
158222
return err;
159223
}
160224

161225
err = url_parse_file(client->file, file, sizeof(file));
162226
if (err) {
227+
LOG_ERR("Unable to parse url");
163228
return err;
164229
}
165230

@@ -185,9 +250,19 @@ int coap_request_send(struct download_client *client)
185250
return err;
186251
}
187252

253+
if (!has_pending(client)) {
254+
err = coap_pending_init(&client->coap.pending, &request, &client->remote_addr,
255+
CONFIG_DOWNLOAD_CLIENT_COAP_MAX_RETRANSMIT_REQUEST_COUNT);
256+
if (err < 0) {
257+
return -EINVAL;
258+
}
259+
260+
coap_pending_cycle(&client->coap.pending);
261+
}
262+
188263
LOG_DBG("CoAP next block: %d", client->coap.block_ctx.current);
189264

190-
err = socket_send(client, request.offset, 0);
265+
err = socket_send(client, request.offset, client->coap.pending.timeout);
191266
if (err) {
192267
LOG_ERR("Failed to send CoAP request, errno %d", errno);
193268
return err;

subsys/net/lib/download_client/src/download_client.c

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ int http_parse(struct download_client *client, size_t len);
3636
int http_get_request_send(struct download_client *client);
3737

3838
int coap_block_init(struct download_client *client, size_t from);
39+
int coap_get_recv_timeout(struct download_client *dl);
40+
int coap_initiate_retransmission(struct download_client *dl);
3941
int coap_parse(struct download_client *client, size_t len);
4042
int coap_request_send(struct download_client *client);
4143

@@ -425,7 +427,11 @@ static size_t socket_recv(struct download_client *dl)
425427
case IPPROTO_UDP:
426428
case IPPROTO_DTLS_1_2:
427429
if (IS_ENABLED(CONFIG_COAP)) {
428-
timeout = CONFIG_DOWNLOAD_CLIENT_UDP_SOCK_TIMEO_MS;
430+
timeout = coap_get_recv_timeout(dl);
431+
if (timeout <= 0) {
432+
errno = ETIMEDOUT;
433+
return -1;
434+
}
429435
break;
430436
}
431437
default:
@@ -441,6 +447,27 @@ static size_t socket_recv(struct download_client *dl)
441447
return recv(dl->fd, dl->buf + dl->offset, sizeof(dl->buf) - dl->offset, 0);
442448
}
443449

450+
static int request_resend(struct download_client *dl)
451+
{
452+
int rc;
453+
454+
if (dl->proto == IPPROTO_UDP || dl->proto == IPPROTO_DTLS_1_2) {
455+
LOG_DBG("Socket timeout, resending");
456+
457+
if (IS_ENABLED(CONFIG_COAP)) {
458+
rc = coap_initiate_retransmission(dl);
459+
if (rc) {
460+
error_evt_send(dl, errno);
461+
return -1;
462+
}
463+
}
464+
465+
return 1;
466+
}
467+
468+
return 0;
469+
}
470+
444471
void download_thread(void *client, void *a, void *b)
445472
{
446473
int rc = 0;
@@ -487,9 +514,10 @@ void download_thread(void *client, void *a, void *b)
487514
if (len == -1) {
488515
if ((errno == ETIMEDOUT) || (errno == EWOULDBLOCK) ||
489516
(errno == EAGAIN)) {
490-
if (dl->proto == IPPROTO_UDP ||
491-
dl->proto == IPPROTO_DTLS_1_2) {
492-
LOG_DBG("Socket timeout, resending");
517+
rc = request_resend(dl);
518+
if (rc == -1) {
519+
break;
520+
} else if (rc == 1) {
493521
goto send_again;
494522
}
495523
error_cause = ETIMEDOUT;

0 commit comments

Comments
 (0)