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/include/net/lwm2m.h b/include/net/lwm2m.h index 33c6437067fdf..f1a09ea8500d0 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) 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 +#endif /* LWM2M Engine */ @@ -192,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/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..ea77695b75741 100644 --- a/samples/net/lwm2m_client/src/lwm2m-client.c +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -147,12 +147,23 @@ 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"); + + /* 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; } +#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) @@ -161,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) { @@ -204,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); - lwm2m_engine_register_exec_callback("5/0/2", firmware_update_cb); +#endif +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) + lwm2m_firmware_set_update_cb(firmware_update_cb); +#endif /* setup TEMP SENSOR object */ @@ -227,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; @@ -251,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/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 */ diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index 3a6d7b7adfd23..ab5f61462ed25 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -130,22 +130,53 @@ 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 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_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_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c index 3ac5d43de0daf..e0628c847c8bc 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) { @@ -595,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; } @@ -607,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; @@ -1698,6 +1828,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; @@ -1707,13 +1843,18 @@ 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); } - /* TODO: check for block transfer fields here */ - if (data_ptr && data_len > 0) { switch (obj_field->data_type) { @@ -1801,14 +1942,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, @@ -2096,20 +2253,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) { @@ -2126,6 +2269,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)); @@ -2201,7 +2347,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; @@ -2238,7 +2384,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) { @@ -2308,37 +2478,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, @@ -2403,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; } @@ -2430,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) { @@ -2513,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; @@ -2525,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; } @@ -2732,6 +2934,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) { @@ -2749,12 +2961,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, @@ -2781,6 +2988,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 f18fa1f80f240..f2810963bedb8 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.h +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -85,8 +85,10 @@ 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 *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); @@ -103,4 +105,13 @@ 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); +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..0ce597b75edab 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c @@ -59,22 +59,163 @@ 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) { - 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, @@ -82,11 +223,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 +251,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 +303,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,12 +325,14 @@ 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 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; diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c index 43e20bd4d581a..a9a6e5e0cb9cd 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -4,41 +4,44 @@ * 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) +#define PACKET_TRANSFER_RETRY_MAX 3 -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 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 firmware_udp_receive(struct net_app_ctx *app_ctx, struct net_pkt *pkt, int status, void *user_data) @@ -46,17 +49,22 @@ 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) -{ - /* TODO: Handle timeout */ -} - static int transfer_request(struct zoap_block_context *ctx, const u8_t *token, u8_t tkl, zoap_reply_t reply_cb) { 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) { @@ -77,15 +85,53 @@ static int transfer_request(struct zoap_block_context *ctx, goto cleanup; } - /* hard code URI path here -- should be pulled from package_uri */ +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT) 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); + uri_path, strlen(uri_path)); if (ret < 0) { - SYS_LOG_ERR("Error adding URI_QUERY 'large'"); + 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; + 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; + } +#endif ret = zoap_add_block2_option(&msg->zpkt, ctx); if (ret) { @@ -93,6 +139,22 @@ 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); if (ret < 0) { @@ -102,6 +164,47 @@ static int transfer_request(struct zoap_block_context *ctx, return 0; +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; +} + +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; @@ -120,111 +223,203 @@ 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; + u8_t resp_code; + struct zoap_block_context received_block_ctx; - SYS_LOG_DBG("TRANSFER REPLY"); + /* 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) { + 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; + } + + /* 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); + lwm2m_firmware_set_update_result(RESULT_INTEGRITY_FAILED); return ret; } - /* TODO: Process incoming data */ + /* 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); + + /* 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) { - token = zoap_header_get_token(check_response, &tkl); + /* More block(s) to come, setup next transfer */ 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; } -static enum zoap_block_size default_block_size(void) +static void do_transmit_timeout_cb(struct lwm2m_message *msg) { - 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; - } + const u8_t *token; + u8_t tkl; - return ZOAP_BLOCK_256; + 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, port, family; + int ret, family; + u16_t off; + u16_t len; + char tmp; + char *server_addr; /* 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); +#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 -#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); + http_parser_url_init(&parsed_uri); + ret = http_parser_parse_url(server_addr, + strlen(server_addr), + 0, + &parsed_uri); + if (ret != 0) { + SYS_LOG_ERR("Invalid firmware URI: %s", server_addr); + 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; } -#endif - ret = net_app_init_udp_client(&firmware_ctx.net_app_ctx, NULL, - &firmware_addr, NULL, port, + /* 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(server_addr + off, "coap", 4)) { + SYS_LOG_ERR("Unsupported schema"); + lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO); + return; + } + + if (!(parsed_uri.field_set & (1 << UF_PORT))) { + /* Set to default port of CoAP */ + parsed_uri.port = 5683; + } + + off = parsed_uri.field_data[UF_HOST].off; + len = parsed_uri.field_data[UF_HOST].len; + + /* truncate host portion */ + tmp = server_addr[off + len]; + server_addr[off + len] = '\0'; + + ret = net_app_init_udp_client(&firmware_ctx.net_app_ctx, NULL, NULL, + &server_addr[off], parsed_uri.port, firmware_ctx.net_init_timeout, NULL); + server_addr[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_INF("Connecting to server %s, port %d", server_addr + off, + 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); if (ret) { SYS_LOG_ERR("Could not set receive callback (err:%d)", ret); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); goto cleanup; } @@ -236,9 +431,9 @@ 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, + zoap_block_transfer_init(&firmware_block_ctx, + lwm2m_default_block_size(), 0); + transfer_request(&firmware_block_ctx, zoap_next_token(), 8, do_firmware_transfer_reply_cb); return; @@ -261,17 +456,16 @@ 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_retry = 0; + 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; } diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.c b/subsys/net/lib/lwm2m/lwm2m_rd_client.c index dcc50ab007449..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) */ @@ -354,7 +394,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 +408,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; } @@ -435,7 +475,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); @@ -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);