From 72d274b541c4f69fb9ad23ca5746aa104107e395 Mon Sep 17 00:00:00 2001 From: Robert Chou Date: Thu, 10 Aug 2017 16:54:15 +0800 Subject: [PATCH 01/15] net: http: add config to separate URL only parser Add HTTP_PARSER_URL_ONLY to separate URL only parser Signed-off-by: Robert Chou --- include/net/http_parser.h | 8 ++++++++ subsys/net/lib/http/Kconfig | 7 +++++++ subsys/net/lib/http/http_parser.c | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/include/net/http_parser.h b/include/net/http_parser.h index ea31fff128cba..95cb5b1b046d5 100644 --- a/include/net/http_parser.h +++ b/include/net/http_parser.h @@ -24,10 +24,12 @@ extern "C" { #endif +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) /* Also update SONAME in the Makefile whenever you change these. */ #define HTTP_PARSER_VERSION_MAJOR 2 #define HTTP_PARSER_VERSION_MINOR 7 #define HTTP_PARSER_VERSION_PATCH 1 +#endif #include #if defined(_WIN32) && !defined(__MINGW32__) && \ @@ -47,6 +49,7 @@ typedef unsigned __int64 u64_t; #include #endif +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) /* Maximium header size allowed. If the macro is not defined * before including this header then the default is used. To * change the maximum header size, define the macro in the build @@ -227,6 +230,7 @@ struct http_parser_settings { http_cb on_chunk_header; http_cb on_chunk_complete; }; +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ enum http_parser_url_fields { @@ -261,6 +265,7 @@ struct http_parser_url { }; +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) /* Returns the library version. Bits 16-23 contain the major version number, * bits 8-15 the minor version number and bits 0-7 the patch level. * Usage example: @@ -305,6 +310,7 @@ const char *http_errno_name(enum http_errno err); /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err); +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ /* Initialize all http_parser_url members to 0 */ void http_parser_url_init(struct http_parser_url *u); @@ -313,11 +319,13 @@ void http_parser_url_init(struct http_parser_url *u); int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u); +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) /* Pause or un-pause the parser; a nonzero value pauses */ void http_parser_pause(struct http_parser *parser, int paused); /* Checks if this is the final chunk of the body. */ int http_body_is_final(const struct http_parser *parser); +#endif #ifdef __cplusplus } diff --git a/subsys/net/lib/http/Kconfig b/subsys/net/lib/http/Kconfig index 2bc269f5b1a22..8ac28a7b9770d 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -65,6 +65,13 @@ config HTTP_PARSER This parser requires some string-related routines commonly provided by a libc implementation. +config HTTP_PARSER_URL_ONLY + bool "HTTP URL parser only support" + default n + select HTTP_PARSER + help + This option only enables URL parser of the http_parser library. + config HTTP_PARSER_STRICT bool "HTTP strict parsing" default n diff --git a/subsys/net/lib/http/http_parser.c b/subsys/net/lib/http/http_parser.c index eab6ca065b292..56cf2de990caf 100644 --- a/subsys/net/lib/http/http_parser.c +++ b/subsys/net/lib/http/http_parser.c @@ -71,6 +71,7 @@ do { \ } \ } while (0) +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) /* Don't allow the total size of the HTTP headers (including the status * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect * embedders against denial-of-service attacks where the attacker feeds @@ -169,6 +170,7 @@ s8_t unhex[256] = { -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ #ifdef HTTP_PARSER_STRICT @@ -284,6 +286,7 @@ enum state { s_message_done }; +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) #define PARSING_HEADER(state) (state <= s_headers_done) enum header_states { @@ -311,6 +314,7 @@ enum header_states { h_connection_close, h_connection_upgrade }; +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ enum http_host_state { s_http_host_dead = 1, @@ -327,6 +331,7 @@ enum http_host_state { s_http_host_port }; +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) static inline int cb_notify(struct http_parser *parser, enum state *current_state, http_cb cb, int cb_error, size_t *parsed, size_t already_parsed) @@ -382,6 +387,7 @@ int cb_data(struct http_parser *parser, http_data_cb cb, int cb_error, return 0; } +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ /* Macros for character classes; depends on strict-mode */ @@ -416,6 +422,7 @@ int cb_data(struct http_parser *parser, http_data_cb cb, int cb_error, (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') #endif +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) /** * Verify that a char is a valid visible (printable) US-ASCII * character or %x80-FF @@ -493,6 +500,7 @@ static struct { }; int http_message_needs_eof(const struct http_parser *parser); +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ /* Our URL parser. * @@ -655,6 +663,7 @@ static enum state parse_url_char(enum state s, const char ch) return s_dead; } +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) static int parser_header_state(struct http_parser *parser, char ch, char c) { @@ -2624,6 +2633,7 @@ const char *http_errno_description(enum http_errno err) return http_strerror_tab[err].description; } +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ static enum http_host_state http_parse_host_char(enum http_host_state s, const char ch) @@ -2906,6 +2916,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, return 0; } +#if !defined(CONFIG_HTTP_PARSER_URL_ONLY) void http_parser_pause(struct http_parser *parser, int paused) { /* Users should only be pausing/unpausing a parser that is not in an @@ -2933,3 +2944,4 @@ unsigned long http_parser_version(void) HTTP_PARSER_VERSION_MINOR * 0x00100 | HTTP_PARSER_VERSION_PATCH * 0x00001; } +#endif /* !CONFIG_HTTP_PARSER_URL_ONLY */ From 5469ea4fca9639b68e24e068b3fe438a79ad6c40 Mon Sep 17 00:00:00 2001 From: Robert Chou Date: Wed, 5 Jul 2017 23:07:41 +0800 Subject: [PATCH 02/15] net: lwm2m: add state machine for firmware pull update 1. Parse firmware pull URI 2. Add lwm2m_firmware_get/set_update_cb() for applicaiton to register callback. This is because we want to check the update_state before we pass to the application 3. Add lwm2m_firmware_get/set_update_result() and lwm2m_firmware_get/set_update_stat() to manage the state trasnition as well as the sanity check Signed-off-by: Robert Chou [michael.scott@linaro.org: rebased on net_app framework and lwm2m_message refactoring.] Signed-off-by: Michael Scott --- include/net/lwm2m.h | 6 + samples/net/lwm2m_client/prj_frdm_k64f.conf | 1 + samples/net/lwm2m_client/prj_qemu_x86.conf | 1 + samples/net/lwm2m_client/src/lwm2m-client.c | 10 +- subsys/net/lib/lwm2m/Kconfig | 1 + subsys/net/lib/lwm2m/lwm2m_engine.h | 7 + subsys/net/lib/lwm2m/lwm2m_obj_firmware.c | 175 +++++++++++++- .../net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 220 ++++++++++++------ 8 files changed, 350 insertions(+), 71 deletions(-) diff --git a/include/net/lwm2m.h b/include/net/lwm2m.h index 33c6437067fdf..f5c944c2686f5 100644 --- a/include/net/lwm2m.h +++ b/include/net/lwm2m.h @@ -122,9 +122,15 @@ int lwm2m_device_add_err(u8_t error_code); #define RESULT_UPDATE_FAILED 8 #define RESULT_UNSUP_PROTO 9 +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb); lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void); +void lwm2m_firmware_set_update_cb(lwm2m_engine_exec_cb_t cb); +lwm2m_engine_exec_cb_t lwm2m_firmware_get_update_cb(void); +#endif +#endif /* LWM2M Engine */ diff --git a/samples/net/lwm2m_client/prj_frdm_k64f.conf b/samples/net/lwm2m_client/prj_frdm_k64f.conf index d159f59f06f64..6e9fcc2d2d232 100644 --- a/samples/net/lwm2m_client/prj_frdm_k64f.conf +++ b/samples/net/lwm2m_client/prj_frdm_k64f.conf @@ -35,6 +35,7 @@ CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT=2 CONFIG_LWM2M_IPSO_SUPPORT=y CONFIG_LWM2M_IPSO_TEMP_SENSOR=y CONFIG_LWM2M_IPSO_LIGHT_CONTROL=y +CONFIG_HTTP_PARSER_URL_ONLY=y CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2" diff --git a/samples/net/lwm2m_client/prj_qemu_x86.conf b/samples/net/lwm2m_client/prj_qemu_x86.conf index 84fa4dabb2de6..78577fc482f07 100644 --- a/samples/net/lwm2m_client/prj_qemu_x86.conf +++ b/samples/net/lwm2m_client/prj_qemu_x86.conf @@ -35,6 +35,7 @@ CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT=2 CONFIG_LWM2M_IPSO_SUPPORT=y CONFIG_LWM2M_IPSO_TEMP_SENSOR=y CONFIG_LWM2M_IPSO_LIGHT_CONTROL=y +CONFIG_HTTP_PARSER_URL_ONLY=y CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2" diff --git a/samples/net/lwm2m_client/src/lwm2m-client.c b/samples/net/lwm2m_client/src/lwm2m-client.c index c95f449176d13..6ffbcc57c2849 100644 --- a/samples/net/lwm2m_client/src/lwm2m-client.c +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -150,6 +150,14 @@ static int device_factory_default_cb(u16_t obj_inst_id) static int firmware_update_cb(u16_t obj_inst_id) { SYS_LOG_DBG("UPDATE"); + + /* TODO: kick off update process */ + + /* If success, set the update result as RESULT_SUCCESS. + * In reality, it should be set at function lwm2m_setup() + */ + lwm2m_engine_set_u8("5/0/3", STATE_IDLE); + lwm2m_engine_set_u8("5/0/5", RESULT_SUCCESS); return 1; } @@ -207,7 +215,7 @@ static int lwm2m_setup(void) lwm2m_engine_register_post_write_callback("5/0/0", firmware_block_received_cb); lwm2m_firmware_set_write_cb(firmware_block_received_cb); - lwm2m_engine_register_exec_callback("5/0/2", firmware_update_cb); + lwm2m_firmware_set_update_cb(firmware_update_cb); /* setup TEMP SENSOR object */ diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index 3a6d7b7adfd23..a69aae837154d 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -130,6 +130,7 @@ config LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT bool "Firmware Update object pull support" default y depends on LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT + depends on (HTTP_PARSER || HTTP_PARSER_URL_ONLY) help Include support for pulling a file from a remote server via block transfer and "FIRMWARE PACKAGE URI" resource. This option diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.h b/subsys/net/lib/lwm2m/lwm2m_engine.h index f18fa1f80f240..ed943a1539149 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.h +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -103,4 +103,11 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt, bool handle_separate_response, udp_request_handler_cb_t udp_request_handler); +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) +u8_t lwm2m_firmware_get_update_state(void); +void lwm2m_firmware_set_update_state(u8_t state); +void lwm2m_firmware_set_update_result(u8_t result); +u8_t lwm2m_firmware_get_update_result(void); +#endif + #endif /* LWM2M_ENGINE_H */ diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c index 7c611bae0f330..1d0f160bc487a 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c @@ -59,11 +59,124 @@ static struct lwm2m_engine_obj_inst inst; static struct lwm2m_engine_res_inst res[FIRMWARE_MAX_ID]; static lwm2m_engine_set_data_cb_t write_cb; +static lwm2m_engine_exec_cb_t update_cb; #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT extern int lwm2m_firmware_start_transfer(char *package_uri); #endif +u8_t lwm2m_firmware_get_update_state(void) +{ + return update_state; +} + +void lwm2m_firmware_set_update_state(u8_t state) +{ + bool error = false; + + /* Check LWM2M SPEC appendix E.6.1 */ + switch (state) { + case STATE_DOWNLOADING: + if (update_state != STATE_IDLE) { + error = true; + } + break; + case STATE_DOWNLOADED: + if (update_state != STATE_DOWNLOADING && + update_state != STATE_UPDATING) { + error = true; + } + break; + case STATE_UPDATING: + if (update_state != STATE_DOWNLOADED) { + error = true; + } + break; + case STATE_IDLE: + break; + default: + SYS_LOG_ERR("Unhandled state: %u", state); + return; + } + + if (error) { + SYS_LOG_ERR("Invalid state transition: %u -> %u", + update_state, state); + } + + update_state = state; + NOTIFY_OBSERVER(LWM2M_OBJECT_FIRMWARE_ID, 0, FIRMWARE_STATE_ID); + SYS_LOG_DBG("Update state = %d", update_state); +} + +u8_t lwm2m_firmware_get_update_result(void) +{ + return update_result; +} + +void lwm2m_firmware_set_update_result(u8_t result) +{ + u8_t state; + bool error = false; + + /* Check LWM2M SPEC appendix E.6.1 */ + switch (result) { + case RESULT_DEFAULT: + lwm2m_firmware_set_update_state(STATE_IDLE); + break; + case RESULT_SUCCESS: + if (update_state != STATE_UPDATING) { + error = true; + state = update_state; + } + + lwm2m_firmware_set_update_state(STATE_IDLE); + break; + case RESULT_NO_STORAGE: + case RESULT_OUT_OF_MEM: + case RESULT_CONNECTION_LOST: + case RESULT_UNSUP_FW: + case RESULT_INVALID_URI: + case RESULT_UNSUP_PROTO: + if (update_state != STATE_DOWNLOADING) { + error = true; + state = update_state; + } + + lwm2m_firmware_set_update_state(STATE_IDLE); + break; + case RESULT_INTEGRITY_FAILED: + if (update_state != STATE_DOWNLOADING && + update_state != STATE_UPDATING) { + error = true; + state = update_state; + } + + lwm2m_firmware_set_update_state(STATE_IDLE); + break; + case RESULT_UPDATE_FAILED: + if (update_state != STATE_UPDATING) { + error = true; + state = update_state; + } + + /* Next state could be idle or downloaded */ + break; + default: + SYS_LOG_ERR("Unhandled result: %u", result); + return; + } + + if (error) { + SYS_LOG_ERR("Unexpected result(%u) set while state is %u", + result, state); + } + + update_result = result; + NOTIFY_OBSERVER(LWM2M_OBJECT_FIRMWARE_ID, 0, FIRMWARE_UPDATE_RESULT_ID); + SYS_LOG_DBG("Update result = %d", update_result); +} + static int package_write_cb(u16_t obj_inst_id, u8_t *data, u16_t data_len, bool last_block, size_t total_size) @@ -82,11 +195,22 @@ static int package_uri_write_cb(u16_t obj_inst_id, bool last_block, size_t total_size) { SYS_LOG_DBG("PACKAGE_URI WRITE: %s", package_uri); + #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT - lwm2m_firmware_start_transfer(package_uri); + u8_t state = lwm2m_firmware_get_update_state(); + + if (state == STATE_IDLE) { + lwm2m_firmware_set_update_result(RESULT_DEFAULT); + lwm2m_firmware_start_transfer(package_uri); + } else if (state == STATE_DOWNLOADED && data_len == 0) { + /* reset to state idle and result default */ + lwm2m_firmware_set_update_result(RESULT_DEFAULT); + } + return 1; -#endif +#else return 0; +#endif } void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb) @@ -99,6 +223,48 @@ lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void) return write_cb; } +void lwm2m_firmware_set_update_cb(lwm2m_engine_exec_cb_t cb) +{ + update_cb = cb; +} + +lwm2m_engine_exec_cb_t lwm2m_firmware_get_update_cb(void) +{ + return update_cb; +} + +static int firmware_update_cb(u16_t obj_inst_id) +{ + lwm2m_engine_exec_cb_t callback; + u8_t state; + int ret; + + state = lwm2m_firmware_get_update_state(); + if (state != STATE_DOWNLOADED) { + /* TODO: pass response code to caller, -1 will be shown as + * 4.05 method not allowed in current implementation + */ + SYS_LOG_ERR("State other than downloaded: %d", state); + return -1; + } + + lwm2m_firmware_set_update_state(STATE_UPDATING); + + callback = lwm2m_firmware_get_update_cb(); + if (callback) { + ret = callback(obj_inst_id); + if (ret < 0) { + SYS_LOG_ERR("Failed to update firmware: %d", ret); + lwm2m_firmware_set_update_result( + ret == -EINVAL ? RESULT_INTEGRITY_FAILED : + RESULT_UPDATE_FAILED); + return 0; + } + } + + return 0; +} + static struct lwm2m_engine_obj_inst *firmware_create(u16_t obj_inst_id) { int i = 0; @@ -109,7 +275,8 @@ static struct lwm2m_engine_obj_inst *firmware_create(u16_t obj_inst_id) INIT_OBJ_RES(res, i, FIRMWARE_PACKAGE_URI_ID, 0, package_uri, PACKAGE_URI_LEN, NULL, NULL, package_uri_write_cb, NULL); - INIT_OBJ_RES_DUMMY(res, i, FIRMWARE_UPDATE_ID); + INIT_OBJ_RES_EXECUTE(res, i, FIRMWARE_UPDATE_ID, + firmware_update_cb); INIT_OBJ_RES_DATA(res, i, FIRMWARE_STATE_ID, &update_state, sizeof(update_state)); INIT_OBJ_RES_DATA(res, i, FIRMWARE_UPDATE_RESULT_ID, @@ -130,6 +297,8 @@ static int lwm2m_firmware_init(struct device *dev) /* Set default values */ package_uri[0] = '\0'; + /* Initialize state machine */ + /* TODO: should be restored from the permanent storage */ update_state = STATE_IDLE; update_result = RESULT_DEFAULT; #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index 43e20bd4d581a..37b4c14412299 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -4,38 +4,32 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* - * TODO: - * Support PULL transfer method (from server) - */ - #define SYS_LOG_DOMAIN "lwm2m_obj_firmware_pull" #define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL #include +#include #include +#include #include #include #include +#include #include #include #include "lwm2m_object.h" #include "lwm2m_engine.h" -#define STATE_IDLE 0 -#define STATE_CONNECTING 1 - #define PACKAGE_URI_LEN 255 #define BUF_ALLOC_TIMEOUT K_SECONDS(1) #define NETWORK_INIT_TIMEOUT K_SECONDS(10) #define NETWORK_CONNECT_TIMEOUT K_SECONDS(10) -static u8_t transfer_state; static struct k_work firmware_work; static char firmware_uri[PACKAGE_URI_LEN]; -static struct sockaddr firmware_addr; +static struct http_parser_url parsed_uri; static struct lwm2m_ctx firmware_ctx; static struct zoap_block_context firmware_block_ctx; @@ -48,7 +42,7 @@ firmware_udp_receive(struct net_app_ctx *app_ctx, struct net_pkt *pkt, static void do_transmit_timeout_cb(struct lwm2m_message *msg) { - /* TODO: Handle timeout */ + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); } static int transfer_request(struct zoap_block_context *ctx, @@ -57,6 +51,11 @@ static int transfer_request(struct zoap_block_context *ctx, { struct lwm2m_message *msg; int ret; + int i; + int path_len; + char *cursor; + u16_t off; + u16_t len; msg = lwm2m_get_message(&firmware_ctx); if (!msg) { @@ -77,14 +76,43 @@ static int transfer_request(struct zoap_block_context *ctx, goto cleanup; } - /* hard code URI path here -- should be pulled from package_uri */ - ret = zoap_add_option(&msg->zpkt, ZOAP_OPTION_URI_PATH, - "large-create", sizeof("large-create") - 1); - ret = zoap_add_option(&msg->zpkt, ZOAP_OPTION_URI_PATH, - "1", sizeof("1") - 1); - if (ret < 0) { - SYS_LOG_ERR("Error adding URI_QUERY 'large'"); - goto cleanup; + /* if path is not available, off/len will be zero */ + off = parsed_uri.field_data[UF_PATH].off; + len = parsed_uri.field_data[UF_PATH].len; + cursor = firmware_uri + off; + path_len = 0; + + for (i = 0; i < len; i++) { + if (firmware_uri[off + i] == '/') { + if (path_len > 0) { + ret = zoap_add_option(&msg->zpkt, + ZOAP_OPTION_URI_PATH, + cursor, path_len); + if (ret < 0) { + SYS_LOG_ERR("Error adding URI_PATH"); + goto cleanup; + } + + cursor += path_len + 1; + path_len = 0; + } else { + /* skip current slash */ + cursor += 1; + } + continue; + } + + if (i == len - 1) { + /* flush the rest */ + ret = zoap_add_option(&msg->zpkt, ZOAP_OPTION_URI_PATH, + cursor, path_len + 1); + if (ret < 0) { + SYS_LOG_ERR("Error adding URI_PATH"); + goto cleanup; + } + break; + } + path_len += 1; } ret = zoap_add_block2_option(&msg->zpkt, ctx); @@ -93,6 +121,13 @@ static int transfer_request(struct zoap_block_context *ctx, goto cleanup; } + /* Ask the server to provide a size estimate */ + ret = zoap_add_option_int(&msg->zpkt, ZOAP_OPTION_SIZE2, 0); + if (ret) { + SYS_LOG_ERR("Unable to add size2 option."); + goto cleanup; + } + /* send request */ ret = lwm2m_send_message(msg); if (ret < 0) { @@ -104,6 +139,13 @@ static int transfer_request(struct zoap_block_context *ctx, cleanup: lwm2m_release_message(msg); + + if (ret == -ENOMEM) { + lwm2m_firmware_set_update_result(RESULT_OUT_OF_MEM); + } else { + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); + } + return ret; } @@ -120,40 +162,64 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, u8_t *payload; struct zoap_packet *check_response = (struct zoap_packet *)response; lwm2m_engine_set_data_cb_t callback; - - SYS_LOG_DBG("TRANSFER REPLY"); + u8_t resp_code; + + /* Check response code from server. Expecting (2.05) */ + resp_code = zoap_header_get_code(check_response); + if (resp_code != ZOAP_RESPONSE_CODE_CONTENT) { + SYS_LOG_ERR("Unexpected response from server: %d.%d", + ZOAP_RESPONSE_CODE_CLASS(resp_code), + ZOAP_RESPONSE_CODE_DETAIL(resp_code)); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); + return -ENOENT; + } ret = zoap_update_from_block(check_response, &firmware_block_ctx); if (ret < 0) { SYS_LOG_ERR("Error from block update: %d", ret); + lwm2m_firmware_set_update_result(RESULT_INTEGRITY_FAILED); return ret; } - /* TODO: Process incoming data */ + /* Reach last block if transfer_offset equals to 0 */ + transfer_offset = zoap_next_block(check_response, &firmware_block_ctx); + + /* Process incoming data */ payload = zoap_packet_get_payload(check_response, &payload_len); if (payload_len > 0) { - /* TODO: Determine when to actually advance to next block */ - transfer_offset = zoap_next_block(response, - &firmware_block_ctx); - SYS_LOG_DBG("total: %zd, current: %zd", firmware_block_ctx.total_size, firmware_block_ctx.current); - /* callback */ callback = lwm2m_firmware_get_write_cb(); if (callback) { - callback(0, payload, payload_len, - transfer_offset == 0, - firmware_block_ctx.total_size); + ret = callback(0, payload, payload_len, + transfer_offset == 0, + firmware_block_ctx.total_size); + if (ret == -ENOMEM) { + lwm2m_firmware_set_update_result( + RESULT_OUT_OF_MEM); + return ret; + } else if (ret == -ENOSPC) { + lwm2m_firmware_set_update_result( + RESULT_NO_STORAGE); + return ret; + } else if (ret < 0) { + lwm2m_firmware_set_update_result( + RESULT_INTEGRITY_FAILED); + return ret; + } } } - /* TODO: Determine actual completion criteria */ if (transfer_offset > 0) { + /* More block(s) to come, setup next transfer */ token = zoap_header_get_token(check_response, &tkl); ret = transfer_request(&firmware_block_ctx, token, tkl, do_firmware_transfer_reply_cb); + } else { + /* Download finished */ + lwm2m_firmware_set_update_state(STATE_DOWNLOADED); } return ret; @@ -183,48 +249,71 @@ static enum zoap_block_size default_block_size(void) static void firmware_transfer(struct k_work *work) { - int ret, port, family; + int ret, family; + u16_t off; + u16_t len; + char tmp; /* Server Peer IP information */ - /* TODO: use parser on firmware_uri to determine IP version + port */ - /* TODO: hard code IPv4 + port for now */ - port = 5685; family = AF_INET; -#if defined(CONFIG_NET_IPV6) - if (family == AF_INET6) { - firmware_addr.sa_family = family; - /* HACK: use firmware_uri directly as IP address */ - net_addr_pton(firmware_addr.sa_family, firmware_uri, - &net_sin6(&firmware_addr)->sin6_addr); - net_sin6(&firmware_addr)->sin6_port = htons(5685); + http_parser_url_init(&parsed_uri); + ret = http_parser_parse_url(firmware_uri, + strlen(firmware_uri), + 0, + &parsed_uri); + if (ret != 0) { + SYS_LOG_ERR("Invalid firmware URI: %s", firmware_uri); + lwm2m_firmware_set_update_result(RESULT_INVALID_URI); + return; + } + + /* Check schema and only support coap for now */ + if (!(parsed_uri.field_set & (1 << UF_SCHEMA))) { + SYS_LOG_ERR("No schema in package uri"); + lwm2m_firmware_set_update_result(RESULT_INVALID_URI); + return; + } + + /* TODO: enable coaps when DTLS is ready */ + off = parsed_uri.field_data[UF_SCHEMA].off; + len = parsed_uri.field_data[UF_SCHEMA].len; + if (len != 4 || memcmp(firmware_uri + off, "coap", 4)) { + SYS_LOG_ERR("Unsupported schema"); + lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO); + return; } -#endif - -#if defined(CONFIG_NET_IPV4) - if (family == AF_INET) { - firmware_addr.sa_family = family; - net_addr_pton(firmware_addr.sa_family, firmware_uri, - &net_sin(&firmware_addr)->sin_addr); - net_sin(&firmware_addr)->sin_port = htons(5685); + + if (!(parsed_uri.field_set & (1 << UF_PORT))) { + /* Set to default port of CoAP */ + parsed_uri.port = 5683; } -#endif - ret = net_app_init_udp_client(&firmware_ctx.net_app_ctx, NULL, - &firmware_addr, NULL, port, + off = parsed_uri.field_data[UF_HOST].off; + len = parsed_uri.field_data[UF_HOST].len; + + /* truncate host portion */ + tmp = firmware_uri[off + len]; + firmware_uri[off + len] = '\0'; + + ret = net_app_init_udp_client(&firmware_ctx.net_app_ctx, NULL, NULL, + &firmware_uri[off], parsed_uri.port, firmware_ctx.net_init_timeout, NULL); + firmware_uri[off + len] = tmp; if (ret) { - NET_ERR("Could not get an UDP context (err:%d)", ret); + SYS_LOG_ERR("Could not get an UDP context (err:%d)", ret); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); return; } - SYS_LOG_DBG("Attached to port: %d", port); + SYS_LOG_DBG("Attached to port: %d", parsed_uri.port); /* set net_app callbacks */ ret = net_app_set_cb(&firmware_ctx.net_app_ctx, NULL, firmware_udp_receive, NULL, NULL); if (ret) { SYS_LOG_ERR("Could not set receive callback (err:%d)", ret); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); goto cleanup; } @@ -237,7 +326,6 @@ static void firmware_transfer(struct k_work *work) /* reset block transfer context */ zoap_block_transfer_init(&firmware_block_ctx, default_block_size(), 0); - transfer_request(&firmware_block_ctx, NULL, 0, do_firmware_transfer_reply_cb); return; @@ -261,17 +349,15 @@ int lwm2m_firmware_start_transfer(char *package_uri) net_app_release(&firmware_ctx.net_app_ctx); } - if (transfer_state == STATE_IDLE) { - memset(&firmware_ctx, 0, sizeof(struct lwm2m_ctx)); - firmware_ctx.net_init_timeout = NETWORK_INIT_TIMEOUT; - firmware_ctx.net_timeout = NETWORK_CONNECT_TIMEOUT; - k_work_init(&firmware_work, firmware_transfer); + memset(&firmware_ctx, 0, sizeof(struct lwm2m_ctx)); + firmware_ctx.net_init_timeout = NETWORK_INIT_TIMEOUT; + firmware_ctx.net_timeout = NETWORK_CONNECT_TIMEOUT; + k_work_init(&firmware_work, firmware_transfer); + lwm2m_firmware_set_update_state(STATE_DOWNLOADING); - /* start file transfer work */ - strncpy(firmware_uri, package_uri, PACKAGE_URI_LEN - 1); - k_work_submit(&firmware_work); - return 0; - } + /* start file transfer work */ + strncpy(firmware_uri, package_uri, PACKAGE_URI_LEN - 1); + k_work_submit(&firmware_work); - return -1; + return 0; } From d48cd68fbdf7dbedd765f0b024f7c899e8a6dbde Mon Sep 17 00:00:00 2001 From: Robert Chou Date: Wed, 23 Aug 2017 10:37:57 +0800 Subject: [PATCH 03/15] net: lwm2m: setup data_ptr/len for OPAQUE resource when none given OPAQUE resource type might/might not have data_ptr/data_len setup depending on the implementation. This introduce an issue that when OPAQUE resource is written from the server side, the ones w/ none setup will not be able to get the data at post_write_cb() Modify to setup data_ptr/data_len as incoming buffer and buffer size Signed-off-by: Robert Chou --- subsys/net/lib/lwm2m/lwm2m_engine.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c index 3ac5d43de0daf..7d014d1568a1c 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.c +++ b/subsys/net/lib/lwm2m/lwm2m_engine.c @@ -1707,6 +1707,13 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, data_ptr = res->data_ptr; data_len = res->data_len; + /* setup data_ptr/data_len for OPAQUE when it has none setup */ + if (obj_field->data_type == LWM2M_RES_TYPE_OPAQUE && + data_ptr == NULL && data_len == 0) { + data_ptr = in->inbuf; + data_len = in->insize; + } + /* allow user to override data elements via callback */ if (res->pre_write_cb) { data_ptr = res->pre_write_cb(obj_inst->obj_inst_id, &data_len); From c72deedc2613f3f07e40e89f1672607af3994662 Mon Sep 17 00:00:00 2001 From: Robert Chou Date: Tue, 25 Jul 2017 16:54:25 +0800 Subject: [PATCH 04/15] net: lwm2m: add firmware push support 1. Add handling block1 option in handle_request(). The basic idea is to declare structure block_context at compiled time and use "token" as a key to pick up the on-going block cotext. It should be able to support multiple blockwise transfer concurrently 2. Use write callback implemented in lwm2m_obj_firmware to deal w/ the update state transition and than call the callback registered by the application 3. move default_block_size to lwm2m_engine.c to share btw lwm2m_engine and lwm2m_obj_firmware_pull Signed-off-by: Robert Chou [michael.scott@linaro.org: rebased on LwM2M net_app changes.] Signed-off-by: Michael Scott --- include/net/lwm2m.h | 2 +- samples/net/lwm2m_client/src/lwm2m-client.c | 10 +- subsys/net/lib/lwm2m/Kconfig | 14 +- subsys/net/lib/lwm2m/lwm2m_engine.c | 283 +++++++++++++++--- subsys/net/lib/lwm2m/lwm2m_engine.h | 2 + subsys/net/lib/lwm2m/lwm2m_obj_firmware.c | 36 ++- .../net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 25 +- 7 files changed, 290 insertions(+), 82 deletions(-) diff --git a/include/net/lwm2m.h b/include/net/lwm2m.h index f5c944c2686f5..6b0df37663ea2 100644 --- a/include/net/lwm2m.h +++ b/include/net/lwm2m.h @@ -123,10 +123,10 @@ int lwm2m_device_add_err(u8_t error_code); #define RESULT_UNSUP_PROTO 9 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) -#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb); lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void); +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) void lwm2m_firmware_set_update_cb(lwm2m_engine_exec_cb_t cb); lwm2m_engine_exec_cb_t lwm2m_firmware_get_update_cb(void); #endif diff --git a/samples/net/lwm2m_client/src/lwm2m-client.c b/samples/net/lwm2m_client/src/lwm2m-client.c index 6ffbcc57c2849..347bb667a49af 100644 --- a/samples/net/lwm2m_client/src/lwm2m-client.c +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -147,6 +147,7 @@ static int device_factory_default_cb(u16_t obj_inst_id) return 1; } +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) static int firmware_update_cb(u16_t obj_inst_id) { SYS_LOG_DBG("UPDATE"); @@ -160,7 +161,9 @@ static int firmware_update_cb(u16_t obj_inst_id) lwm2m_engine_set_u8("5/0/5", RESULT_SUCCESS); return 1; } +#endif +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) static int firmware_block_received_cb(u16_t obj_inst_id, u8_t *data, u16_t data_len, bool last_block, size_t total_size) @@ -169,6 +172,7 @@ static int firmware_block_received_cb(u16_t obj_inst_id, data_len, last_block); return 1; } +#endif static int lwm2m_setup(void) { @@ -212,10 +216,12 @@ static int lwm2m_setup(void) /* setup FIRMWARE object */ - lwm2m_engine_register_post_write_callback("5/0/0", - firmware_block_received_cb); +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) lwm2m_firmware_set_write_cb(firmware_block_received_cb); +#endif +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) lwm2m_firmware_set_update_cb(firmware_update_cb); +#endif /* setup TEMP SENSOR object */ diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index a69aae837154d..70b19795bed77 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -136,17 +136,23 @@ config LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT block transfer and "FIRMWARE PACKAGE URI" resource. This option adds another UDP context and packet handling. -config LWM2M_FIRMWARE_UPDATE_PULL_COAP_BLOCK_SIZE - int "Firmware Update object pull CoAP block size" - depends on LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT +config LWM2M_COAP_BLOCK_SIZE + int "LWM2M CoAP block-wise transfer size" default 256 default 64 if NET_L2_BT default 64 if NET_L2_IEEE802154 range 16 1024 help - CoAP block size used by firmware pull when performing block-wise + CoAP block size used by LWM2M when performing block-wise transfers. Possible values: 16, 32, 64, 128, 256, 512 and 1024. +config LWM2M_NUM_BLOCK1_CONTEXT + int "Maximum # of LWM2M block1 contexts" + default 3 + help + This value sets up the maximum number of block1 contexts for + CoAP block-wise transfer we can handle at the same time. + config LWM2M_RW_JSON_SUPPORT bool "support for JSON writer" default y diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c index 7d014d1568a1c..bde50bde953c4 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.c +++ b/subsys/net/lib/lwm2m/lwm2m_engine.c @@ -16,8 +16,6 @@ * * - Use server / security object instance 0 for initial connection * - Add DNS support for security uri parsing - * - Block-transfer support / Large response messages - * (use Block2 to limit message size to 64 bytes for 6LOWPAN compat.) * - BOOTSTRAP/DTLS cleanup * - Handle WRITE_ATTRIBUTES (pmin=10&pmax=60) * - Handle Resource ObjLink type @@ -102,6 +100,24 @@ static sys_slist_t engine_obj_list; static sys_slist_t engine_obj_inst_list; static sys_slist_t engine_observer_list; +#define NUM_BLOCK1_CONTEXT CONFIG_LWM2M_NUM_BLOCK1_CONTEXT + +/* TODO: figure out what's correct value */ +#define TIMEOUT_BLOCKWISE_TRANSFER K_SECONDS(30) + +#define GET_BLOCK_NUM(v) ((v) >> 4) +#define GET_BLOCK_SIZE(v) (((v) & 0x7)) +#define GET_MORE(v) (!!((v) & 0x08)) + +struct block_context { + struct zoap_block_context ctx; + s64_t timestamp; + u8_t token[8]; + u8_t tkl; +}; + +static struct block_context block1_contexts[NUM_BLOCK1_CONTEXT]; + /* periodic / notify / observe handling stack */ static K_THREAD_STACK_DEFINE(engine_thread_stack, CONFIG_LWM2M_ENGINE_STACK_SIZE); @@ -160,6 +176,101 @@ static char *sprint_token(const u8_t *token, u8_t tkl) } #endif +/* block-wise transfer functions */ + +enum zoap_block_size lwm2m_default_block_size(void) +{ + switch (CONFIG_LWM2M_COAP_BLOCK_SIZE) { + case 16: + return ZOAP_BLOCK_16; + case 32: + return ZOAP_BLOCK_32; + case 64: + return ZOAP_BLOCK_64; + case 128: + return ZOAP_BLOCK_128; + case 256: + return ZOAP_BLOCK_256; + case 512: + return ZOAP_BLOCK_512; + case 1024: + return ZOAP_BLOCK_1024; + } + + return ZOAP_BLOCK_256; +} + +static int +init_block_ctx(const u8_t *token, u8_t tkl, struct block_context **ctx) +{ + int i; + s64_t timestamp; + + *ctx = NULL; + timestamp = k_uptime_get(); + for (i = 0; i < NUM_BLOCK1_CONTEXT; i++) { + if (block1_contexts[i].tkl == 0) { + *ctx = &block1_contexts[i]; + break; + } + + if (timestamp - block1_contexts[i].timestamp > + TIMEOUT_BLOCKWISE_TRANSFER) { + *ctx = &block1_contexts[i]; + /* TODO: notify application for block + * transfer timeout + */ + break; + } + } + + if (*ctx == NULL) { + SYS_LOG_ERR("Cannot find free block context"); + return -ENOMEM; + } + + (*ctx)->tkl = tkl; + memcpy((*ctx)->token, token, tkl); + zoap_block_transfer_init(&(*ctx)->ctx, lwm2m_default_block_size(), 0); + (*ctx)->timestamp = timestamp; + + return 0; +} + +static int +get_block_ctx(const u8_t *token, u8_t tkl, struct block_context **ctx) +{ + int i; + + *ctx = NULL; + + for (i = 0; i < NUM_BLOCK1_CONTEXT; i++) { + if (block1_contexts[i].tkl == tkl && + memcmp(token, block1_contexts[i].token, tkl) == 0) { + *ctx = &block1_contexts[i]; + /* refresh timestmap */ + (*ctx)->timestamp = k_uptime_get(); + break; + } + } + + if (*ctx == NULL) { + SYS_LOG_ERR("Cannot find block context"); + return -ENOENT; + } + + return 0; +} + +static void free_block_ctx(struct block_context *ctx) +{ + if (ctx == NULL) { + return; + } + + ctx->tkl = 0; +} + /* observer functions */ int lwm2m_notify_observer(u16_t obj_id, u16_t obj_inst_id, u16_t res_id) @@ -530,6 +641,20 @@ int lwm2m_delete_obj_inst(u16_t obj_id, u16_t obj_inst_id) /* utility functions */ +static int get_option_int(const struct zoap_packet *zpkt, u8_t opt) +{ + struct zoap_option option = {}; + u16_t count = 1; + int r; + + r = zoap_find_options(zpkt, opt, &option, count); + if (r <= 0) { + return -ENOENT; + } + + return zoap_option_value_to_int(&option); +} + static void engine_clear_context(struct lwm2m_engine_context *context) { if (context->in) { @@ -1698,6 +1823,12 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, void *data_ptr = NULL; size_t data_len = 0; size_t len = 0; + size_t total_size = 0; + int ret = 0; + u8_t tkl = 0; + const u8_t *token; + bool last_block = true; + struct block_context *block_ctx = NULL; if (!obj_inst || !res || !obj_field || !context) { return -EINVAL; @@ -1719,8 +1850,6 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, data_ptr = res->pre_write_cb(obj_inst->obj_inst_id, &data_len); } - /* TODO: check for block transfer fields here */ - if (data_ptr && data_len > 0) { switch (obj_field->data_type) { @@ -1808,14 +1937,30 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, } if (res->post_write_cb) { + /* Get block1 option for checking MORE block flag */ + ret = get_option_int(in->in_zpkt, ZOAP_OPTION_BLOCK1); + if (ret >= 0) { + last_block = !GET_MORE(ret); + + /* Get block_ctx for total_size (might be zero) */ + token = zoap_header_get_token(in->in_zpkt, &tkl); + if (token != NULL && + !get_block_ctx(token, tkl, &block_ctx)) { + total_size = block_ctx->ctx.total_size; + } + } + /* ignore return value here */ - res->post_write_cb(obj_inst->obj_inst_id, data_ptr, len, - false, 0); + ret = res->post_write_cb(obj_inst->obj_inst_id, data_ptr, len, + last_block, total_size); + if (ret >= 0) { + ret = 0; + } } NOTIFY_OBSERVER_PATH(path); - return 0; + return ret; } static int lwm2m_write_attr_handler(struct lwm2m_engine_obj *obj, @@ -2103,20 +2248,6 @@ static int do_write_op(struct lwm2m_engine_obj *obj, } } -static int get_observe_option(const struct zoap_packet *zpkt) -{ - struct zoap_option option = {}; - u16_t count = 1; - int r; - - r = zoap_find_options(zpkt, ZOAP_OPTION_OBSERVE, &option, count); - if (r <= 0) { - return -ENOENT; - } - - return zoap_option_value_to_int(&option); -} - static int handle_request(struct zoap_packet *request, struct lwm2m_message *msg) { @@ -2133,6 +2264,9 @@ static int handle_request(struct zoap_packet *request, struct lwm2m_engine_context context; int observe = -1; /* default to -1, 0 = ENABLE, 1 = DISABLE */ bool discover = false; + struct block_context *block_ctx = NULL; + size_t block_offset = 0; + enum zoap_block_size block_size; /* setup engine context */ memset(&context, 0, sizeof(struct lwm2m_engine_context)); @@ -2208,7 +2342,7 @@ static int handle_request(struct zoap_packet *request, context.operation = LWM2M_OP_READ; } /* check for observe */ - observe = get_observe_option(in.in_zpkt); + observe = get_option_int(in.in_zpkt, ZOAP_OPTION_OBSERVE); zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_CONTENT); break; @@ -2245,7 +2379,31 @@ static int handle_request(struct zoap_packet *request, in.inpos = 0; in.inbuf = zoap_packet_get_payload(in.in_zpkt, &in.insize); - /* TODO: check for block transfer? */ + /* Check for block transfer */ + r = get_option_int(in.in_zpkt, ZOAP_OPTION_BLOCK1); + if (r > 0) { + /* RFC7252: 4.6. Message Size */ + block_size = GET_BLOCK_SIZE(r); + if (GET_MORE(r) && + zoap_block_size_to_bytes(block_size) > in.insize) { + SYS_LOG_DBG("Trailing payload is discarded!"); + r = -EFBIG; + goto error; + } + + if (GET_BLOCK_NUM(r) == 0) { + r = init_block_ctx(token, tkl, &block_ctx); + } else { + r = get_block_ctx(token, tkl, &block_ctx); + } + + if (r < 0) { + goto error; + } + + /* 0 will be returned if it's the last block */ + block_offset = zoap_next_block(in.in_zpkt, &block_ctx->ctx); + } switch (context.operation) { @@ -2315,37 +2473,63 @@ static int handle_request(struct zoap_packet *request, return -EINVAL; } - if (r == 0) { - /* TODO: Handle blockwise 1 */ + if (r) { + goto error; + } - if (out.outlen > 0) { - SYS_LOG_DBG("replying with %u bytes", out.outlen); - zoap_packet_set_used(out.out_zpkt, out.outlen); + /* Handle blockwise 1 */ + if (block_ctx) { + if (block_offset > 0) { + /* More to come, ack with correspond block # */ + r = zoap_add_block1_option( + out.out_zpkt, &block_ctx->ctx); + if (r) { + /* report as internal server error */ + SYS_LOG_ERR("Fail adding block1 option: %d", r); + r = -EINVAL; + goto error; + } else { + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_CONTINUE); + } } else { - SYS_LOG_DBG("no data in reply"); + /* Free context when finished */ + free_block_ctx(block_ctx); } + } + + if (out.outlen > 0) { + SYS_LOG_DBG("replying with %u bytes", out.outlen); + zoap_packet_set_used(out.out_zpkt, out.outlen); } else { - if (r == -ENOENT) { - zoap_header_set_code(out.out_zpkt, - ZOAP_RESPONSE_CODE_NOT_FOUND); - r = 0; - } else if (r == -EPERM) { - zoap_header_set_code(out.out_zpkt, - ZOAP_RESPONSE_CODE_NOT_ALLOWED); - r = 0; - } else if (r == -EEXIST) { - zoap_header_set_code(out.out_zpkt, - ZOAP_RESPONSE_CODE_BAD_REQUEST); - r = 0; - } else { - /* Failed to handle the request */ - zoap_header_set_code(out.out_zpkt, - ZOAP_RESPONSE_CODE_INTERNAL_ERROR); - r = 0; - } + SYS_LOG_DBG("no data in reply"); } - return r; + return 0; + +error: + if (r == -ENOENT) { + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_NOT_FOUND); + } else if (r == -EPERM) { + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_NOT_ALLOWED); + } else if (r == -EEXIST) { + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_BAD_REQUEST); + } else if (r == -EFBIG) { + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_REQUEST_TOO_LARGE); + } else { + /* Failed to handle the request */ + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_INTERNAL_ERROR); + } + + /* Free block context when error happened */ + free_block_ctx(block_ctx); + + return 0; } void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt, @@ -2788,6 +2972,9 @@ int lwm2m_engine_start(struct lwm2m_ctx *client_ctx, static int lwm2m_engine_init(struct device *dev) { + memset(block1_contexts, 0, + sizeof(struct block_context) * NUM_BLOCK1_CONTEXT); + /* start thread to handle OBSERVER / NOTIFY events */ k_thread_create(&engine_thread_data, &engine_thread_stack[0], diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.h b/subsys/net/lib/lwm2m/lwm2m_engine.h index ed943a1539149..bf1116afb4077 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.h +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -103,6 +103,8 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt, bool handle_separate_response, udp_request_handler_cb_t udp_request_handler); +enum zoap_block_size lwm2m_default_block_size(void); + #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) u8_t lwm2m_firmware_get_update_state(void); void lwm2m_firmware_set_update_state(u8_t state); diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c index 1d0f160bc487a..52b8ff252bebf 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c @@ -181,13 +181,41 @@ static int package_write_cb(u16_t obj_inst_id, u8_t *data, u16_t data_len, bool last_block, size_t total_size) { - SYS_LOG_DBG("PACKAGE WRITE"); + u8_t state; + int ret = 0; + + state = lwm2m_firmware_get_update_state(); + if (state == STATE_IDLE) { + /* TODO: setup timer to check download status, + * make sure it fail after timeout + */ + lwm2m_firmware_set_update_state(STATE_DOWNLOADING); + } else if (state != STATE_DOWNLOADING) { + if (data_len == 0 && state == STATE_DOWNLOADED) { + /* reset to state idle and result default */ + lwm2m_firmware_set_update_result(RESULT_DEFAULT); + return 1; + } + + SYS_LOG_DBG("Cannot download: state = %d", state); + return -EPERM; + } + if (write_cb) { - write_cb(obj_inst_id, data, data_len, last_block, total_size); - return 1; + ret = write_cb(obj_inst_id, data, data_len, + last_block, total_size); + if (ret < 0) { + SYS_LOG_ERR("Failed to store firmware: %d", ret); + lwm2m_firmware_set_update_result( + RESULT_INTEGRITY_FAILED); + } } - return 0; + if (last_block) { + lwm2m_firmware_set_update_state(STATE_DOWNLOADED); + } + + return 1; } static int package_uri_write_cb(u16_t obj_inst_id, diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index 37b4c14412299..7c9e2d9ba4143 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -225,28 +225,6 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, return ret; } -static enum zoap_block_size default_block_size(void) -{ - switch (CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_BLOCK_SIZE) { - case 16: - return ZOAP_BLOCK_16; - case 32: - return ZOAP_BLOCK_32; - case 64: - return ZOAP_BLOCK_64; - case 128: - return ZOAP_BLOCK_128; - case 256: - return ZOAP_BLOCK_256; - case 512: - return ZOAP_BLOCK_512; - case 1024: - return ZOAP_BLOCK_1024; - } - - return ZOAP_BLOCK_256; -} - static void firmware_transfer(struct k_work *work) { int ret, family; @@ -325,7 +303,8 @@ static void firmware_transfer(struct k_work *work) } /* reset block transfer context */ - zoap_block_transfer_init(&firmware_block_ctx, default_block_size(), 0); + zoap_block_transfer_init(&firmware_block_ctx, + lwm2m_default_block_size(), 0); transfer_request(&firmware_block_ctx, NULL, 0, do_firmware_transfer_reply_cb); return; From d90c965a3e75033e1e1d02655f9522447f10d374 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Mon, 9 Oct 2017 15:10:40 -0700 Subject: [PATCH 05/15] net: lwm2m: fix compile w/o CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT Fix wrong check of DELIVERY_METHOD_PUSH to DELIVERY_METHOD_PUSH_ONLY Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_obj_firmware.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c index 52b8ff252bebf..0ce597b75edab 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c @@ -332,7 +332,7 @@ static int lwm2m_firmware_init(struct device *dev) #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT delivery_method = DELIVERY_METHOD_BOTH; #else - delivery_method = DELIVERY_METHOD_PUSH; + delivery_method = DELIVERY_METHOD_PUSH_ONLY; #endif firmware.obj_id = LWM2M_OBJECT_FIRMWARE_ID; From 64032b66050257a5e7d0d3409daa181dd7c63d8f Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Wed, 6 Sep 2017 14:34:52 -0700 Subject: [PATCH 06/15] net: lwm2m: RD client start message should be INFO not DEBUG This is a useful message announcing that the RD client state machine is starting for a particular connection. If the log level is set low so that DBG messages are hidden, then this message goes away. Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_rd_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.c b/subsys/net/lib/lwm2m/lwm2m_rd_client.c index dcc50ab007449..6f8814c28a81f 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rd_client.c +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.c @@ -435,7 +435,7 @@ static void do_deregister_timeout_cb(struct lwm2m_message *msg) static int sm_do_init(int index) { - SYS_LOG_DBG("RD Client started with endpoint " + SYS_LOG_INF("RD Client started with endpoint " "'%s' and client lifetime %d", clients[index].ep_name, clients[index].lifetime); From 39ae6cd300115b754a2af14dbb8aaed0329be60e Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Thu, 7 Sep 2017 08:29:36 -0700 Subject: [PATCH 07/15] net: lwm2m: change registraion update messages to INFO Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_rd_client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.c b/subsys/net/lib/lwm2m/lwm2m_rd_client.c index 6f8814c28a81f..fca90d8a99749 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rd_client.c +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.c @@ -354,7 +354,7 @@ static int do_update_reply_cb(const struct zoap_packet *response, int index; code = zoap_header_get_code(response); - SYS_LOG_DBG("Update callback (code:%u.%u)", + SYS_LOG_INF("Update callback (code:%u.%u)", ZOAP_RESPONSE_CODE_CLASS(code), ZOAP_RESPONSE_CODE_DETAIL(code)); @@ -368,7 +368,7 @@ static int do_update_reply_cb(const struct zoap_packet *response, if ((code == ZOAP_RESPONSE_CODE_CHANGED) || (code == ZOAP_RESPONSE_CODE_CREATED)) { set_sm_state(index, ENGINE_REGISTRATION_DONE); - SYS_LOG_DBG("Update Done"); + SYS_LOG_INF("Update Done"); return 0; } From 1fa259c5f013251c0ff93e6cfe45dbdfc5300bd0 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Tue, 5 Sep 2017 21:03:49 -0700 Subject: [PATCH 08/15] net: lwm2m: fix message release for separate reply In the case of a proxy server translating HTTP -> COAP (known in the code as "separate reply"), we were leaking lwm2m_message structures. This was due to pending objects being cleared out during the first ACK, and no other way of finding a matching message when the follow up packet was received. Let's add a second match for reply to make sure we can find our matching message resources. NOTE: This change renames find_msg_from_pending() to find_msg() and makes it a static function as it's only used by the lwm2m_engine. Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_engine.c | 17 +++++++++++++---- subsys/net/lib/lwm2m/lwm2m_engine.h | 1 - 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c index bde50bde953c4..3e6d1e741d0dd 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.c +++ b/subsys/net/lib/lwm2m/lwm2m_engine.c @@ -720,11 +720,12 @@ static void zoap_options_to_path(struct zoap_option *opt, int options_count, } } -struct lwm2m_message *find_msg_from_pending(struct zoap_pending *pending) +static struct lwm2m_message *find_msg(struct zoap_pending *pending, + struct zoap_reply *reply) { size_t i; - if (!pending) { + if (!pending && !reply) { return NULL; } @@ -732,6 +733,10 @@ struct lwm2m_message *find_msg_from_pending(struct zoap_pending *pending) if (messages[i].ctx && messages[i].pending == pending) { return &messages[i]; } + + if (messages[i].ctx && messages[i].reply == reply) { + return &messages[i]; + } } return NULL; @@ -2594,7 +2599,7 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt, * is != NULL. */ if (pending) { - msg = find_msg_from_pending(pending); + msg = find_msg(pending, NULL); if (msg) { msg->pending = NULL; } @@ -2621,6 +2626,10 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt, SYS_LOG_DBG("separated response, not removing reply"); goto cleanup; } + + if (!msg) { + msg = find_msg(pending, reply); + } } if (reply || pending) { @@ -2704,7 +2713,7 @@ static void retransmit_request(struct k_work *work) return; } - msg = find_msg_from_pending(pending); + msg = find_msg(pending, NULL); if (!msg) { SYS_LOG_ERR("pending has no valid LwM2M message!"); return; diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.h b/subsys/net/lib/lwm2m/lwm2m_engine.h index bf1116afb4077..4d1d047add774 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.h +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -86,7 +86,6 @@ int lwm2m_get_or_create_engine_obj(struct lwm2m_engine_context *context, u8_t *created); /* LwM2M message functions */ -struct lwm2m_message *find_msg_from_pending(struct zoap_pending *pending); struct lwm2m_message *lwm2m_get_message(struct lwm2m_ctx *client_ctx); void lwm2m_release_message(struct lwm2m_message *msg); int lwm2m_init_message(struct lwm2m_message *msg); From faf71651f31ead39b41e9c654f9eb276b67b4b85 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 8 Sep 2017 08:22:59 -0700 Subject: [PATCH 09/15] net: lwm2m: split out lwm2m context initialization Create an internal function lwm2m_engine_context_init() which sets the extra packet pools and initializes retransmit work internal to the LwM2M engine. This function will be used by firmware pull support which establishes a new LwM2M context for downloading firmware. Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_engine.c | 17 +++++++++++------ subsys/net/lib/lwm2m/lwm2m_engine.h | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c index 3e6d1e741d0dd..7ee3f5980af9b 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.c +++ b/subsys/net/lib/lwm2m/lwm2m_engine.c @@ -2932,6 +2932,16 @@ int lwm2m_engine_set_net_pkt_pool(struct lwm2m_ctx *ctx, } #endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ +void lwm2m_engine_context_init(struct lwm2m_ctx *client_ctx) +{ + k_delayed_work_init(&client_ctx->retransmit_work, retransmit_request); + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) + net_app_set_net_pkt_pool(&client_ctx->net_app_ctx, + client_ctx->tx_slab, client_ctx->data_pool); +#endif +} + int lwm2m_engine_start(struct lwm2m_ctx *client_ctx, char *peer_str, u16_t peer_port) { @@ -2949,12 +2959,7 @@ int lwm2m_engine_start(struct lwm2m_ctx *client_ctx, goto error_start; } - k_delayed_work_init(&client_ctx->retransmit_work, retransmit_request); - -#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) - net_app_set_net_pkt_pool(&client_ctx->net_app_ctx, - client_ctx->tx_slab, client_ctx->data_pool); -#endif + lwm2m_engine_context_init(client_ctx); /* set net_app callbacks */ ret = net_app_set_cb(&client_ctx->net_app_ctx, diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.h b/subsys/net/lib/lwm2m/lwm2m_engine.h index 4d1d047add774..f2810963bedb8 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.h +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -85,6 +85,9 @@ int lwm2m_get_or_create_engine_obj(struct lwm2m_engine_context *context, struct lwm2m_engine_obj_inst **obj_inst, u8_t *created); +/* LwM2M context functions */ +void lwm2m_engine_context_init(struct lwm2m_ctx *client_ctx); + /* LwM2M message functions */ struct lwm2m_message *lwm2m_get_message(struct lwm2m_ctx *client_ctx); void lwm2m_release_message(struct lwm2m_message *msg); From 3912573918679508290dd0951571b62f4cd63f46 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 8 Sep 2017 08:27:45 -0700 Subject: [PATCH 10/15] net: lwm2m: setup lwm2m ctx for firmware download Previously, firmware support wasn't initializing the retransmit work or the extra network packet pools. Let's fix that. NOTE: While this fixes the setup of retransmit work, the actual attempts to re-send packets which are pending is failing. Needs another follow-up fix. Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index 7c9e2d9ba4143..6b9e23d4759b4 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -286,6 +286,8 @@ static void firmware_transfer(struct k_work *work) SYS_LOG_DBG("Attached to port: %d", parsed_uri.port); + lwm2m_engine_context_init(&firmware_ctx); + /* set net_app callbacks */ ret = net_app_set_cb(&firmware_ctx.net_app_ctx, NULL, firmware_udp_receive, NULL, NULL); From 6d7f17c69e854174f7c947e2d10ce5230a285509 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 8 Sep 2017 08:32:16 -0700 Subject: [PATCH 11/15] net: lwm2m: fix packet leak in timeout work When a packet expires after the pending retries we call lwm2m_release_message() to free up resources. This includes cleanup of the pending structure which calls net_pkt_unref on the pending packet. This would normally free up the packet memory. However, earlier in the pending processing we add a ref to the packet so that normal send processing doesn't free up the memory. This meant we were leaking packet memory every time we had an expiration due to timeout. Let's do an unref prior to calling lwm2m_release_message() to make sure the packet memory is freed correctly. Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_engine.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c index 7ee3f5980af9b..e0628c847c8bc 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.c +++ b/subsys/net/lib/lwm2m/lwm2m_engine.c @@ -2725,6 +2725,8 @@ static void retransmit_request(struct k_work *work) msg->message_timeout_cb(msg); } + /* final unref to release pkt */ + net_pkt_unref(pending->pkt); lwm2m_release_message(msg); return; } From 729f9c1b99f0e87c891b0101e94e28181db0e3d7 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 8 Sep 2017 08:37:48 -0700 Subject: [PATCH 12/15] net: lwm2m: add full block-wise retries for firmware download UDP packets can be lost in heavy traffic. Normally we can handle this with pending packet processing for packets which have not been responded to with an ACK. However, due to the time it takes for firmware to download via CoAP, an extra level of retries should be added. The process works like this: Normal pending packets will try to send 3 times fairly quickly. If that fails, then the timeout callback is called for the firmware download process. A retry counter is incremented and the timeout callback perform a new packet send of the block-wise transfer packet that is missing, until the retry counter hits a limit (3) and then the transfer is aborted. This allows for a longer "outage" to happen during firmware transfer and the process can still succeed. NOTE: This patch does not fix a current bug where the pending process is not re-sending the packets correctly, it only makes the process more stable with a better chance to work. Signed-off-by: Michael Scott --- .../net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index 6b9e23d4759b4..e3af25d003bff 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -26,13 +26,17 @@ #define BUF_ALLOC_TIMEOUT K_SECONDS(1) #define NETWORK_INIT_TIMEOUT K_SECONDS(10) #define NETWORK_CONNECT_TIMEOUT K_SECONDS(10) +#define PACKET_TRANSFER_RETRY_MAX 3 static struct k_work firmware_work; static char firmware_uri[PACKAGE_URI_LEN]; static struct http_parser_url parsed_uri; static struct lwm2m_ctx firmware_ctx; +static int firmware_retry; static struct zoap_block_context firmware_block_ctx; +static void do_transmit_timeout_cb(struct lwm2m_message *msg); + static void firmware_udp_receive(struct net_app_ctx *app_ctx, struct net_pkt *pkt, int status, void *user_data) @@ -40,11 +44,6 @@ firmware_udp_receive(struct net_app_ctx *app_ctx, struct net_pkt *pkt, lwm2m_udp_receive(&firmware_ctx, pkt, true, NULL); } -static void do_transmit_timeout_cb(struct lwm2m_message *msg) -{ - lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); -} - static int transfer_request(struct zoap_block_context *ctx, const u8_t *token, u8_t tkl, zoap_reply_t reply_cb) @@ -225,6 +224,26 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, return ret; } +static void do_transmit_timeout_cb(struct lwm2m_message *msg) +{ + const u8_t *token; + u8_t tkl; + + if (firmware_retry < PACKET_TRANSFER_RETRY_MAX) { + /* retry block */ + SYS_LOG_WRN("TIMEOUT - Sending a retry packet!"); + token = zoap_header_get_token(&msg->zpkt, &tkl); + + transfer_request(&firmware_block_ctx, token, tkl, + do_firmware_transfer_reply_cb); + firmware_retry++; + } else { + SYS_LOG_ERR("TIMEOUT - Too many retry packet attempts! " + "Aborting firmware download."); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); + } +} + static void firmware_transfer(struct k_work *work) { int ret, family; @@ -331,6 +350,7 @@ int lwm2m_firmware_start_transfer(char *package_uri) } memset(&firmware_ctx, 0, sizeof(struct lwm2m_ctx)); + firmware_retry = 0; firmware_ctx.net_init_timeout = NETWORK_INIT_TIMEOUT; firmware_ctx.net_timeout = NETWORK_CONNECT_TIMEOUT; k_work_init(&firmware_work, firmware_transfer); From 3389d452070c0ec37ec97cf05525083317cd7c66 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 8 Sep 2017 14:03:56 -0700 Subject: [PATCH 13/15] net: lwm2m: ignore duplicate/older block transfers During firmware download via block-wise transfer, we can see packets occaionally get re-transmitted (normal logic in the pending / retry functions). However, Both of these packets end up coming through the reply handler and we should ignore any block-wise transfer that has a current value less than where we expect to be. NOTE: This fixes K64F ethernet transfers where we were getting too many packets back in the handler. Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index e3af25d003bff..4d6bc751719c5 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -162,6 +162,7 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, struct zoap_packet *check_response = (struct zoap_packet *)response; lwm2m_engine_set_data_cb_t callback; u8_t resp_code; + struct zoap_block_context received_block_ctx; /* Check response code from server. Expecting (2.05) */ resp_code = zoap_header_get_code(check_response); @@ -173,6 +174,10 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, return -ENOENT; } + /* save main firmware block context */ + memcpy(&received_block_ctx, &firmware_block_ctx, + sizeof(firmware_block_ctx)); + ret = zoap_update_from_block(check_response, &firmware_block_ctx); if (ret < 0) { SYS_LOG_ERR("Error from block update: %d", ret); @@ -180,6 +185,15 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, return ret; } + /* test for duplicate transfer */ + if (firmware_block_ctx.current < received_block_ctx.current) { + SYS_LOG_WRN("Duplicate packet ignored"); + /* restore main firmware block context */ + memcpy(&firmware_block_ctx, &received_block_ctx, + sizeof(firmware_block_ctx)); + return 0; + } + /* Reach last block if transfer_offset equals to 0 */ transfer_offset = zoap_next_block(check_response, &firmware_block_ctx); From 3025b153212f69defd148bbd4c772a7df53a7977 Mon Sep 17 00:00:00 2001 From: Ricardo Salveti Date: Thu, 27 Jul 2017 18:30:44 -0300 Subject: [PATCH 14/15] net: lwm2m: firmware: add support for firmware pull over CoAP proxy CoAP allows a proxy to be used when transferring data (CoAP-CoAP and/or CoAP-HTTP) by creating request on a specific URI path and by using the Proxy URI CoAP option. Create specific Kconfig options for the proxy server address and port, until a parser gets implemented. Code tested with Californium acting as CoAP proxy. Signed-off-by: Ricardo Salveti [michael.scott@linaro.org: rebased on net_app + lwm2m_message refactoring + firmware update changes.] Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/Kconfig | 24 ++++ .../net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 115 ++++++++++++++++-- 2 files changed, 128 insertions(+), 11 deletions(-) diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index 70b19795bed77..ab5f61462ed25 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -153,6 +153,30 @@ config LWM2M_NUM_BLOCK1_CONTEXT This value sets up the maximum number of block1 contexts for CoAP block-wise transfer we can handle at the same time. +config LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT + bool "Firmware Update object pull via CoAP-CoAP/HTTP proxy support" + depends on LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT + default n + help + Include support for pulling firmware file via a CoAP-CoAP/HTTP proxy. + +if LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT + +config LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_ADDR + string "CoAP proxy network address" + help + Network address of the CoAP proxy server. + +config LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_URI_PATH + string "CoAP URI path element used by the proxy" + default "coap2http" + help + CoAP URI path element exported by the CoAP proxy server. + Defaults to coap2http, which is the URI path used by the + Californium CoAP-HTTP proxy. + +endif # LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT + config LWM2M_RW_JSON_SUPPORT bool "support for JSON writer" default y diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index 4d6bc751719c5..a9a6e5e0cb9cd 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -35,6 +35,11 @@ static struct lwm2m_ctx firmware_ctx; static int firmware_retry; static struct zoap_block_context firmware_block_ctx; +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT) +#define PROXY_URI_LEN 255 +static char proxy_uri[PROXY_URI_LEN]; +#endif + static void do_transmit_timeout_cb(struct lwm2m_message *msg); static void @@ -50,11 +55,16 @@ static int transfer_request(struct zoap_block_context *ctx, { struct lwm2m_message *msg; int ret; +#if !defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT) int i; int path_len; char *cursor; u16_t off; u16_t len; +#else + char *uri_path = + CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_URI_PATH; +#endif msg = lwm2m_get_message(&firmware_ctx); if (!msg) { @@ -75,6 +85,14 @@ static int transfer_request(struct zoap_block_context *ctx, goto cleanup; } +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT) + ret = zoap_add_option(&msg->zpkt, ZOAP_OPTION_URI_PATH, + uri_path, strlen(uri_path)); + if (ret < 0) { + SYS_LOG_ERR("Error adding URI_PATH '%s'", uri_path); + goto cleanup; + } +#else /* if path is not available, off/len will be zero */ off = parsed_uri.field_data[UF_PATH].off; len = parsed_uri.field_data[UF_PATH].len; @@ -113,6 +131,7 @@ static int transfer_request(struct zoap_block_context *ctx, } path_len += 1; } +#endif ret = zoap_add_block2_option(&msg->zpkt, ctx); if (ret) { @@ -120,12 +139,21 @@ static int transfer_request(struct zoap_block_context *ctx, goto cleanup; } +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT) + ret = zoap_add_option(&msg->zpkt, ZOAP_OPTION_PROXY_URI, + firmware_uri, strlen(firmware_uri)); + if (ret < 0) { + SYS_LOG_ERR("Error adding PROXY_URI '%s'", firmware_uri); + goto cleanup; + } +#else /* Ask the server to provide a size estimate */ ret = zoap_add_option_int(&msg->zpkt, ZOAP_OPTION_SIZE2, 0); if (ret) { SYS_LOG_ERR("Unable to add size2 option."); goto cleanup; } +#endif /* send request */ ret = lwm2m_send_message(msg); @@ -148,6 +176,40 @@ static int transfer_request(struct zoap_block_context *ctx, return ret; } +static int transfer_empty_ack(u16_t mid) +{ + struct lwm2m_message *msg; + int ret; + + msg = lwm2m_get_message(&firmware_ctx); + if (!msg) { + SYS_LOG_ERR("Unable to get a lwm2m message!"); + return -ENOMEM; + } + + msg->type = ZOAP_TYPE_ACK; + msg->code = ZOAP_CODE_EMPTY; + msg->mid = mid; + + ret = lwm2m_init_message(msg); + if (ret) { + goto cleanup; + } + + ret = lwm2m_send_message(msg); + if (ret < 0) { + SYS_LOG_ERR("Error sending LWM2M packet (err:%d).", + ret); + goto cleanup; + } + + return 0; + +cleanup: + lwm2m_release_message(msg); + return ret; +} + static int do_firmware_transfer_reply_cb(const struct zoap_packet *response, struct zoap_reply *reply, @@ -164,6 +226,21 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, u8_t resp_code; struct zoap_block_context received_block_ctx; + /* token is used to determine a valid ACK vs a separated response */ + token = zoap_header_get_token(check_response, &tkl); + + /* If separated response (ACK) return and wait for response */ + if (!tkl && zoap_header_get_type(response) == ZOAP_TYPE_ACK) { + return 0; + } else if (zoap_header_get_type(response) == ZOAP_TYPE_CON) { + /* Send back ACK so the server knows we received the pkt */ + ret = transfer_empty_ack(zoap_header_get_id(check_response)); + if (ret < 0) { + SYS_LOG_ERR("Error transmitting ACK"); + return ret; + } + } + /* Check response code from server. Expecting (2.05) */ resp_code = zoap_header_get_code(check_response); if (resp_code != ZOAP_RESPONSE_CODE_CONTENT) { @@ -227,7 +304,6 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response, if (transfer_offset > 0) { /* More block(s) to come, setup next transfer */ - token = zoap_header_get_token(check_response, &tkl); ret = transfer_request(&firmware_block_ctx, token, tkl, do_firmware_transfer_reply_cb); } else { @@ -264,17 +340,33 @@ static void firmware_transfer(struct k_work *work) u16_t off; u16_t len; char tmp; + char *server_addr; /* Server Peer IP information */ family = AF_INET; +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT) + server_addr = CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_ADDR; + if (strlen(server_addr) >= PROXY_URI_LEN) { + SYS_LOG_ERR("Invalid Proxy URI: %s", server_addr); + lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO); + return; + } + + /* Copy required as it gets modified when port is available */ + strcpy(proxy_uri, server_addr); + server_addr = proxy_uri; +#else + server_addr = firmware_uri; +#endif + http_parser_url_init(&parsed_uri); - ret = http_parser_parse_url(firmware_uri, - strlen(firmware_uri), + ret = http_parser_parse_url(server_addr, + strlen(server_addr), 0, &parsed_uri); if (ret != 0) { - SYS_LOG_ERR("Invalid firmware URI: %s", firmware_uri); + SYS_LOG_ERR("Invalid firmware URI: %s", server_addr); lwm2m_firmware_set_update_result(RESULT_INVALID_URI); return; } @@ -289,7 +381,7 @@ static void firmware_transfer(struct k_work *work) /* TODO: enable coaps when DTLS is ready */ off = parsed_uri.field_data[UF_SCHEMA].off; len = parsed_uri.field_data[UF_SCHEMA].len; - if (len != 4 || memcmp(firmware_uri + off, "coap", 4)) { + if (len != 4 || memcmp(server_addr + off, "coap", 4)) { SYS_LOG_ERR("Unsupported schema"); lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO); return; @@ -304,20 +396,21 @@ static void firmware_transfer(struct k_work *work) len = parsed_uri.field_data[UF_HOST].len; /* truncate host portion */ - tmp = firmware_uri[off + len]; - firmware_uri[off + len] = '\0'; + tmp = server_addr[off + len]; + server_addr[off + len] = '\0'; ret = net_app_init_udp_client(&firmware_ctx.net_app_ctx, NULL, NULL, - &firmware_uri[off], parsed_uri.port, + &server_addr[off], parsed_uri.port, firmware_ctx.net_init_timeout, NULL); - firmware_uri[off + len] = tmp; + server_addr[off + len] = tmp; if (ret) { SYS_LOG_ERR("Could not get an UDP context (err:%d)", ret); lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); return; } - SYS_LOG_DBG("Attached to port: %d", parsed_uri.port); + SYS_LOG_INF("Connecting to server %s, port %d", server_addr + off, + parsed_uri.port); lwm2m_engine_context_init(&firmware_ctx); @@ -340,7 +433,7 @@ static void firmware_transfer(struct k_work *work) /* reset block transfer context */ zoap_block_transfer_init(&firmware_block_ctx, lwm2m_default_block_size(), 0); - transfer_request(&firmware_block_ctx, NULL, 0, + transfer_request(&firmware_block_ctx, zoap_next_token(), 8, do_firmware_transfer_reply_cb); return; From 5204b0ecb4981a5a560d4bccbb16d40b0fd84edd Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Mon, 11 Sep 2017 22:20:36 -0700 Subject: [PATCH 15/15] net: lwm2m: add RD client callbacks for app Applications may want to be notified when various events happen in the LwM2M rd client. Let's implement an event callback which sends: connect, disconnect and update events. Signed-off-by: Michael Scott --- include/net/lwm2m.h | 20 ++++++++- samples/net/lwm2m_client/src/lwm2m-client.c | 50 ++++++++++++++++++++- subsys/net/lib/lwm2m/lwm2m_rd_client.c | 48 ++++++++++++++++++-- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/include/net/lwm2m.h b/include/net/lwm2m.h index 6b0df37663ea2..f1a09ea8500d0 100644 --- a/include/net/lwm2m.h +++ b/include/net/lwm2m.h @@ -198,8 +198,26 @@ int lwm2m_engine_start(struct lwm2m_ctx *client_ctx, /* LWM2M RD Client */ +/* Client events */ +enum lwm2m_rd_client_event { + LWM2M_RD_CLIENT_EVENT_NONE, + LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_FAILURE, + LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_COMPLETE, + LWM2M_RD_CLIENT_EVENT_REGISTRATION_FAILURE, + LWM2M_RD_CLIENT_EVENT_REGISTRATION_COMPLETE, + LWM2M_RD_CLIENT_EVENT_REG_UPDATE_FAILURE, + LWM2M_RD_CLIENT_EVENT_REG_UPDATE_COMPLETE, + LWM2M_RD_CLIENT_EVENT_DEREGISTER_FAILURE, + LWM2M_RD_CLIENT_EVENT_DISCONNECT +}; + +/* Event callback */ +typedef void (*lwm2m_ctx_event_cb_t)(struct lwm2m_ctx *ctx, + enum lwm2m_rd_client_event event); + int lwm2m_rd_client_start(struct lwm2m_ctx *client_ctx, char *peer_str, u16_t peer_port, - const char *ep_name); + const char *ep_name, + lwm2m_ctx_event_cb_t event_cb); #endif /* __LWM2M_H__ */ diff --git a/samples/net/lwm2m_client/src/lwm2m-client.c b/samples/net/lwm2m_client/src/lwm2m-client.c index 347bb667a49af..ea77695b75741 100644 --- a/samples/net/lwm2m_client/src/lwm2m-client.c +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -241,6 +241,50 @@ static int lwm2m_setup(void) return 0; } +static void rd_client_event(struct lwm2m_ctx *client, + enum lwm2m_rd_client_event client_event) +{ + switch (client_event) { + + case LWM2M_RD_CLIENT_EVENT_NONE: + /* do nothing */ + break; + + case LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_FAILURE: + SYS_LOG_DBG("Bootstrap failure!"); + break; + + case LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_COMPLETE: + SYS_LOG_DBG("Bootstrap complete"); + break; + + case LWM2M_RD_CLIENT_EVENT_REGISTRATION_FAILURE: + SYS_LOG_DBG("Registration failure!"); + break; + + case LWM2M_RD_CLIENT_EVENT_REGISTRATION_COMPLETE: + SYS_LOG_DBG("Registration complete"); + break; + + case LWM2M_RD_CLIENT_EVENT_REG_UPDATE_FAILURE: + SYS_LOG_DBG("Registration update failure!"); + break; + + case LWM2M_RD_CLIENT_EVENT_REG_UPDATE_COMPLETE: + SYS_LOG_DBG("Registration update complete"); + break; + + case LWM2M_RD_CLIENT_EVENT_DEREGISTER_FAILURE: + SYS_LOG_DBG("Deregister failure!"); + break; + + case LWM2M_RD_CLIENT_EVENT_DISCONNECT: + SYS_LOG_DBG("Disconnected"); + break; + + } +} + void main(void) { int ret; @@ -265,10 +309,12 @@ void main(void) #if defined(CONFIG_NET_IPV6) ret = lwm2m_rd_client_start(&client, CONFIG_NET_APP_PEER_IPV6_ADDR, - CONFIG_LWM2M_PEER_PORT, CONFIG_BOARD); + CONFIG_LWM2M_PEER_PORT, CONFIG_BOARD, + rd_client_event); #elif defined(CONFIG_NET_IPV4) ret = lwm2m_rd_client_start(&client, CONFIG_NET_APP_PEER_IPV4_ADDR, - CONFIG_LWM2M_PEER_PORT, CONFIG_BOARD); + CONFIG_LWM2M_PEER_PORT, CONFIG_BOARD, + rd_client_event); #else SYS_LOG_ERR("LwM2M client requires IPv4 or IPv6."); ret = -EPROTONOSUPPORT; diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.c b/subsys/net/lib/lwm2m/lwm2m_rd_client.c index fca90d8a99749..a723bd5184004 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rd_client.c +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.c @@ -106,6 +106,8 @@ struct lwm2m_rd_client_info { char ep_name[CLIENT_EP_LEN]; char server_ep[CLIENT_EP_LEN]; + + lwm2m_ctx_event_cb_t event_cb; }; static K_THREAD_STACK_DEFINE(lwm2m_rd_client_thread_stack, @@ -120,10 +122,31 @@ static u8_t client_data[256]; /* allocate some data for the RD */ static struct lwm2m_rd_client_info clients[CLIENT_INSTANCE_COUNT]; static int client_count; -static void set_sm_state(int index, u8_t state) +static void set_sm_state(int index, u8_t sm_state) { + enum lwm2m_rd_client_event event = LWM2M_RD_CLIENT_EVENT_NONE; + + /* Determine if a callback to the app is needed */ + if (sm_state == ENGINE_BOOTSTRAP_DONE) { + event = LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_COMPLETE; + } else if (clients[index].engine_state == ENGINE_UPDATE_SENT && + sm_state == ENGINE_REGISTRATION_DONE) { + event = LWM2M_RD_CLIENT_EVENT_REG_UPDATE_COMPLETE; + } else if (sm_state == ENGINE_REGISTRATION_DONE) { + event = LWM2M_RD_CLIENT_EVENT_REGISTRATION_COMPLETE; + } else if ((sm_state == ENGINE_INIT || + sm_state == ENGINE_DEREGISTERED) && + (clients[index].engine_state > ENGINE_BOOTSTRAP_DONE && + clients[index].engine_state < ENGINE_DEREGISTER)) { + event = LWM2M_RD_CLIENT_EVENT_DISCONNECT; + } + /* TODO: add locking? */ - clients[index].engine_state = state; + clients[index].engine_state = sm_state; + + if (event > LWM2M_RD_CLIENT_EVENT_NONE && clients[index].event_cb) { + clients[index].event_cb(clients[index].ctx, event); + } } static bool sm_is_registered(int index) @@ -200,6 +223,7 @@ static void sm_handle_timeout_state(struct lwm2m_message *msg, enum sm_engine_state sm_state) { int index; + enum lwm2m_rd_client_event event = LWM2M_RD_CLIENT_EVENT_NONE; index = find_rd_client_from_msg(msg, clients, CLIENT_INSTANCE_COUNT); if (index < 0) { @@ -207,7 +231,23 @@ static void sm_handle_timeout_state(struct lwm2m_message *msg, return; } + if (clients[index].engine_state == ENGINE_BOOTSTRAP_SENT) { + event = LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_FAILURE; + } else if (clients[index].engine_state == ENGINE_REGISTRATION_SENT) { + event = LWM2M_RD_CLIENT_EVENT_REGISTRATION_FAILURE; + } else if (clients[index].engine_state == ENGINE_UPDATE_SENT) { + event = LWM2M_RD_CLIENT_EVENT_REG_UPDATE_FAILURE; + } else if (clients[index].engine_state == ENGINE_DEREGISTER_SENT) { + event = LWM2M_RD_CLIENT_EVENT_DEREGISTER_FAILURE; + } else { + /* TODO: unknown timeout state */ + } + set_sm_state(index, sm_state); + + if (event > LWM2M_RD_CLIENT_EVENT_NONE && clients[index].event_cb) { + clients[index].event_cb(clients[index].ctx, event); + } } /* force re-update with remote peer(s) */ @@ -820,7 +860,8 @@ static void lwm2m_rd_client_service(void) int lwm2m_rd_client_start(struct lwm2m_ctx *client_ctx, char *peer_str, u16_t peer_port, - const char *ep_name) + const char *ep_name, + lwm2m_ctx_event_cb_t event_cb) { int index, ret = 0; @@ -843,6 +884,7 @@ int lwm2m_rd_client_start(struct lwm2m_ctx *client_ctx, index = client_count; client_count++; clients[index].ctx = client_ctx; + clients[index].event_cb = event_cb; set_sm_state(index, ENGINE_INIT); strncpy(clients[index].ep_name, ep_name, CLIENT_EP_LEN - 1); SYS_LOG_INF("LWM2M Client: %s", clients[index].ep_name);