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 1f99380f9d4ce..ed16c535621ec 100644 --- a/include/net/lwm2m.h +++ b/include/net/lwm2m.h @@ -89,9 +89,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 */ @@ -156,5 +162,4 @@ int lwm2m_engine_start(struct net_context *net_ctx); int lwm2m_rd_client_start(struct net_context *net_ctx, struct sockaddr *peer_addr, const char *ep_name); - #endif /* __LWM2M_H__ */ diff --git a/samples/net/lwm2m_client/src/lwm2m-client.c b/samples/net/lwm2m_client/src/lwm2m-client.c index 5d5eb5d21896a..69d329db88ef1 100644 --- a/samples/net/lwm2m_client/src/lwm2m-client.c +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -160,12 +160,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) @@ -174,6 +185,7 @@ static int firmware_block_received_cb(u16_t obj_inst_id, data_len, last_block); return 1; } +#endif static int set_endpoint_name(char *ep_name, sa_family_t family) { @@ -232,10 +244,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 */ 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 55b293b4890b5..4873e7476c833 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -122,22 +122,29 @@ config LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT bool "Firmware Update object pull support" default y depends on LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT + select 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_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 b45c0450e7010..bf65c5e2b0ff3 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 @@ -107,6 +105,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); @@ -149,6 +165,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) @@ -439,6 +550,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) { @@ -1516,6 +1641,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; @@ -1525,13 +1656,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) { @@ -1619,14 +1755,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, @@ -1934,20 +2086,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 zoap_packet *response, struct sockaddr *from_addr) @@ -1965,6 +2103,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)); @@ -2040,7 +2181,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; @@ -2077,7 +2218,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) { @@ -2148,37 +2313,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 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); } - return r; + /* Free block context when error happened */ + free_block_ctx(block_ctx); + + return 0; } int lwm2m_udp_sendto(struct net_pkt *pkt, const struct sockaddr *dst_addr) @@ -2552,6 +2743,9 @@ int lwm2m_engine_start(struct net_context *net_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 37c53d527fb37..6b37606fe76ff 100644 --- a/subsys/net/lib/lwm2m/lwm2m_engine.h +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -75,4 +75,13 @@ void lwm2m_udp_receive(struct net_context *ctx, struct net_pkt *pkt, struct zoap_packet *response, struct sockaddr *from_addr)); +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..3e34509616d21 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c @@ -4,11 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* - * TODO: - * Support PUSH transfer method (from server) - */ - #define SYS_LOG_DOMAIN "lwm2m_obj_firmware" #define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL #include @@ -21,7 +16,7 @@ /* Firmware resource IDs */ #define FIRMWARE_PACKAGE_ID 0 -#define FIRMWARE_PACKAGE_URI_ID 1 /* TODO */ +#define FIRMWARE_PACKAGE_URI_ID 1 #define FIRMWARE_UPDATE_ID 2 #define FIRMWARE_STATE_ID 3 #define FIRMWARE_UPDATE_RESULT_ID 5 @@ -59,34 +54,190 @@ 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_cancel_transfer(void); 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) { + /* reset to default state when empty string received */ + if (data_len == 0) { + 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, u8_t *data, u16_t data_len, 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); +#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) + 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 (data_len == 0) { + /* cancel on-going transmission if any */ + lwm2m_firmware_cancel_transfer(); + /* reset to state idle and result default */ + lwm2m_firmware_set_update_result(RESULT_DEFAULT); + } else if (state == STATE_DOWNLOADING) { + SYS_LOG_ERR("Package URI (%s) is written while downloading", + package_uri); + } + return 1; -#endif +#else return 0; +#endif } void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb) @@ -99,6 +250,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) { + /* Appendix E6, The resource is only executable when + * the value of the State Resource is Downloaded + */ + SYS_LOG_ERR("State other than downloaded: %d", state); + return -EPERM; + } + + 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 +302,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 +324,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 3a8e29e057a67..9da8d85572e66 100644 --- a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -4,35 +4,30 @@ * 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 "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) -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 net_context *firmware_net_ctx; static struct k_delayed_work retransmit_work; @@ -62,11 +57,13 @@ static void retransmit_request(struct k_work *work) r = lwm2m_udp_sendto(pending->pkt, &pending->addr); if (r < 0) { + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); return; } if (!zoap_pending_cycle(pending)) { zoap_pending_clear(pending); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); return; } @@ -82,6 +79,11 @@ static int transfer_request(struct zoap_block_context *ctx, struct zoap_pending *pending = NULL; struct zoap_reply *reply = NULL; int ret; + int i; + int path_len; + char *cursor; + u16_t off; + u16_t len; ret = lwm2m_init_message(firmware_net_ctx, &request, &pkt, ZOAP_TYPE_CON, ZOAP_METHOD_GET, @@ -90,14 +92,44 @@ 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(&request, ZOAP_OPTION_URI_PATH, - "large-create", sizeof("large-create") - 1); - ret = zoap_add_option(&request, 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(&request, + 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(&request, 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(&request, ctx); @@ -106,6 +138,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(&request, ZOAP_OPTION_SIZE2, 0); + if (ret) { + SYS_LOG_ERR("Unable to add size2 option."); + goto cleanup; + } + pending = lwm2m_init_message_pending(&request, &firmware_addr, pendings, NUM_PENDINGS); if (!pending) { @@ -140,6 +179,13 @@ static int transfer_request(struct zoap_block_context *ctx, cleanup: lwm2m_init_message_cleanup(pkt, pending, reply); + + if (ret == -ENOMEM) { + lwm2m_firmware_set_update_result(RESULT_OUT_OF_MEM); + } else { + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); + } + return ret; } @@ -156,67 +202,69 @@ 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; } -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) { #if defined(CONFIG_NET_IPV6) @@ -228,44 +276,112 @@ static void firmware_transfer(struct k_work *work) .sin_family = AF_INET }; #endif struct net_if *iface; - 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) { + 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; + } + + 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; + + /* IPv6 is wrapped by brackets */ + if (off > 0 && firmware_uri[off - 1] == '[') { + if (!IS_ENABLED(CONFIG_NET_IPV6)) { + SYS_LOG_ERR("Doesn't support IPv6"); + lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO); + return; + } + + family = AF_INET6; + } else { + /* Distinguish IPv4 or DNS */ + for (int i = off; i < off + len; i++) { + if (!isdigit(firmware_uri[i]) && + firmware_uri[i] != '.') { + SYS_LOG_ERR("Doesn't support DNS lookup"); + lwm2m_firmware_set_update_result( + RESULT_UNSUP_PROTO); + return; + } + } + + if (!IS_ENABLED(CONFIG_NET_IPV4)) { + SYS_LOG_ERR("Doesn't support IPv4"); + lwm2m_firmware_set_update_result(RESULT_UNSUP_PROTO); + return; + } + + family = AF_INET; + } + + tmp = firmware_uri[off + len]; + firmware_uri[off + len] = '\0'; + + if (IS_ENABLED(CONFIG_NET_IPV6) && 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_addr_pton(firmware_addr.sa_family, firmware_uri + off, &net_sin6(&firmware_addr)->sin6_addr); - net_sin6(&firmware_addr)->sin6_port = htons(5685); + net_sin6(&firmware_addr)->sin6_port = htons(parsed_uri.port); } -#endif -#if defined(CONFIG_NET_IPV4) - if (family == AF_INET) { + if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { firmware_addr.sa_family = family; - net_addr_pton(firmware_addr.sa_family, firmware_uri, + net_addr_pton(firmware_addr.sa_family, firmware_uri + off, &net_sin(&firmware_addr)->sin_addr); - net_sin(&firmware_addr)->sin_port = htons(5685); + net_sin(&firmware_addr)->sin_port = htons(parsed_uri.port); } -#endif + + /* restore firmware_uri */ + firmware_uri[off + len] = tmp; ret = net_context_get(firmware_addr.sa_family, SOCK_DGRAM, IPPROTO_UDP, &firmware_net_ctx); 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; } iface = net_if_get_default(); if (!iface) { - NET_ERR("Could not find default interface"); - goto cleanup; + SYS_LOG_ERR("Could not find default interface"); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); + return; } #if defined(CONFIG_NET_IPV6) @@ -275,7 +391,6 @@ static void firmware_transfer(struct k_work *work) sizeof(any_addr6)); } #endif - #if defined(CONFIG_NET_IPV4) if (firmware_addr.sa_family == AF_INET) { ret = net_context_bind(firmware_net_ctx, @@ -286,20 +401,22 @@ static void firmware_transfer(struct k_work *work) if (ret) { NET_ERR("Could not bind the UDP context (err:%d)", ret); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); goto cleanup; } - SYS_LOG_DBG("Attached to port: %d", port); + SYS_LOG_DBG("Attached to port: %d", parsed_uri.port); ret = net_context_recv(firmware_net_ctx, firmware_udp_receive, 0, NULL); if (ret) { SYS_LOG_ERR("Could not set receive for net context (err:%d)", ret); + lwm2m_firmware_set_update_result(RESULT_CONNECTION_LOST); goto cleanup; } /* 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; @@ -323,15 +440,14 @@ int lwm2m_firmware_start_transfer(char *package_uri) net_context_put(firmware_net_ctx); } - if (transfer_state == STATE_IDLE) { - k_work_init(&firmware_work, firmware_transfer); - k_delayed_work_init(&retransmit_work, retransmit_request); + k_work_init(&firmware_work, firmware_transfer); + k_delayed_work_init(&retransmit_work, retransmit_request); - /* start file transfer work */ - strncpy(firmware_uri, package_uri, PACKAGE_URI_LEN - 1); - k_work_submit(&firmware_work); - return 0; - } + 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 -1; + return 0; }