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