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