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_pull_context
9 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
10 
11 #include <zephyr/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 <zephyr/net/http/parser.h>
19 #include <zephyr/net/socket.h>
20 
21 #include "lwm2m_pull_context.h"
22 #include "lwm2m_engine.h"
23 #include "lwm2m_util.h"
24 
25 static K_SEM_DEFINE(lwm2m_pull_sem, 1, 1);
26 
27 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
28 #define COAP2COAP_PROXY_URI_PATH "coap2coap"
29 #define COAP2HTTP_PROXY_URI_PATH "coap2http"
30 
31 static char proxy_uri[LWM2M_PACKAGE_URI_LEN];
32 #endif
33 
34 static void do_transmit_timeout_cb(struct lwm2m_message *msg);
35 
36 static struct firmware_pull_context {
37 	uint8_t obj_inst_id;
38 	char uri[LWM2M_PACKAGE_URI_LEN];
39 	bool is_firmware_uri;
40 	void (*result_cb)(uint16_t obj_inst_id, int error_code);
41 	lwm2m_engine_set_data_cb_t write_cb;
42 
43 	struct lwm2m_ctx firmware_ctx;
44 	struct coap_block_context block_ctx;
45 } context;
46 
47 static enum service_state {
48 	NOT_STARTED,
49 	IDLE,
50 	STOPPING,
51 } pull_service_state;
52 
pull_service(struct k_work * work)53 static void pull_service(struct k_work *work)
54 {
55 	ARG_UNUSED(work);
56 	switch (pull_service_state) {
57 	case NOT_STARTED:
58 		pull_service_state = IDLE;
59 		/* Set a long 5s time for a service that does not do anything*/
60 		/* Will be set to smaller, when there is time to clena up */
61 		lwm2m_engine_update_service_period(pull_service, 5000);
62 		break;
63 	case IDLE:
64 		/* Nothing to do */
65 		break;
66 	case STOPPING:
67 		/* Clean up the current socket context */
68 		lwm2m_engine_stop(&context.firmware_ctx);
69 		lwm2m_engine_update_service_period(pull_service, 5000);
70 		pull_service_state = IDLE;
71 		k_sem_give(&lwm2m_pull_sem);
72 		break;
73 	}
74 }
75 
start_service(void)76 static int start_service(void)
77 {
78 	if (pull_service_state != NOT_STARTED) {
79 		return 0;
80 	}
81 
82 	return lwm2m_engine_add_service(pull_service, 1);
83 }
84 
85 /**
86  * Close all open connections and release the context semaphore
87  */
cleanup_context(void)88 static void cleanup_context(void)
89 {
90 	pull_service_state = STOPPING;
91 	lwm2m_engine_update_service_period(pull_service, 1);
92 }
93 
transfer_request(struct coap_block_context * ctx,uint8_t * token,uint8_t tkl,coap_reply_t reply_cb)94 static int transfer_request(struct coap_block_context *ctx, uint8_t *token, uint8_t tkl,
95 			    coap_reply_t reply_cb)
96 {
97 	struct lwm2m_message *msg;
98 	int ret;
99 	char *cursor;
100 #if !defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
101 	struct http_parser_url parser;
102 	uint16_t off, len;
103 	char *next_slash;
104 #endif
105 
106 	msg = lwm2m_get_message(&context.firmware_ctx);
107 	if (!msg) {
108 		LOG_ERR("Unable to get a lwm2m message!");
109 		return -ENOMEM;
110 	}
111 
112 	msg->type = COAP_TYPE_CON;
113 	msg->code = COAP_METHOD_GET;
114 	msg->mid = coap_next_id();
115 	msg->token = token;
116 	msg->tkl = tkl;
117 	msg->reply_cb = reply_cb;
118 	msg->message_timeout_cb = do_transmit_timeout_cb;
119 
120 	ret = lwm2m_init_message(msg);
121 	if (ret < 0) {
122 		LOG_ERR("Error setting up lwm2m message");
123 		goto cleanup;
124 	}
125 
126 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
127 	/* TODO: shift to lower case */
128 	if (strncmp(context.uri, "http", 4) == 0) {
129 		cursor = COAP2HTTP_PROXY_URI_PATH;
130 	} else if (strncmp(context.uri, "coap", 4) == 0) {
131 		cursor = COAP2COAP_PROXY_URI_PATH;
132 	} else {
133 		ret = -EPROTONOSUPPORT;
134 		LOG_ERR("Unsupported schema");
135 		goto cleanup;
136 	}
137 
138 	ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH, cursor, strlen(cursor));
139 	if (ret < 0) {
140 		LOG_ERR("Error adding URI_PATH '%s'", cursor);
141 		goto cleanup;
142 	}
143 #else
144 	http_parser_url_init(&parser);
145 	ret = http_parser_parse_url(context.uri, strlen(context.uri), 0, &parser);
146 	if (ret < 0) {
147 		LOG_ERR("Invalid firmware url: %s", context.uri);
148 		ret = -ENOTSUP;
149 		goto cleanup;
150 	}
151 
152 	/* if path is not available, off/len will be zero */
153 	off = parser.field_data[UF_PATH].off;
154 	len = parser.field_data[UF_PATH].len;
155 	cursor = context.uri + off;
156 
157 	/* add path portions (separated by slashes) */
158 	while (len > 0 && (next_slash = strchr(cursor, '/')) != NULL) {
159 		if (next_slash != cursor) {
160 			ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH, cursor,
161 							next_slash - cursor);
162 			if (ret < 0) {
163 				LOG_ERR("Error adding URI_PATH");
164 				goto cleanup;
165 			}
166 		}
167 
168 		/* skip slash */
169 		len -= (next_slash - cursor) + 1;
170 		cursor = next_slash + 1;
171 	}
172 
173 	if (len > 0) {
174 		/* flush the rest */
175 		ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH, cursor, len);
176 		if (ret < 0) {
177 			LOG_ERR("Error adding URI_PATH");
178 			goto cleanup;
179 		}
180 	}
181 #endif
182 
183 	ret = coap_append_block2_option(&msg->cpkt, ctx);
184 	if (ret < 0) {
185 		LOG_ERR("Unable to add block2 option.");
186 		goto cleanup;
187 	}
188 
189 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
190 	ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_PROXY_URI, context.uri,
191 					strlen(context.uri));
192 	if (ret < 0) {
193 		LOG_ERR("Error adding PROXY_URI '%s'", context.uri);
194 		goto cleanup;
195 	}
196 #else
197 	/* Ask the server to provide a size estimate */
198 	ret = coap_append_option_int(&msg->cpkt, COAP_OPTION_SIZE2, 0);
199 	if (ret < 0) {
200 		LOG_ERR("Unable to add size2 option.");
201 		goto cleanup;
202 	}
203 #endif
204 
205 	/* send request */
206 	ret = lwm2m_send_message_async(msg);
207 	if (ret < 0) {
208 		LOG_ERR("Error sending LWM2M packet (err:%d).", ret);
209 		goto cleanup;
210 	}
211 
212 	return 0;
213 
214 cleanup:
215 	lwm2m_reset_message(msg, true);
216 	return ret;
217 }
218 
do_firmware_transfer_reply_cb(const struct coap_packet * response,struct coap_reply * reply,const struct sockaddr * from)219 static int do_firmware_transfer_reply_cb(const struct coap_packet *response,
220 					 struct coap_reply *reply, const struct sockaddr *from)
221 {
222 	int ret;
223 	bool last_block;
224 	uint8_t token[8];
225 	uint8_t tkl;
226 	uint16_t payload_len, payload_offset, len;
227 	struct coap_packet *check_response = (struct coap_packet *)response;
228 	struct lwm2m_engine_res *res = NULL;
229 	size_t write_buflen;
230 	uint8_t resp_code, *write_buf;
231 	struct coap_block_context received_block_ctx;
232 	const uint8_t *payload_start;
233 
234 	/* token is used to determine a valid ACK vs a separated response */
235 	tkl = coap_header_get_token(check_response, token);
236 
237 	/* If separated response (ACK) return and wait for response */
238 	if (!tkl && coap_header_get_type(response) == COAP_TYPE_ACK) {
239 		return 0;
240 	} else if (coap_header_get_type(response) == COAP_TYPE_CON) {
241 		/* Send back ACK so the server knows we received the pkt */
242 		ret = lwm2m_send_empty_ack(&context.firmware_ctx,
243 					   coap_header_get_id(check_response));
244 		if (ret < 0) {
245 			LOG_ERR("Error transmitting ACK");
246 			goto error;
247 		}
248 	}
249 
250 	/* Check response code from server. Expecting (2.05) */
251 	resp_code = coap_header_get_code(check_response);
252 	if (resp_code != COAP_RESPONSE_CODE_CONTENT) {
253 		LOG_ERR("Unexpected response from server: %d.%d",
254 			COAP_RESPONSE_CODE_CLASS(resp_code), COAP_RESPONSE_CODE_DETAIL(resp_code));
255 		ret = -ENOMSG;
256 		goto error;
257 	}
258 
259 	/* save main firmware block context */
260 	memcpy(&received_block_ctx, &context.block_ctx, sizeof(context.block_ctx));
261 
262 	ret = coap_update_from_block(check_response, &context.block_ctx);
263 	if (ret < 0) {
264 		LOG_ERR("Error from block update: %d", ret);
265 		ret = -EFAULT;
266 		goto error;
267 	}
268 
269 	/* test for duplicate transfer */
270 	if (context.block_ctx.current < received_block_ctx.current) {
271 		LOG_WRN("Duplicate packet ignored");
272 
273 		/* restore main firmware block context */
274 		memcpy(&context.block_ctx, &received_block_ctx, sizeof(context.block_ctx));
275 
276 		/* set reply->user_data to error to avoid releasing */
277 		reply->user_data = (void *)COAP_REPLY_STATUS_ERROR;
278 		return 0;
279 	}
280 
281 	/* Reach last block if ret equals to 0 */
282 	last_block = !coap_next_block(check_response, &context.block_ctx);
283 
284 	/* Process incoming data */
285 	payload_start = coap_packet_get_payload(response, &payload_len);
286 	if (payload_len > 0) {
287 		payload_offset = payload_start - response->data;
288 		LOG_DBG("total: %zd, current: %zd", context.block_ctx.total_size,
289 			context.block_ctx.current);
290 
291 		struct lwm2m_obj_path path;
292 
293 		ret = lwm2m_string_to_path("5/0/0", &path, '/');
294 		if (ret < 0) {
295 			goto error;
296 		}
297 
298 		/* look up firmware package resource */
299 		ret = lwm2m_get_resource(&path, &res);
300 		if (ret < 0) {
301 			goto error;
302 		}
303 
304 		/* get buffer data */
305 		write_buf = res->res_instances->data_ptr;
306 		write_buflen = res->res_instances->max_data_len;
307 
308 		/* check for user override to buffer */
309 		if (res->pre_write_cb) {
310 			write_buf = res->pre_write_cb(0, 0, 0, &write_buflen);
311 		}
312 
313 		if (context.write_cb) {
314 			size_t offset = context.block_ctx.current;
315 
316 			/* flush incoming data to write_cb */
317 			while (payload_len > 0) {
318 				len = (payload_len > write_buflen) ? write_buflen : payload_len;
319 				payload_len -= len;
320 				/* check for end of packet */
321 				if (buf_read(write_buf, len, CPKT_BUF_READ(response),
322 					     &payload_offset) < 0) {
323 					/* malformed packet */
324 					ret = -EFAULT;
325 					goto error;
326 				}
327 
328 				ret = context.write_cb(context.obj_inst_id, 0, 0, write_buf, len,
329 						       last_block && (payload_len == 0U),
330 						       context.block_ctx.total_size, offset);
331 				offset += len;
332 				if (ret < 0) {
333 					goto error;
334 				}
335 			}
336 		}
337 	}
338 
339 	if (!last_block) {
340 		/* More block(s) to come, setup next transfer */
341 		ret = transfer_request(&context.block_ctx, token, tkl,
342 				       do_firmware_transfer_reply_cb);
343 		if (ret < 0) {
344 			goto error;
345 		}
346 	} else {
347 		/* Download finished */
348 		context.result_cb(context.obj_inst_id, 0);
349 		cleanup_context();
350 	}
351 
352 	return 0;
353 
354 error:
355 	context.result_cb(context.obj_inst_id, ret);
356 	cleanup_context();
357 	return ret;
358 }
359 
do_transmit_timeout_cb(struct lwm2m_message * msg)360 static void do_transmit_timeout_cb(struct lwm2m_message *msg)
361 {
362 	LOG_ERR("TIMEOUT - Too many retry packet attempts! "
363 		"Aborting firmware download.");
364 	context.result_cb(context.obj_inst_id, -ENOMSG);
365 	cleanup_context();
366 }
367 
firmware_transfer(void)368 static void firmware_transfer(void)
369 {
370 	int ret;
371 	char *server_addr;
372 
373 	ret = k_sem_take(&lwm2m_pull_sem, K_FOREVER);
374 
375 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
376 	server_addr = CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_ADDR;
377 	if (strlen(server_addr) >= LWM2M_PACKAGE_URI_LEN) {
378 		LOG_ERR("Invalid Proxy URI: %s", server_addr);
379 		ret = -ENOTSUP;
380 		goto error;
381 	}
382 
383 	/* Copy required as it gets modified when port is available */
384 	strcpy(proxy_uri, server_addr);
385 	server_addr = proxy_uri;
386 #else
387 	server_addr = context.uri;
388 #endif
389 
390 	ret = lwm2m_parse_peerinfo(server_addr, &context.firmware_ctx, context.is_firmware_uri);
391 	if (ret < 0) {
392 		LOG_ERR("Failed to parse server URI.");
393 		goto error;
394 	}
395 
396 	lwm2m_engine_context_init(&context.firmware_ctx);
397 	ret = lwm2m_socket_start(&context.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", context.uri);
404 
405 	/* reset block transfer context */
406 	coap_block_transfer_init(&context.block_ctx, lwm2m_default_block_size(), 0);
407 	ret = transfer_request(&context.block_ctx, coap_next_token(), 8,
408 			       do_firmware_transfer_reply_cb);
409 	if (ret < 0) {
410 		goto error;
411 	}
412 
413 	return;
414 
415 error:
416 	context.result_cb(context.obj_inst_id, ret);
417 	cleanup_context();
418 }
419 
lwm2m_pull_context_start_transfer(char * uri,struct requesting_object req,k_timeout_t timeout)420 int lwm2m_pull_context_start_transfer(char *uri, struct requesting_object req, k_timeout_t timeout)
421 {
422 	int ret;
423 
424 	if (!req.write_cb || !req.result_cb) {
425 		LOG_DBG("Context failed sanity check. Verify initialization!");
426 		return -EINVAL;
427 	}
428 
429 	ret = start_service();
430 	if (ret) {
431 		LOG_ERR("Failed to start the pull-service");
432 		return ret;
433 	}
434 
435 	/* Check if we are not in the middle of downloading */
436 	ret = k_sem_take(&lwm2m_pull_sem, K_NO_WAIT);
437 	if (ret) {
438 		context.result_cb(req.obj_inst_id, -EALREADY);
439 		return -EALREADY;
440 	}
441 	k_sem_give(&lwm2m_pull_sem);
442 
443 	context.obj_inst_id = req.obj_inst_id;
444 	memcpy(context.uri, uri, LWM2M_PACKAGE_URI_LEN);
445 	context.is_firmware_uri = req.is_firmware_uri;
446 	context.result_cb = req.result_cb;
447 	context.write_cb = req.write_cb;
448 
449 	(void)memset(&context.block_ctx, 0, sizeof(struct coap_block_context));
450 	context.firmware_ctx.sock_fd = -1;
451 
452 	firmware_transfer();
453 
454 	return 0;
455 }
456 
lwm2m_pull_context_set_sockopt_callback(lwm2m_set_sockopt_cb_t set_sockopt_cb)457 void lwm2m_pull_context_set_sockopt_callback(lwm2m_set_sockopt_cb_t set_sockopt_cb)
458 {
459 	context.firmware_ctx.set_socketoptions = set_sockopt_cb;
460 }
461