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