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