1 /*
2 * Copyright (c) 2017 Linaro Limited
3 * Copyright (c) 2018-2019 Foundries.io
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define LOG_MODULE_NAME net_lwm2m_obj_firmware_pull
9 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
10
11 #include <logging/log.h>
12 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
13
14 #include <ctype.h>
15 #include <stdio.h>
16 #include <string.h>
17
18 #include <net/http_parser.h>
19 #include <net/socket.h>
20
21 #include "lwm2m_object.h"
22 #include "lwm2m_engine.h"
23
24 #define URI_LEN 255
25
26 #define NETWORK_INIT_TIMEOUT K_SECONDS(10)
27 #define NETWORK_CONNECT_TIMEOUT K_SECONDS(10)
28 #define PACKET_TRANSFER_RETRY_MAX 3
29
30 static char firmware_uri[URI_LEN];
31 static struct lwm2m_ctx firmware_ctx = {
32 .sock_fd = -1
33 };
34 static int firmware_retry;
35 static struct coap_block_context firmware_block_ctx;
36
37 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
38 #define COAP2COAP_PROXY_URI_PATH "coap2coap"
39 #define COAP2HTTP_PROXY_URI_PATH "coap2http"
40
41 static char proxy_uri[URI_LEN];
42 #endif
43
44 static void do_transmit_timeout_cb(struct lwm2m_message *msg);
45
set_update_result_from_error(int error_code)46 static void set_update_result_from_error(int error_code)
47 {
48 if (error_code == -ENOMEM) {
49 lwm2m_firmware_set_update_result(RESULT_OUT_OF_MEM);
50 } else if (error_code == -ENOSPC) {
51 lwm2m_firmware_set_update_result(RESULT_NO_STORAGE);
52 } else if (error_code == -EFAULT) {
53 lwm2m_firmware_set_update_result(RESULT_INTEGRITY_FAILED);
54 } else if (error_code == -ENOMSG) {
55 lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST);
56 } else if (error_code == -ENOTSUP) {
57 lwm2m_firmware_set_update_result(RESULT_INVALID_URI);
58 } else if (error_code == -EPROTONOSUPPORT) {
59 lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO);
60 } else {
61 lwm2m_firmware_set_update_result(RESULT_UPDATE_FAILED);
62 }
63 }
64
transfer_request(struct coap_block_context * ctx,uint8_t * token,uint8_t tkl,coap_reply_t reply_cb)65 static int transfer_request(struct coap_block_context *ctx,
66 uint8_t *token, uint8_t tkl,
67 coap_reply_t reply_cb)
68 {
69 struct lwm2m_message *msg;
70 int ret;
71 char *cursor;
72 #if !defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
73 struct http_parser_url parser;
74 uint16_t off, len;
75 char *next_slash;
76 #endif
77
78 msg = lwm2m_get_message(&firmware_ctx);
79 if (!msg) {
80 LOG_ERR("Unable to get a lwm2m message!");
81 return -ENOMEM;
82 }
83
84 msg->type = COAP_TYPE_CON;
85 msg->code = COAP_METHOD_GET;
86 msg->mid = coap_next_id();
87 msg->token = token;
88 msg->tkl = tkl;
89 msg->reply_cb = reply_cb;
90 msg->message_timeout_cb = do_transmit_timeout_cb;
91
92 ret = lwm2m_init_message(msg);
93 if (ret < 0) {
94 LOG_ERR("Error setting up lwm2m message");
95 goto cleanup;
96 }
97
98 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
99 /* TODO: shift to lower case */
100 if (strncmp(firmware_uri, "http", 4) == 0) {
101 cursor = COAP2HTTP_PROXY_URI_PATH;
102 } else if (strncmp(firmware_uri, "coap", 4) == 0) {
103 cursor = COAP2COAP_PROXY_URI_PATH;
104 } else {
105 ret = -EPROTONOSUPPORT;
106 LOG_ERR("Unsupported schema");
107 goto cleanup;
108 }
109
110 ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH,
111 cursor, strlen(cursor));
112 if (ret < 0) {
113 LOG_ERR("Error adding URI_PATH '%s'", log_strdup(cursor));
114 goto cleanup;
115 }
116 #else
117 http_parser_url_init(&parser);
118 ret = http_parser_parse_url(firmware_uri, strlen(firmware_uri), 0,
119 &parser);
120 if (ret < 0) {
121 LOG_ERR("Invalid firmware url: %s", log_strdup(firmware_uri));
122 ret = -ENOTSUP;
123 goto cleanup;
124 }
125
126 /* if path is not available, off/len will be zero */
127 off = parser.field_data[UF_PATH].off;
128 len = parser.field_data[UF_PATH].len;
129 cursor = firmware_uri + off;
130
131 /* add path portions (separated by slashes) */
132 while (len > 0 && (next_slash = strchr(cursor, '/')) != NULL) {
133 if (next_slash != cursor) {
134 ret = coap_packet_append_option(&msg->cpkt,
135 COAP_OPTION_URI_PATH,
136 cursor,
137 next_slash - cursor);
138 if (ret < 0) {
139 LOG_ERR("Error adding URI_PATH");
140 goto cleanup;
141 }
142 }
143
144 /* skip slash */
145 len -= (next_slash - cursor) + 1;
146 cursor = next_slash + 1;
147 }
148
149 if (len > 0) {
150 /* flush the rest */
151 ret = coap_packet_append_option(&msg->cpkt,
152 COAP_OPTION_URI_PATH,
153 cursor, len);
154 if (ret < 0) {
155 LOG_ERR("Error adding URI_PATH");
156 goto cleanup;
157 }
158 }
159 #endif
160
161 ret = coap_append_block2_option(&msg->cpkt, ctx);
162 if (ret < 0) {
163 LOG_ERR("Unable to add block2 option.");
164 goto cleanup;
165 }
166
167 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
168 ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_PROXY_URI,
169 firmware_uri, strlen(firmware_uri));
170 if (ret < 0) {
171 LOG_ERR("Error adding PROXY_URI '%s'",
172 log_strdup(firmware_uri));
173 goto cleanup;
174 }
175 #else
176 /* Ask the server to provide a size estimate */
177 ret = coap_append_option_int(&msg->cpkt, COAP_OPTION_SIZE2, 0);
178 if (ret < 0) {
179 LOG_ERR("Unable to add size2 option.");
180 goto cleanup;
181 }
182 #endif
183
184 /* send request */
185 ret = lwm2m_send_message_async(msg);
186 if (ret < 0) {
187 LOG_ERR("Error sending LWM2M packet (err:%d).", ret);
188 goto cleanup;
189 }
190
191 return 0;
192
193 cleanup:
194 lwm2m_reset_message(msg, true);
195 return ret;
196 }
197
198 static int
do_firmware_transfer_reply_cb(const struct coap_packet * response,struct coap_reply * reply,const struct sockaddr * from)199 do_firmware_transfer_reply_cb(const struct coap_packet *response,
200 struct coap_reply *reply,
201 const struct sockaddr *from)
202 {
203 int ret;
204 bool last_block;
205 uint8_t token[8];
206 uint8_t tkl;
207 uint16_t payload_len, payload_offset, len;
208 struct coap_packet *check_response = (struct coap_packet *)response;
209 struct lwm2m_engine_res *res = NULL;
210 lwm2m_engine_set_data_cb_t write_cb;
211 size_t write_buflen;
212 uint8_t resp_code, *write_buf;
213 struct coap_block_context received_block_ctx;
214 const uint8_t *payload_start;
215
216 /* token is used to determine a valid ACK vs a separated response */
217 tkl = coap_header_get_token(check_response, token);
218
219 /* If separated response (ACK) return and wait for response */
220 if (!tkl && coap_header_get_type(response) == COAP_TYPE_ACK) {
221 return 0;
222 } else if (coap_header_get_type(response) == COAP_TYPE_CON) {
223 /* Send back ACK so the server knows we received the pkt */
224 ret = lwm2m_send_empty_ack(&firmware_ctx,
225 coap_header_get_id(check_response));
226 if (ret < 0) {
227 LOG_ERR("Error transmitting ACK");
228 goto error;
229 }
230 }
231
232 /* Check response code from server. Expecting (2.05) */
233 resp_code = coap_header_get_code(check_response);
234 if (resp_code != COAP_RESPONSE_CODE_CONTENT) {
235 LOG_ERR("Unexpected response from server: %d.%d",
236 COAP_RESPONSE_CODE_CLASS(resp_code),
237 COAP_RESPONSE_CODE_DETAIL(resp_code));
238 ret = -ENOMSG;
239 goto error;
240 }
241
242 /* save main firmware block context */
243 memcpy(&received_block_ctx, &firmware_block_ctx,
244 sizeof(firmware_block_ctx));
245
246 ret = coap_update_from_block(check_response, &firmware_block_ctx);
247 if (ret < 0) {
248 LOG_ERR("Error from block update: %d", ret);
249 ret = -EFAULT;
250 goto error;
251 }
252
253 /* test for duplicate transfer */
254 if (firmware_block_ctx.current < received_block_ctx.current) {
255 LOG_WRN("Duplicate packet ignored");
256
257 /* restore main firmware block context */
258 memcpy(&firmware_block_ctx, &received_block_ctx,
259 sizeof(firmware_block_ctx));
260
261 /* set reply->user_data to error to avoid releasing */
262 reply->user_data = (void *)COAP_REPLY_STATUS_ERROR;
263 return 0;
264 }
265
266 /* Reach last block if ret equals to 0 */
267 last_block = !coap_next_block(check_response, &firmware_block_ctx);
268
269 /* Process incoming data */
270 payload_start = coap_packet_get_payload(response, &payload_len);
271 if (payload_len > 0) {
272 payload_offset = payload_start - response->data;
273 LOG_DBG("total: %zd, current: %zd",
274 firmware_block_ctx.total_size,
275 firmware_block_ctx.current);
276
277 /* look up firmware package resource */
278 ret = lwm2m_engine_get_resource("5/0/0", &res);
279 if (ret < 0) {
280 goto error;
281 }
282
283 /* get buffer data */
284 write_buf = res->res_instances->data_ptr;
285 write_buflen = res->res_instances->max_data_len;
286
287 /* check for user override to buffer */
288 if (res->pre_write_cb) {
289 write_buf = res->pre_write_cb(0, 0, 0, &write_buflen);
290 }
291
292 write_cb = lwm2m_firmware_get_write_cb();
293 if (write_cb) {
294 /* flush incoming data to write_cb */
295 while (payload_len > 0) {
296 len = (payload_len > write_buflen) ?
297 write_buflen : payload_len;
298 payload_len -= len;
299 /* check for end of packet */
300 if (buf_read(write_buf, len,
301 CPKT_BUF_READ(response),
302 &payload_offset) < 0) {
303 /* malformed packet */
304 ret = -EFAULT;
305 goto error;
306 }
307
308 ret = write_cb(0, 0, 0,
309 write_buf, len,
310 last_block &&
311 (payload_len == 0U),
312 firmware_block_ctx.total_size);
313 if (ret < 0) {
314 goto error;
315 }
316 }
317 }
318 }
319
320 if (!last_block) {
321 /* More block(s) to come, setup next transfer */
322 ret = transfer_request(&firmware_block_ctx, token, tkl,
323 do_firmware_transfer_reply_cb);
324 if (ret < 0) {
325 goto error;
326 }
327 } else {
328 /* Download finished */
329 lwm2m_firmware_set_update_state(STATE_DOWNLOADED);
330 lwm2m_engine_context_close(&firmware_ctx);
331 }
332
333 return 0;
334
335 error:
336 set_update_result_from_error(ret);
337 lwm2m_engine_context_close(&firmware_ctx);
338 return ret;
339 }
340
do_transmit_timeout_cb(struct lwm2m_message * msg)341 static void do_transmit_timeout_cb(struct lwm2m_message *msg)
342 {
343 int ret;
344
345 if (firmware_retry < PACKET_TRANSFER_RETRY_MAX) {
346 /* retry block */
347 LOG_WRN("TIMEOUT - Sending a retry packet!");
348
349 ret = transfer_request(&firmware_block_ctx,
350 msg->token, msg->tkl,
351 do_firmware_transfer_reply_cb);
352 if (ret < 0) {
353 /* abort retries / transfer */
354 set_update_result_from_error(ret);
355 firmware_retry = PACKET_TRANSFER_RETRY_MAX;
356 lwm2m_engine_context_close(&firmware_ctx);
357 return;
358 }
359
360 firmware_retry++;
361 } else {
362 LOG_ERR("TIMEOUT - Too many retry packet attempts! "
363 "Aborting firmware download.");
364 lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST);
365 lwm2m_engine_context_close(&firmware_ctx);
366 }
367 }
368
firmware_transfer(void)369 static void firmware_transfer(void)
370 {
371 int ret;
372 char *server_addr;
373
374 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
375 server_addr = CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_ADDR;
376 if (strlen(server_addr) >= URI_LEN) {
377 LOG_ERR("Invalid Proxy URI: %s", log_strdup(server_addr));
378 ret = -ENOTSUP;
379 goto error;
380 }
381
382 /* Copy required as it gets modified when port is available */
383 strcpy(proxy_uri, server_addr);
384 server_addr = proxy_uri;
385 #else
386 server_addr = firmware_uri;
387 #endif
388
389 ret = lwm2m_parse_peerinfo(server_addr, &firmware_ctx.remote_addr,
390 &firmware_ctx.use_dtls);
391 if (ret < 0) {
392 LOG_ERR("Failed to parse server URI.");
393 goto error;
394 }
395
396 lwm2m_engine_context_init(&firmware_ctx);
397 ret = lwm2m_socket_start(&firmware_ctx);
398 if (ret < 0) {
399 LOG_ERR("Cannot start a firmware-pull connection:%d", ret);
400 goto error;
401 }
402
403 LOG_INF("Connecting to server %s", log_strdup(firmware_uri));
404
405 /* reset block transfer context */
406 coap_block_transfer_init(&firmware_block_ctx,
407 lwm2m_default_block_size(), 0);
408 ret = transfer_request(&firmware_block_ctx, coap_next_token(), 8,
409 do_firmware_transfer_reply_cb);
410 if (ret < 0) {
411 goto error;
412 }
413
414 return;
415
416 error:
417 set_update_result_from_error(ret);
418 lwm2m_engine_context_close(&firmware_ctx);
419 }
420
socket_fault_cb(int error)421 static void socket_fault_cb(int error)
422 {
423 int ret;
424
425 LOG_ERR("FW update socket error: %d", error);
426
427 lwm2m_engine_context_close(&firmware_ctx);
428
429 /* Reopen the socket and retransmit the last request. */
430 lwm2m_engine_context_init(&firmware_ctx);
431 ret = lwm2m_socket_start(&firmware_ctx);
432 if (ret < 0) {
433 LOG_ERR("Failed to start a firmware-pull connection: %d", ret);
434 goto error;
435 }
436
437 ret = transfer_request(&firmware_block_ctx,
438 NULL, LWM2M_MSG_TOKEN_GENERATE_NEW,
439 do_firmware_transfer_reply_cb);
440 if (ret < 0) {
441 LOG_ERR("Failed to send a retry packet: %d", ret);
442 goto error;
443 }
444
445 return;
446
447 error:
448 /* Abort retries. */
449 firmware_retry = PACKET_TRANSFER_RETRY_MAX;
450 set_update_result_from_error(ret);
451 lwm2m_engine_context_close(&firmware_ctx);
452 }
453
454 /* TODO: */
lwm2m_firmware_cancel_transfer(void)455 int lwm2m_firmware_cancel_transfer(void)
456 {
457 return 0;
458 }
459
lwm2m_firmware_start_transfer(char * package_uri)460 int lwm2m_firmware_start_transfer(char *package_uri)
461 {
462 /* close old socket */
463 if (firmware_ctx.sock_fd > -1) {
464 lwm2m_engine_context_close(&firmware_ctx);
465 }
466
467 (void)memset(&firmware_ctx, 0, sizeof(struct lwm2m_ctx));
468 firmware_ctx.sock_fd = -1;
469 firmware_ctx.fault_cb = socket_fault_cb;
470 firmware_retry = 0;
471 lwm2m_firmware_set_update_state(STATE_DOWNLOADING);
472
473 /* start file transfer */
474 strncpy(firmware_uri, package_uri, URI_LEN - 1);
475 firmware_transfer();
476
477 return 0;
478 }
479
480 /**
481 * @brief Get the block context of the current firmware block.
482 *
483 * @return A pointer to the firmware block context
484 */
lwm2m_firmware_get_block_context()485 struct coap_block_context *lwm2m_firmware_get_block_context()
486 {
487 return &firmware_block_ctx;
488 }
489