From 10d897adb6df302d2664ca04bdc5e91f9e27d55d Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 7 Jul 2017 11:04:01 -0700 Subject: [PATCH 1/9] misc/byteorder: add support for __bswap_64 We currently support converting from cpu format to BE for u16_t and u32_t. Let's add u64_t as well. NOTE: This will be used in LWM2M subsys later. Signed-off-by: Michael Scott --- include/misc/byteorder.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/misc/byteorder.h b/include/misc/byteorder.h index bcd160671c309..fc10852bb9bb2 100644 --- a/include/misc/byteorder.h +++ b/include/misc/byteorder.h @@ -21,6 +21,14 @@ (((x) >> 8) & 0xff00) | \ (((x) & 0xff00) << 8) | \ (((x) & 0xff) << 24))) +#define __bswap_64(x) ((u64_t) ((((x) >> 56) & 0xff) | \ + (((x) >> 40) & 0xff00) | \ + (((x) >> 24) & 0xff0000) | \ + (((x) >> 8) & 0xff000000) | \ + (((x) & 0xff000000) << 8) | \ + (((x) & 0xff0000) << 24) | \ + (((x) & 0xff00) << 40) | \ + (((x) & 0xff) << 56))) /** @def sys_le16_to_cpu * @brief Convert 16-bit integer from little-endian to host endianness. @@ -93,8 +101,12 @@ #define sys_cpu_to_be16(val) __bswap_16(val) #define sys_le32_to_cpu(val) (val) #define sys_cpu_to_le32(val) (val) +#define sys_le64_to_cpu(val) (val) +#define sys_cpu_to_le64(val) (val) #define sys_be32_to_cpu(val) __bswap_32(val) #define sys_cpu_to_be32(val) __bswap_32(val) +#define sys_be64_to_cpu(val) __bswap_64(val) +#define sys_cpu_to_be64(val) __bswap_64(val) #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define sys_le16_to_cpu(val) __bswap_16(val) #define sys_cpu_to_le16(val) __bswap_16(val) @@ -102,8 +114,12 @@ #define sys_cpu_to_be16(val) (val) #define sys_le32_to_cpu(val) __bswap_32(val) #define sys_cpu_to_le32(val) __bswap_32(val) +#define sys_le64_to_cpu(val) __bswap_64(val) +#define sys_cpu_to_le64(val) __bswap_64(val) #define sys_be32_to_cpu(val) (val) #define sys_cpu_to_be32(val) (val) +#define sys_be64_to_cpu(val) (val) +#define sys_cpu_to_be64(val) (val) #else #error "Unknown byte order" #endif From de8fbcc0a803e75a96d1444dcded2a14307348a9 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 7 Jul 2017 11:04:02 -0700 Subject: [PATCH 2/9] net: zoap: use message id for reply matching In the 08 Feb 2017 V1.0 LwM2M specification page 80 mentions: in response to a "Notify" operation for which it is not interested in any more, the LwM2M Server can send a "Reset Message". Leshan server sends this CoAP RST response and it does not contain the originating message token (which is also how the packet flow looks on page 81 of the LwM2M spec). Using the current ZoAP sources, the client has no way of matching back to observation which needs to be cancelled. Let's add a match for message ID of a reply where there is no token to handle this case. Signed-off-by: Michael Scott [ricardo.salveti@linaro.org: Handle both piggybackend and separate response (id doesn't need to match, only token).] Signed-off-by: Ricardo Salveti --- include/net/zoap.h | 1 + subsys/net/lib/zoap/zoap.c | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/net/zoap.h b/include/net/zoap.h index 63de763599401..e3d3e98631c6b 100644 --- a/include/net/zoap.h +++ b/include/net/zoap.h @@ -231,6 +231,7 @@ struct zoap_reply { void *user_data; int age; u8_t token[8]; + u16_t id; u8_t tkl; }; diff --git a/subsys/net/lib/zoap/zoap.c b/subsys/net/lib/zoap/zoap.c index f86fadf19f900..dbff2337a4a8c 100644 --- a/subsys/net/lib/zoap/zoap.c +++ b/subsys/net/lib/zoap/zoap.c @@ -615,18 +615,26 @@ struct zoap_reply *zoap_response_received( { struct zoap_reply *r; const u8_t *token; + u16_t id; u8_t tkl; size_t i; + id = zoap_header_get_id(response); token = zoap_header_get_token(response, &tkl); for (i = 0, r = replies; i < len; i++, r++) { int age; - if (r->tkl != tkl) { + if ((r->id == 0) && (r->tkl == 0)) { continue; } + /* Piggybacked must match id when token is empty */ + if ((r->id != id) && (tkl == 0)) { + continue; + } + + /* Separate response must only match token */ if (tkl > 0 && memcmp(r->token, token, tkl)) { continue; } @@ -658,6 +666,7 @@ void zoap_reply_init(struct zoap_reply *reply, u8_t tkl; int age; + reply->id = zoap_header_get_id(request); token = zoap_header_get_token(request, &tkl); if (tkl > 0) { @@ -675,6 +684,8 @@ void zoap_reply_init(struct zoap_reply *reply, void zoap_reply_clear(struct zoap_reply *reply) { + reply->id = 0; + reply->tkl = 0; reply->reply = NULL; } From 4e06a923b8ef009dbe82add70f6f39594c1dbfca Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 7 Jul 2017 11:04:03 -0700 Subject: [PATCH 3/9] net: lwm2m: initial library support for LWM2M Origin: SICS-IoT / Contiki OS URL: https://github.com/sics-iot/lwm2m-contiki/tree/lwm2m-standalone-dtls commit: d07b0bcd77ec7e8b93787669507f3d86cfbea64a Purpose: Introduction of LwM2M client library. Maintained-by: Zephyr Lightweight Machine-to-Machine (LwM2M) is a protocol stack extension of the Constrained Application Protocol (CoAP) which uses UDP transmission packets. This library was based on source worked on by Joakim Eriksson, Niclas Finne and Joel Hoglund which was adopted by Contiki and then later revamped to work as a stand-alone library. A VERY high level summary of the changes made: - [ALL] sources were re-formatted to Zephyr coding standards - [engine] The engine portion was re-written due to the heavy reliance on ER-CoAP APIs which are not compatible to the Zephyr CoAP APIs as well as other Zephyr specific needs. - [engine] All LWM2M/IPSO object data is now abstracted into resource data which stores information like the data type, length, callbacks to help with read/write. The engine modifies this data directly (or makes callbacks) instead of all of the logic for this living in each object's code. (This wasn't scaling well as I was implementing changes). - [engine] Related to the above change, I also added a generic set of getter/setter functions that user applications can call to change the object data instead of having to add getter/setting methods in each object. - [engine] The original sources shared the engine's context structure quite extensively causing a problem with portability. I broke up the context into it's individual parts: LWM2M path data, input data and output data and pass only the needed data into each set of APIs. - [content format read/writer] sources were re-organized into single .c/h files per content formatter. - [content format read/writer] sources were re-written where necessary to remove the sharing of the lwm2m engine's context and instead only requires the path and input or output data specific to it's function. - [LwM2M objects] re-written using the new engine's abstractions Signed-off-by: Michael Scott --- doc/subsystems/networking/overview.rst | 6 + include/net/lwm2m.h | 155 + subsys/net/lib/Kbuild | 1 + subsys/net/lib/Kconfig | 2 + subsys/net/lib/Makefile | 4 + subsys/net/lib/lwm2m/Kconfig | 144 + subsys/net/lib/lwm2m/Makefile | 27 + subsys/net/lib/lwm2m/lwm2m_engine.c | 2516 +++++++++++++++++ subsys/net/lib/lwm2m/lwm2m_engine.h | 60 + subsys/net/lib/lwm2m/lwm2m_obj_device.c | 382 +++ subsys/net/lib/lwm2m/lwm2m_obj_firmware.c | 157 + .../net/lib/lwm2m/lwm2m_obj_firmware_pull.c | 322 +++ subsys/net/lib/lwm2m/lwm2m_obj_security.c | 128 + subsys/net/lib/lwm2m/lwm2m_obj_server.c | 184 ++ subsys/net/lib/lwm2m/lwm2m_object.h | 408 +++ subsys/net/lib/lwm2m/lwm2m_rd_client.c | 877 ++++++ subsys/net/lib/lwm2m/lwm2m_rd_client.h | 36 + subsys/net/lib/lwm2m/lwm2m_rw_json.c | 639 +++++ subsys/net/lib/lwm2m/lwm2m_rw_json.h | 56 + subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c | 707 +++++ subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h | 72 + subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c | 434 +++ subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h | 54 + 23 files changed, 7371 insertions(+) create mode 100644 include/net/lwm2m.h create mode 100644 subsys/net/lib/lwm2m/Kconfig create mode 100644 subsys/net/lib/lwm2m/Makefile create mode 100644 subsys/net/lib/lwm2m/lwm2m_engine.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_engine.h create mode 100644 subsys/net/lib/lwm2m/lwm2m_obj_device.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_obj_firmware.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_obj_security.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_obj_server.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_object.h create mode 100644 subsys/net/lib/lwm2m/lwm2m_rd_client.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_rd_client.h create mode 100644 subsys/net/lib/lwm2m/lwm2m_rw_json.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_rw_json.h create mode 100644 subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h create mode 100644 subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c create mode 100644 subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h diff --git a/doc/subsystems/networking/overview.rst b/doc/subsystems/networking/overview.rst index a8043d04a6c6a..eb129e5d6cd31 100644 --- a/doc/subsystems/networking/overview.rst +++ b/doc/subsystems/networking/overview.rst @@ -65,6 +65,12 @@ can be disabled if not needed. :ref:`coap-server-sample` using DTLS (Datagram Transport Layer Security) (RFC 6347) are also implemented. +* **LWM2M** OMA Lightweight Machine-to-Machine Protocol (V1.0 Feb 2017) is + supported via the "Register Device" API (Register, De-Register and Update) + and has template implementations for Securty, Server, Device Management and + Firmware objects. DTLS and Bootstrap support are currently not supported. + :ref:`lwm2m-client-sample` implements the library as an example. + * **RPL** IPv6 Routing Protocol for Low-Power and Lossy Networks (RFC 6550) is supported. RPL is an IPv6 based mesh routing protocol. diff --git a/include/net/lwm2m.h b/include/net/lwm2m.h new file mode 100644 index 0000000000000..73f9490fa774b --- /dev/null +++ b/include/net/lwm2m.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __LWM2M_H__ +#define __LWM2M_H__ + +#include + +/* LWM2M Objects defined by OMA */ + +#define LWM2M_OBJECT_SECURITY_ID 0 +#define LWM2M_OBJECT_SERVER_ID 1 +#define LWM2M_OBJECT_ACCESS_CONTROL_ID 2 +#define LWM2M_OBJECT_DEVICE_ID 3 +#define LWM2M_OBJECT_CONNECTIVITY_MONITORING_ID 4 +#define LWM2M_OBJECT_FIRMWARE_ID 5 +#define LWM2M_OBJECT_LOCATION_ID 6 +#define LWM2M_OBJECT_CONNECTIVITY_STATISTICS_ID 7 + +/* callback can return 1 if handled (don't update value) */ +typedef void *(*lwm2m_engine_get_data_cb_t)(u16_t obj_inst_id, + size_t *data_len); +typedef int (*lwm2m_engine_set_data_cb_t)(u16_t obj_inst_id, + u8_t *data, u16_t data_len, + bool last_block, size_t total_size); +typedef int (*lwm2m_engine_exec_cb_t)(u16_t obj_inst_id); + + +/* LWM2M Device Object */ + +#define LWM2M_DEVICE_PWR_SRC_TYPE_DC_POWER 0 +#define LWM2M_DEVICE_PWR_SRC_TYPE_BAT_INT 1 +#define LWM2M_DEVICE_PWR_SRC_TYPE_BAT_EXT 2 +#define LWM2M_DEVICE_PWR_SRC_TYPE_UNUSED 3 +#define LWM2M_DEVICE_PWR_SRC_TYPE_PWR_OVER_ETH 4 +#define LWM2M_DEVICE_PWR_SRC_TYPE_USB 5 +#define LWM2M_DEVICE_PWR_SRC_TYPE_AC_POWER 6 +#define LWM2M_DEVICE_PWR_SRC_TYPE_SOLAR 7 +#define LWM2M_DEVICE_PWR_SRC_TYPE_MAX 8 + +#define LWM2M_DEVICE_ERROR_NONE 0 +#define LWM2M_DEVICE_ERROR_LOW_POWER 1 +#define LWM2M_DEVICE_ERROR_EXT_POWER_SUPPLY_OFF 2 +#define LWM2M_DEVICE_ERROR_GPS_FAILURE 3 +#define LWM2M_DEVICE_ERROR_LOW_SIGNAL_STRENGTH 4 +#define LWM2M_DEVICE_ERROR_OUT_OF_MEMORY 5 +#define LWM2M_DEVICE_ERROR_SMS_FAILURE 6 +#define LWM2M_DEVICE_ERROR_NETWORK_FAILURE 7 +#define LWM2M_DEVICE_ERROR_PERIPHERAL_FAILURE 8 + +#define LWM2M_DEVICE_BATTERY_STATUS_NORMAL 0 +#define LWM2M_DEVICE_BATTERY_STATUS_CHARGING 1 +#define LWM2M_DEVICE_BATTERY_STATUS_CHARGE_COMP 2 +#define LWM2M_DEVICE_BATTERY_STATUS_DAMAGED 3 +#define LWM2M_DEVICE_BATTERY_STATUS_LOW 4 +#define LWM2M_DEVICE_BATTERY_STATUS_NOT_INST 5 +#define LWM2M_DEVICE_BATTERY_STATUS_UNKNOWN 6 + +int lwm2m_device_add_pwrsrc(u8_t pwr_src_type); /* returns index */ +int lwm2m_device_remove_pwrsrc(int index); +int lwm2m_device_set_pwrsrc_voltage_mv(int index, int voltage_mv); +int lwm2m_device_set_pwrsrc_current_ma(int index, int current_ma); +int lwm2m_device_add_err(u8_t error_code); + + +/* LWM2M Firemware Update Object */ + +#define STATE_IDLE 0 +#define STATE_DOWNLOADING 1 +#define STATE_DOWNLOADED 2 +#define STATE_UPDATING 3 + +#define RESULT_DEFAULT 0 +#define RESULT_SUCCESS 1 +#define RESULT_NO_STORAGE 2 +#define RESULT_OUT_OF_MEM 3 +#define RESULT_CONNECTION_LOST 4 +#define RESULT_INTEGRITY_FAILED 5 +#define RESULT_UNSUP_FW 6 +#define RESULT_INVALID_URI 7 +#define RESULT_UPDATE_FAILED 8 +#define RESULT_UNSUP_PROTO 9 + +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); + + +/* LWM2M Engine */ + +/* + * float type below use the following logic: + * val1 is the whole number portion of decimal + * val2 is the decimal portion *1000000 for 32bit, *1000000000 for 64bit + * Example: 123.456 == val1: 123, val2:456000 + * Example: 123.000456 = val1: 123, val2:456 + */ +typedef struct float32_value { + s32_t val1; + s32_t val2; +} float32_value_t; + +typedef struct float64_value { + s64_t val1; + s64_t val2; +} float64_value_t; + +int lwm2m_engine_create_obj_inst(char *pathstr); + +int lwm2m_engine_set_string(char *path, char *data_ptr); +int lwm2m_engine_set_u8(char *path, u8_t value); +int lwm2m_engine_set_u16(char *path, u16_t value); +int lwm2m_engine_set_u32(char *path, u32_t value); +int lwm2m_engine_set_u64(char *path, u64_t value); +int lwm2m_engine_set_s8(char *path, s8_t value); +int lwm2m_engine_set_s16(char *path, s16_t value); +int lwm2m_engine_set_s32(char *path, s32_t value); +int lwm2m_engine_set_s64(char *path, s64_t value); +int lwm2m_engine_set_bool(char *path, bool value); +int lwm2m_engine_set_float32(char *pathstr, float32_value_t *value); +int lwm2m_engine_set_float64(char *pathstr, float64_value_t *value); + +int lwm2m_engine_get_string(char *path, void *str, u16_t strlen); +u8_t lwm2m_engine_get_u8(char *path); +u16_t lwm2m_engine_get_u16(char *path); +u32_t lwm2m_engine_get_u32(char *path); +u64_t lwm2m_engine_get_u64(char *path); +s8_t lwm2m_engine_get_s8(char *path); +s16_t lwm2m_engine_get_s16(char *path); +s32_t lwm2m_engine_get_s32(char *path); +s64_t lwm2m_engine_get_s64(char *path); +bool lwm2m_engine_get_bool(char *path); +int lwm2m_engine_get_float32(char *pathstr, float32_value_t *buf); +int lwm2m_engine_get_float64(char *pathstr, float64_value_t *buf); + +int lwm2m_engine_register_read_callback(char *path, + lwm2m_engine_get_data_cb_t cb); +int lwm2m_engine_register_pre_write_callback(char *path, + lwm2m_engine_get_data_cb_t cb); +int lwm2m_engine_register_post_write_callback(char *path, + lwm2m_engine_set_data_cb_t cb); +int lwm2m_engine_register_exec_callback(char *path, + lwm2m_engine_exec_cb_t cb); + +int lwm2m_engine_start(struct net_context *net_ctx); + +/* LWM2M RD Client */ + +int lwm2m_rd_client_start(struct net_context *net_ctx, + struct sockaddr *peer_addr, + const char *ep_name); + +#endif /* __LWM2M_H__ */ diff --git a/subsys/net/lib/Kbuild b/subsys/net/lib/Kbuild index 3a06bf9f9f6be..cc297ac8d350a 100644 --- a/subsys/net/lib/Kbuild +++ b/subsys/net/lib/Kbuild @@ -4,3 +4,4 @@ obj-$(CONFIG_DNS_RESOLVER) += dns/ obj-$(CONFIG_MQTT_LIB) += mqtt/ obj-$(CONFIG_HTTP) += http/ obj-$(CONFIG_NET_APP_SETTINGS) += app/ +obj-$(CONFIG_LWM2M) += lwm2m/ diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index 96b9091ff9627..bd1c8f0b03292 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -16,6 +16,8 @@ source "subsys/net/lib/mqtt/Kconfig" source "subsys/net/lib/http/Kconfig" +source "subsys/net/lib/lwm2m/Kconfig" + endmenu menu "Network Applications" diff --git a/subsys/net/lib/Makefile b/subsys/net/lib/Makefile index 158cb29fd0a09..d90e6d6b24976 100644 --- a/subsys/net/lib/Makefile +++ b/subsys/net/lib/Makefile @@ -21,3 +21,7 @@ endif ifdef CONFIG_NET_APP_SETTINGS include $(srctree)/subsys/net/lib/app/Makefile endif + +ifdef CONFIG_LWM2M +include $(srctree)/subsys/net/lib/lwm2m/Makefile +endif diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig new file mode 100644 index 0000000000000..8d12c5d5b0e7d --- /dev/null +++ b/subsys/net/lib/lwm2m/Kconfig @@ -0,0 +1,144 @@ +# +# Copyright (c) 2017 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig LWM2M + bool "OMA LWM2M protocol stack" + default n + select ZOAP + help + This option adds logic for managing OMA LWM2M data + +if LWM2M + +config SYS_LOG_LWM2M_LEVEL + int "LWM2M log level" + depends on SYS_LOG + default 1 + range 0 4 + help + Set the log level for the LWM2M library. + +config LWM2M_ENGINE_STACK_SIZE + int "LWM2M engine stack size" + default 1024 + help + Set the stack size for the LWM2M library engine (used for handling + OBSERVE and NOTIFY events) + +config LWM2M_ENGINE_MAX_PENDING + int "LWM2M engine max. pending objects" + default 5 + help + Set the maximum pending objects for the LWM2M library client + +config LWM2M_ENGINE_MAX_REPLIES + int "LWM2M engine max. reply objects" + default 5 + help + Set the maximum reply objects for the LWM2M library client + +config LWM2M_ENGINE_MAX_OBSERVER + int "Maximum # of observeable LWM2M resources" + default 50 + range 10 200 + help + This value sets the maximum number of resources which can be + added to the observe notification list. + +config LWM2M_ENGINE_DEFAULT_LIFETIME + int "LWM2M engine default server connection lifetime" + default 30 + help + Set the default lifetime (in seconds) for the LWM2M library engine + +config LWM2M_LOCAL_PORT + int "LWM2M client port" + default 5683 + help + This is the client port for LWM2M communication (0 = random, defaults + to 5683) + +config LWM2M_SECURITY_INSTANCE_COUNT + int "Maximum # of LWM2M Security object instances" + default 3 + range 1 10 + help + This setting establishes the total count of LWM2M Security instances + available to the client. + +config LWM2M_SERVER_INSTANCE_COUNT + int "Maximum # of LWM2M Server object instances" + default 3 + range 1 10 + help + This setting establishes the total count of LWM2M Server instances + available to the client (including: bootstrap and regular servers). + +config LWM2M_RD_CLIENT_SUPPORT + bool "support for LWM2M client bootstrap/registration state machine" + default y + help + Client will use registration state machine to locate and connect to + LWM2M servers (including bootstrap server support) + +config LWM2M_RD_CLIENT_STACK_SIZE + int "LWM2M RD client stack size" + default 1024 + help + Set the stack size for the LWM2M RD client + +config LWM2M_RD_CLIENT_INSTANCE_COUNT + int "Maximum # of LWM2M RD client instances" + default 2 + help + This setting establishes the total count of LWM2M RD client instances + available. + +config LWM2M_PEER_PORT + int "LWM2M server port" + depends on LWM2M_RD_CLIENT_SUPPORT + default 5683 + help + This is the server port to connect to for LWM2M communication + +config LWM2M_BOOTSTRAP_PORT + int "LWM2M bootstrap port" + depends on LWM2M_RD_CLIENT_SUPPORT + default 5684 + help + This is the bootstrap port to connect to for security configuration + + +config LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT + bool "Firmware Update object support" + default y + help + Include support for LWM2M Firmware Update Object (ID 5) + +config LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT + bool "Firmware Update object pull support" + default y + depends on LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT + 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_RW_JSON_SUPPORT + bool "support for JSON writer" + default y + help + Include support for writing JSON data + +config LWM2M_DEVICE_ERROR_CODE_MAX + int "Maximum # of device obj error codes to store" + default 10 + range 1 20 + help + This value sets the maximum number of error codes that the device + object will store before ignoring new values. + +endif # LWM2M diff --git a/subsys/net/lib/lwm2m/Makefile b/subsys/net/lib/lwm2m/Makefile new file mode 100644 index 0000000000000..63ad267a9ca8a --- /dev/null +++ b/subsys/net/lib/lwm2m/Makefile @@ -0,0 +1,27 @@ +# Makefile - LWM2M library + +# +# Copyright (c) 2017 Linaro +# +# SPDX-License-Identifier: Apache-2.0 +# + +ccflags-y += -I$(srctree)/subsys/net/lib/lwm2m + +obj-y := \ + lwm2m_engine.o \ + lwm2m_obj_security.o \ + lwm2m_obj_server.o \ + lwm2m_obj_device.o \ + lwm2m_rw_plain_text.o \ + lwm2m_rw_oma_tlv.o + +# LWM2M RD Client Support +obj-$(CONFIG_LWM2M_RD_CLIENT_SUPPORT) += lwm2m_rd_client.o + +# LWM2M Object Support +obj-$(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) += lwm2m_obj_firmware.o +obj-$(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) += lwm2m_obj_firmware_pull.o + +# JSON Support +obj-$(CONFIG_LWM2M_RW_JSON_SUPPORT) += lwm2m_rw_json.o diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c new file mode 100644 index 0000000000000..5d271a4ed755e --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_engine.c @@ -0,0 +1,2516 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Uses some original concepts by: + * Joakim Eriksson + * Niclas Finne + * Joel Hoglund + */ + +/* + * TODO: + * + * - 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 + */ + +#define SYS_LOG_DOMAIN "lib/lwm2m_engine" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" +#include "lwm2m_rw_plain_text.h" +#include "lwm2m_rw_oma_tlv.h" +#ifdef CONFIG_LWM2M_RW_JSON_SUPPORT +#include "lwm2m_rw_json.h" +#endif +#ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT +#include "lwm2m_rd_client.h" +#endif + +#define ENGINE_UPDATE_INTERVAL 500 + +/* LWM2M / CoAP Content-Formats */ +#define LWM2M_FORMAT_PLAIN_TEXT 0 +#define LWM2M_FORMAT_APP_LINK_FORMAT 40 +#define LWM2M_FORMAT_APP_OCTET_STREAM 42 +#define LWM2M_FORMAT_APP_EXI 47 +#define LWM2M_FORMAT_APP_JSON 50 +#define LWM2M_FORMAT_OMA_PLAIN_TEXT 1541 +#define LWM2M_FORMAT_OMA_OLD_TLV 1542 +#define LWM2M_FORMAT_OMA_OLD_JSON 1543 +#define LWM2M_FORMAT_OMA_OLD_OPAQUE 1544 +#define LWM2M_FORMAT_OMA_TLV 11542 +#define LWM2M_FORMAT_OMA_JSON 11543 + +#define DISCOVER_PREFACE ";ct=40" + +#define BUF_ALLOC_TIMEOUT K_SECONDS(1) + +struct observe_node { + sys_snode_t node; + struct net_context *net_ctx; + struct sockaddr addr; + struct lwm2m_obj_path path; + u8_t token[8]; + s64_t event_timestamp; + s64_t last_timestamp; + u32_t min_period_sec; + u32_t max_period_sec; + u32_t counter; + bool used; + u8_t tkl; +}; + +#define NUM_PENDINGS CONFIG_LWM2M_ENGINE_MAX_PENDING +#define NUM_REPLIES CONFIG_LWM2M_ENGINE_MAX_REPLIES + +struct zoap_pending pendings[NUM_PENDINGS]; +struct zoap_reply replies[NUM_REPLIES]; +struct k_delayed_work retransmit_work; + +static struct observe_node observe_node_data[CONFIG_LWM2M_ENGINE_MAX_OBSERVER]; + +static sys_slist_t engine_obj_list; +static sys_slist_t engine_obj_inst_list; +static sys_slist_t engine_observer_list; + +/* periodic / notify / observe handling stack */ +static K_THREAD_STACK_DEFINE(engine_thread_stack, + CONFIG_LWM2M_ENGINE_STACK_SIZE); +static struct k_thread engine_thread_data; + +/* for debugging: to print IP addresses */ +char *lwm2m_sprint_ip_addr(const struct sockaddr *addr) +{ + static char buf[NET_IPV6_ADDR_LEN]; + +#if defined(CONFIG_NET_IPV6) + if (addr->family == AF_INET6) { + return net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, + buf, sizeof(buf)); + } +#endif +#if defined(CONFIG_NET_IPV4) + if (addr->family == AF_INET) { + return net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, + buf, sizeof(buf)); + } +#endif + + SYS_LOG_ERR("Unknown IP address family:%d", addr->family); + return NULL; +} + +static char *sprint_token(const u8_t *token, u8_t tkl) +{ + int i; + static char buf[32]; + int pos = 0; + + for (i = 0; i < tkl; i++) { + pos += snprintf(&buf[pos], 31 - pos, "%x", token[i]); + } + buf[pos] = '\0'; + return buf; +} + +/* observer functions */ + +int lwm2m_notify_observer(u16_t obj_id, u16_t obj_inst_id, u16_t res_id) +{ + struct observe_node *obs; + int ret = 0; + + /* look for observers which match our resource */ + SYS_SLIST_FOR_EACH_CONTAINER(&engine_observer_list, obs, node) { + if (obs->path.obj_id == obj_id && + obs->path.obj_inst_id == obj_inst_id && + (obs->path.level < 3 || + obs->path.res_id == res_id)) { + /* update the event time for this observer */ + obs->event_timestamp = k_uptime_get(); + + SYS_LOG_DBG("NOTIFY EVENT %u/%u/%u", + obj_id, obj_inst_id, res_id); + + ret++; + } + } + + return ret; +} + +int lwm2m_notify_observer_path(struct lwm2m_obj_path *path) +{ + return lwm2m_notify_observer(path->obj_id, path->obj_inst_id, + path->res_id); +} + +static int engine_add_observer(struct net_context *net_ctx, + struct sockaddr *addr, + const u8_t *token, u8_t tkl, + struct lwm2m_obj_path *path) +{ + struct observe_node *obs; + int i; + + if (!addr) { + SYS_LOG_ERR("sockaddr is required"); + return -EINVAL; + } + + /* make sure this observer doesn't exist already */ + SYS_SLIST_FOR_EACH_CONTAINER(&engine_observer_list, obs, node) { + if (memcmp(&obs->addr, addr, sizeof(addr)) == 0 && + memcmp(&obs->path, path, sizeof(*path)) == 0) { + /* quietly update the token information */ + memcpy(obs->token, token, tkl); + obs->tkl = tkl; + + SYS_LOG_DBG("OBSERVER DUPLICATE %u/%u/%u(%u) [%s]", + path->obj_id, path->obj_inst_id, + path->res_id, path->level, + lwm2m_sprint_ip_addr(addr)); + + return 0; + } + } + + /* find an unused observer index node */ + for (i = 0; i < CONFIG_LWM2M_ENGINE_MAX_OBSERVER; i++) { + if (!observe_node_data[i].used) { + break; + } + } + + /* couldn't find an index */ + if (i == CONFIG_LWM2M_ENGINE_MAX_OBSERVER) { + return -ENOMEM; + } + + /* copy the values and add it to the list */ + observe_node_data[i].used = true; + observe_node_data[i].net_ctx = net_ctx; + memcpy(&observe_node_data[i].addr, addr, sizeof(*addr)); + memcpy(&observe_node_data[i].path, path, sizeof(*path)); + memcpy(observe_node_data[i].token, token, tkl); + observe_node_data[i].tkl = tkl; + observe_node_data[i].last_timestamp = k_uptime_get(); + observe_node_data[i].event_timestamp = + observe_node_data[i].last_timestamp; + /* TODO: use server object instance or WRITE_ATTR values */ + observe_node_data[i].min_period_sec = 10; + observe_node_data[i].max_period_sec = 60; + observe_node_data[i].counter = 1; + sys_slist_append(&engine_observer_list, + &observe_node_data[i].node); + + SYS_LOG_DBG("OBSERVER ADDED %u/%u/%u(%u) token:'%s' addr:%s", + path->obj_id, path->obj_inst_id, path->res_id, path->level, + sprint_token(token, tkl), lwm2m_sprint_ip_addr(addr)); + + return 0; +} + +static int engine_remove_observer(const u8_t *token, u8_t tkl) +{ + struct observe_node *obs, *found_obj = NULL; + + if (!token || tkl == 0) { + SYS_LOG_ERR("token(%p) and token length(%u) must be valid.", + token, tkl); + return -EINVAL; + } + + /* find the node index */ + SYS_SLIST_FOR_EACH_CONTAINER(&engine_observer_list, obs, node) { + if (memcmp(obs->token, token, tkl) == 0) { + found_obj = obs; + break; + } + } + + if (!found_obj) { + return -ENOENT; + } + + sys_slist_remove(&engine_observer_list, NULL, &found_obj->node); + + SYS_LOG_DBG("oberver '%s' removed", sprint_token(token, tkl)); + + return 0; +} + +/* engine object */ + +void lwm2m_register_obj(struct lwm2m_engine_obj *obj) +{ + sys_slist_append(&engine_obj_list, &obj->node); +} + +void lwm2m_unregister_obj(struct lwm2m_engine_obj *obj) +{ + /* TODO: remove all observer instances */ + sys_slist_remove(&engine_obj_list, NULL, &obj->node); +} + +static struct lwm2m_engine_obj *get_engine_obj(int obj_id) +{ + struct lwm2m_engine_obj *obj; + + SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) { + if (obj->obj_id == obj_id) { + return obj; + } + } + + return NULL; +} + +struct lwm2m_engine_obj_field * +lwm2m_get_engine_obj_field(struct lwm2m_engine_obj *obj, int res_id) +{ + int i; + + if (obj && obj->fields && obj->field_count > 0) { + for (i = 0; i < obj->field_count; i++) { + if (obj->fields[i].res_id == res_id) { + return &obj->fields[i]; + } + } + } + + return NULL; +} + +/* engine object instance */ + +static void engine_register_obj_inst(struct lwm2m_engine_obj_inst *obj_inst) +{ + sys_slist_append(&engine_obj_inst_list, &obj_inst->node); +} + +static void engine_unregister_obj_inst(struct lwm2m_engine_obj_inst *obj_inst) +{ + sys_slist_remove(&engine_obj_inst_list, NULL, &obj_inst->node); +} + +static struct lwm2m_engine_obj_inst *get_engine_obj_inst(int obj_id, + int obj_inst_id) +{ + struct lwm2m_engine_obj_inst *obj_inst; + + SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, + node) { + if (obj_inst->obj->obj_id == obj_id && + obj_inst->obj_inst_id == obj_inst_id) { + return obj_inst; + } + } + + return NULL; +} + +static struct lwm2m_engine_obj_inst * +next_engine_obj_inst(struct lwm2m_engine_obj_inst *last, + int obj_id, int obj_inst_id) +{ + while (last) { + last = SYS_SLIST_PEEK_NEXT_CONTAINER(last, node); + if (last && last->obj->obj_id == obj_id && + last->obj_inst_id == obj_inst_id) { + return last; + } + } + + return NULL; +} + +int lwm2m_create_obj_inst(u16_t obj_id, u16_t obj_inst_id, + struct lwm2m_engine_obj_inst **obj_inst) +{ + int i; + struct lwm2m_engine_obj *obj; + + *obj_inst = NULL; + obj = get_engine_obj(obj_id); + if (!obj) { + SYS_LOG_ERR("unable to find obj: %u", obj_id); + return -ENOENT; + } + + if (!obj->create_cb) { + SYS_LOG_ERR("obj %u has no create_cb", obj_id); + return -EINVAL; + } + + if (obj->instance_count + 1 > obj->max_instance_count) { + SYS_LOG_ERR("no more instances available for obj %u", obj_id); + return -ENOMEM; + } + + *obj_inst = obj->create_cb(obj_inst_id); + if (!*obj_inst) { + SYS_LOG_ERR("unable to create obj %u instance %u", + obj_id, obj_inst_id); + return -EINVAL; + } + + obj->instance_count++; + (*obj_inst)->obj = obj; + (*obj_inst)->obj_inst_id = obj_inst_id; + sprintf((*obj_inst)->path, "%u/%u", obj_id, obj_inst_id); + for (i = 0; i < (*obj_inst)->resource_count; i++) { + sprintf((*obj_inst)->resources[i].path, "%u/%u/%u", + obj_id, obj_inst_id, (*obj_inst)->resources[i].res_id); + } + + engine_register_obj_inst(*obj_inst); +#ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT + engine_trigger_update(); +#endif + return 0; +} + +int lwm2m_delete_obj_inst(u16_t obj_id, u16_t obj_inst_id) +{ + struct lwm2m_engine_obj *obj; + struct lwm2m_engine_obj_inst *obj_inst; + + obj = get_engine_obj(obj_id); + if (!obj) { + return -ENOENT; + } + + obj_inst = get_engine_obj_inst(obj_id, obj_inst_id); + if (!obj_inst) { + return -ENOENT; + } + + engine_unregister_obj_inst(obj_inst); + obj->instance_count--; + if (obj->delete_cb) { + return obj->delete_cb(obj_inst_id); + } + + return 0; +} + +/* utility functions */ + +static void engine_clear_context(struct lwm2m_engine_context *context) +{ + if (context->in) { + memset(context->in, 0, sizeof(struct lwm2m_input_context)); + } + + if (context->out) { + memset(context->out, 0, sizeof(struct lwm2m_output_context)); + } + + if (context->path) { + memset(context->path, 0, sizeof(struct lwm2m_obj_path)); + } + + context->operation = 0; +} + +static u16_t atou16(u8_t *buf, u16_t buflen, u16_t *len) +{ + u16_t val = 0; + u16_t pos = 0; + + /* we should get a value first - consume all numbers */ + while (pos < buflen && isdigit(buf[pos])) { + val = val * 10 + (buf[pos] - '0'); + pos++; + } + + *len = pos; + return val; +} + +static void zoap_options_to_path(struct zoap_option *opt, int options_count, + struct lwm2m_obj_path *path) +{ + u16_t len; + + path->level = options_count; + path->obj_id = atou16(opt[0].value, opt[0].len, &len); + if (len == 0) { + path->level = 0; + } + + if (path->level > 1) { + path->obj_inst_id = atou16(opt[1].value, opt[1].len, &len); + if (len == 0) { + path->level = 1; + } + } + + if (path->level > 2) { + path->res_id = atou16(opt[2].value, opt[2].len, &len); + if (len == 0) { + path->level = 2; + } + } + + if (path->level > 3) { + path->res_inst_id = atou16(opt[3].value, opt[3].len, &len); + if (len == 0) { + path->level = 3; + } + } +} + +int lwm2m_init_message(struct net_context *net_ctx, struct zoap_packet *zpkt, + struct net_pkt **pkt, u8_t type, u8_t code, u16_t mid, + const u8_t *token, u8_t tkl) +{ + struct net_buf *frag; + int r; + + *pkt = net_pkt_get_tx(net_ctx, BUF_ALLOC_TIMEOUT); + if (!*pkt) { + SYS_LOG_ERR("Unable to get TX packet, not enough memory."); + return -ENOMEM; + } + + frag = net_pkt_get_data(net_ctx, BUF_ALLOC_TIMEOUT); + if (!frag) { + SYS_LOG_ERR("Unable to get DATA buffer, not enough memory."); + net_pkt_unref(*pkt); + *pkt = NULL; + return -ENOMEM; + } + + net_pkt_frag_add(*pkt, frag); + + r = zoap_packet_init(zpkt, *pkt); + if (r < 0) { + SYS_LOG_ERR("zoap packet init error (err:%d)", r); + return r; + } + + /* FIXME: Could be that zoap_packet_init() sets some defaults */ + zoap_header_set_version(zpkt, 1); + zoap_header_set_type(zpkt, type); + zoap_header_set_code(zpkt, code); + + if (mid > 0) { + zoap_header_set_id(zpkt, mid); + } else { + zoap_header_set_id(zpkt, zoap_next_id()); + } + + /* tkl == 0 is for a new TOKEN, tkl == -1 means dont set */ + if (token && tkl > 0) { + zoap_header_set_token(zpkt, token, tkl); + } else if (tkl == 0) { + zoap_header_set_token(zpkt, zoap_next_token(), 8); + } + + return 0; +} + +struct zoap_pending *lwm2m_init_message_pending(struct zoap_packet *zpkt, + struct sockaddr *addr, + struct zoap_pending *zpendings, + int num_zpendings) +{ + struct zoap_pending *pending = NULL; + int ret; + + pending = zoap_pending_next_unused(zpendings, num_zpendings); + if (!pending) { + SYS_LOG_ERR("Unable to find a free pending to track " + "retransmissions."); + return NULL; + } + + ret = zoap_pending_init(pending, zpkt, addr); + if (ret < 0) { + SYS_LOG_ERR("Unable to initialize a pending " + "retransmission (err:%d).", ret); + pending->pkt = NULL; + return NULL; + } + + return pending; +} + +void lwm2m_init_message_cleanup(struct net_pkt *pkt, + struct zoap_pending *pending, + struct zoap_reply *reply) +{ + if (pending) { + zoap_pending_clear(pending); + /* don't unref attached pkt twice */ + if (!pending->pkt) { + pkt = NULL; + } + } + + if (reply) { + zoap_reply_clear(reply); + } + + if (pkt) { + net_pkt_unref(pkt); + } +} + +u16_t lwm2m_get_rd_data(u8_t *client_data, u16_t size) +{ + struct lwm2m_engine_obj *obj; + struct lwm2m_engine_obj_inst *obj_inst; + u8_t temp[32]; + u16_t pos = 0; + int len; + + SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) { + len = snprintf(temp, sizeof(temp), "%s", + (pos > 0) ? "," : "", obj->obj_id); + if (pos + len >= size) { + /* full buffer -- exit loop */ + break; + } + + memcpy(&client_data[pos], temp, len); + pos += len; + + SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, + obj_inst, node) { + if (obj_inst->obj->obj_id == obj->obj_id) { + len = snprintf(temp, sizeof(temp), + "%s", + (pos > 0) ? "," : "", + obj_inst->path); + /* + * TODO: iterate through resources once block + * transfer is handled correctly + */ + if (pos + len >= size) { + /* full buffer -- exit loop */ + break; + } + + memcpy(&client_data[pos], temp, len); + pos += len; + } + } + } + + client_data[pos] = '\0'; + return pos; +} + +/* input / output selection */ + +static u16_t select_writer(struct lwm2m_output_context *out, u16_t accept) +{ + switch (accept) { + + case LWM2M_FORMAT_PLAIN_TEXT: + case LWM2M_FORMAT_OMA_PLAIN_TEXT: + out->writer = &plain_text_writer; + break; + + case LWM2M_FORMAT_OMA_TLV: + case LWM2M_FORMAT_OMA_OLD_TLV: + out->writer = &oma_tlv_writer; + break; + +#ifdef CONFIG_LWM2M_RW_JSON_SUPPORT + case LWM2M_FORMAT_OMA_JSON: + case LWM2M_FORMAT_OMA_OLD_JSON: + out->writer = &json_writer; + break; +#endif + + default: + SYS_LOG_ERR("Unknown Accept type %u, using LWM2M plain text", + accept); + out->writer = &plain_text_writer; + accept = LWM2M_FORMAT_OMA_PLAIN_TEXT; + break; + + } + + return accept; +} + +static u16_t select_reader(struct lwm2m_input_context *in, u16_t format) +{ + switch (format) { + + case LWM2M_FORMAT_OMA_PLAIN_TEXT: + in->reader = &plain_text_reader; + break; + + case LWM2M_FORMAT_OMA_TLV: + case LWM2M_FORMAT_OMA_OLD_TLV: + in->reader = &oma_tlv_reader; + break; + + default: + SYS_LOG_ERR("Unknown content type %u, using LWM2M plain text", + format); + in->reader = &plain_text_reader; + format = LWM2M_FORMAT_OMA_PLAIN_TEXT; + break; + + } + + return format; +} + +/* user data setter functions */ + +static int string_to_path(char *pathstr, struct lwm2m_obj_path *path, + char delim) +{ + u16_t value, len; + int i, tokstart = -1, toklen; + int end_index = strlen(pathstr) - 1; + + for (i = 0; i <= end_index; i++) { + /* search for first numeric */ + if (tokstart == -1) { + if (!isdigit(pathstr[i])) { + continue; + } + + tokstart = i; + } + + /* find delimiter char or end of string */ + if (pathstr[i] == delim || i == end_index) { + toklen = i - tokstart + 1; + + /* don't process delimiter char */ + if (pathstr[i] == delim) { + toklen--; + } + + if (toklen <= 0) { + continue; + } + + value = atou16(&pathstr[tokstart], toklen, &len); + switch (path->level) { + + case 0: + path->obj_id = value; + break; + + case 1: + path->obj_inst_id = value; + break; + + case 2: + path->res_id = value; + break; + + case 3: + path->res_inst_id = value; + break; + + default: + SYS_LOG_ERR("invalid level (%d)", + path->level); + return -EINVAL; + + } + + /* increase the path level for each token found */ + path->level++; + tokstart = -1; + } + } + + return 0; +} + +int lwm2m_engine_create_obj_inst(char *pathstr) +{ + struct lwm2m_obj_path path; + struct lwm2m_engine_obj_inst *obj_inst; + int ret = 0; + + SYS_LOG_DBG("path:%s", pathstr); + + /* translate path -> path_obj */ + memset(&path, 0, sizeof(path)); + ret = string_to_path(pathstr, &path, '/'); + if (ret < 0) { + return ret; + } + + if (path.level != 2) { + SYS_LOG_ERR("path must have 2 parts"); + return -EINVAL; + } + + return lwm2m_create_obj_inst(path.obj_id, path.obj_inst_id, &obj_inst); +} + +static int lwm2m_engine_set(char *pathstr, void *value, u16_t len) +{ + int ret = 0, i; + struct lwm2m_obj_path path; + struct lwm2m_engine_obj_inst *obj_inst; + struct lwm2m_engine_obj_field *obj_field; + struct lwm2m_engine_res_inst *res = NULL; + bool changed = false; + void *data_ptr = NULL; + size_t data_len = 0; + + SYS_LOG_DBG("path:%s, value:%p, len:%d", pathstr, value, len); + + /* translate path -> path_obj */ + memset(&path, 0, sizeof(path)); + ret = string_to_path(pathstr, &path, '/'); + if (ret < 0) { + return ret; + } + + if (path.level < 3) { + SYS_LOG_ERR("path must have 3 parts"); + return -EINVAL; + } + + /* find obj_inst/res_id */ + obj_inst = get_engine_obj_inst(path.obj_id, path.obj_inst_id); + if (!obj_inst) { + SYS_LOG_ERR("obj instance %d/%d not found", + path.obj_id, path.obj_inst_id); + return -ENOENT; + } + + if (!obj_inst->resources || obj_inst->resource_count == 0) { + SYS_LOG_ERR("obj instance has no resources"); + return -EINVAL; + } + + obj_field = lwm2m_get_engine_obj_field(obj_inst->obj, path.res_id); + if (!obj_field) { + SYS_LOG_ERR("obj field %d not found", path.res_id); + return -ENOENT; + } + + for (i = 0; i < obj_inst->resource_count; i++) { + if (obj_inst->resources[i].res_id == path.res_id) { + res = &obj_inst->resources[i]; + break; + } + } + + if (!res) { + SYS_LOG_ERR("res instance %d not found", path.res_id); + return -ENOENT; + } + + /* setup initial data elements */ + data_ptr = res->data_ptr; + data_len = res->data_len; + + /* 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); + } + + if (!data_ptr) { + SYS_LOG_ERR("res data pointer is NULL"); + return -EINVAL; + } + + /* check length (note: we add 1 to string length for NULL pad) */ + if (len > res->data_len - + (obj_field->data_type == LWM2M_RES_TYPE_STRING ? 1 : 0)) { + SYS_LOG_ERR("length %u is too long for resource %d data", + len, path.res_id); + return -ENOMEM; + } + + if (memcmp(data_ptr, value, len) != 0) { + changed = true; + } + + switch (obj_field->data_type) { + + case LWM2M_RES_TYPE_STRING: + memcpy((u8_t *)data_ptr, value, len); + ((u8_t *)data_ptr)[len] = '\0'; + break; + + case LWM2M_RES_TYPE_U64: + *((u64_t *)data_ptr) = *(u64_t *)value; + break; + + case LWM2M_RES_TYPE_U32: + case LWM2M_RES_TYPE_TIME: + *((u32_t *)data_ptr) = *(u32_t *)value; + break; + + case LWM2M_RES_TYPE_U16: + *((u16_t *)data_ptr) = *(u16_t *)value; + break; + + case LWM2M_RES_TYPE_U8: + *((u8_t *)data_ptr) = *(u8_t *)value; + break; + + case LWM2M_RES_TYPE_S64: + *((s64_t *)data_ptr) = *(s64_t *)value; + break; + + case LWM2M_RES_TYPE_S32: + *((s32_t *)data_ptr) = *(s32_t *)value; + break; + + case LWM2M_RES_TYPE_S16: + *((s16_t *)data_ptr) = *(s16_t *)value; + break; + + case LWM2M_RES_TYPE_S8: + *((s8_t *)data_ptr) = *(s8_t *)value; + break; + + case LWM2M_RES_TYPE_BOOL: + *((bool *)data_ptr) = *(bool *)value; + break; + + case LWM2M_RES_TYPE_FLOAT32: + ((float32_value_t *)data_ptr)->val1 = + ((float32_value_t *)value)->val1; + ((float32_value_t *)data_ptr)->val2 = + ((float32_value_t *)value)->val2; + break; + + case LWM2M_RES_TYPE_FLOAT64: + ((float64_value_t *)data_ptr)->val1 = + ((float64_value_t *)value)->val1; + ((float64_value_t *)data_ptr)->val2 = + ((float64_value_t *)value)->val2; + break; + + default: + SYS_LOG_ERR("unknown obj data_type %d", + obj_field->data_type); + return -EINVAL; + + } + + if (res->post_write_cb) { + /* ignore return value here */ + res->post_write_cb(obj_inst->obj_inst_id, data_ptr, len, + false, 0); + } + + if (changed) { + NOTIFY_OBSERVER_PATH(&path); + } + + return ret; +} + +int lwm2m_engine_set_string(char *pathstr, char *data_ptr) +{ + return lwm2m_engine_set(pathstr, data_ptr, strlen(data_ptr)); +} + +int lwm2m_engine_set_u8(char *pathstr, u8_t value) +{ + return lwm2m_engine_set(pathstr, &value, 1); +} + +int lwm2m_engine_set_u16(char *pathstr, u16_t value) +{ + return lwm2m_engine_set(pathstr, &value, 2); +} + +int lwm2m_engine_set_u32(char *pathstr, u32_t value) +{ + return lwm2m_engine_set(pathstr, &value, 4); +} + +int lwm2m_engine_set_u64(char *pathstr, u64_t value) +{ + return lwm2m_engine_set(pathstr, &value, 8); +} + +int lwm2m_engine_set_s8(char *pathstr, s8_t value) +{ + return lwm2m_engine_set(pathstr, &value, 1); +} + +int lwm2m_engine_set_s16(char *pathstr, s16_t value) +{ + return lwm2m_engine_set(pathstr, &value, 2); +} + +int lwm2m_engine_set_s32(char *pathstr, s32_t value) +{ + return lwm2m_engine_set(pathstr, &value, 4); +} + +int lwm2m_engine_set_s64(char *pathstr, s64_t value) +{ + return lwm2m_engine_set(pathstr, &value, 8); +} + +int lwm2m_engine_set_bool(char *pathstr, bool value) +{ + u8_t temp = (value != 0 ? 1 : 0); + + return lwm2m_engine_set(pathstr, &temp, 1); +} + +int lwm2m_engine_set_float32(char *pathstr, float32_value_t *value) +{ + return lwm2m_engine_set(pathstr, value, sizeof(float32_value_t)); +} + +int lwm2m_engine_set_float64(char *pathstr, float64_value_t *value) +{ + return lwm2m_engine_set(pathstr, value, sizeof(float64_value_t)); +} + +/* user data getter functions */ + +static int lwm2m_engine_get(char *pathstr, void *buf, u16_t buflen) +{ + int ret = 0, i; + struct lwm2m_obj_path path; + struct lwm2m_engine_obj_inst *obj_inst; + struct lwm2m_engine_obj_field *obj_field; + struct lwm2m_engine_res_inst *res = NULL; + u16_t len = 0; + void *data_ptr = NULL; + size_t data_len = 0; + + SYS_LOG_DBG("path:%s, buf:%p, buflen:%d", pathstr, buf, buflen); + + /* translate path -> path_obj */ + memset(&path, 0, sizeof(path)); + ret = string_to_path(pathstr, &path, '/'); + if (ret < 0) { + return ret; + } + + if (path.level < 3) { + SYS_LOG_ERR("path must have 3 parts"); + return -EINVAL; + } + + /* find obj_inst/res_id */ + obj_inst = get_engine_obj_inst(path.obj_id, path.obj_inst_id); + if (!obj_inst) { + SYS_LOG_ERR("obj instance %d/%d not found", + path.obj_id, path.obj_inst_id); + return -ENOENT; + } + + if (!obj_inst->resources || obj_inst->resource_count == 0) { + SYS_LOG_ERR("obj instance has no resources"); + return -EINVAL; + } + + obj_field = lwm2m_get_engine_obj_field(obj_inst->obj, path.res_id); + if (!obj_field) { + SYS_LOG_ERR("obj field %d not found", path.res_id); + return -ENOENT; + } + + for (i = 0; i < obj_inst->resource_count; i++) { + if (obj_inst->resources[i].res_id == path.res_id) { + res = &obj_inst->resources[i]; + break; + } + } + + if (!res) { + SYS_LOG_ERR("res instance %d not found", path.res_id); + return -ENOENT; + } + + /* setup initial data elements */ + data_ptr = res->data_ptr; + data_len = res->data_len; + + /* allow user to override data elements via callback */ + if (res->read_cb) { + data_ptr = res->read_cb(obj_inst->obj_inst_id, &data_len); + } + + /* TODO: handle data_len > buflen case */ + + if (data_ptr && data_len > 0) { + switch (obj_field->data_type) { + + case LWM2M_RES_TYPE_OPAQUE: + break; + + case LWM2M_RES_TYPE_STRING: + strncpy((u8_t *)buf, (u8_t *)data_ptr, buflen); + len = strlen(buf); + break; + + case LWM2M_RES_TYPE_U64: + *(u64_t *)buf = *(u64_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_U32: + case LWM2M_RES_TYPE_TIME: + *(u32_t *)buf = *(u32_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_U16: + *(u16_t *)buf = *(u16_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_U8: + *(u8_t *)buf = *(u8_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_S64: + *(s64_t *)buf = *(s64_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_S32: + *(s32_t *)buf = *(s32_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_S16: + *(s16_t *)buf = *(s16_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_S8: + *(s8_t *)buf = *(s8_t *)data_ptr; + break; + + case LWM2M_RES_TYPE_BOOL: + *(bool *)buf = *(bool *)data_ptr; + break; + + case LWM2M_RES_TYPE_FLOAT32: + ((float32_value_t *)buf)->val1 = + ((float32_value_t *)data_ptr)->val1; + ((float32_value_t *)buf)->val2 = + ((float32_value_t *)data_ptr)->val2; + break; + + case LWM2M_RES_TYPE_FLOAT64: + ((float64_value_t *)buf)->val1 = + ((float64_value_t *)data_ptr)->val1; + ((float64_value_t *)buf)->val2 = + ((float64_value_t *)data_ptr)->val2; + break; + + default: + SYS_LOG_ERR("unknown obj data_type %d", + obj_field->data_type); + return -EINVAL; + + } + } + + return 0; +} + +int lwm2m_engine_get_string(char *pathstr, void *str, u16_t strlen) +{ + return lwm2m_engine_get(pathstr, str, strlen); +} + +u8_t lwm2m_engine_get_u8(char *pathstr) +{ + u8_t value = 0; + + lwm2m_engine_get(pathstr, &value, 1); + return value; +} + +u16_t lwm2m_engine_get_u16(char *pathstr) +{ + u16_t value = 0; + + lwm2m_engine_get(pathstr, &value, 2); + return value; +} + +u32_t lwm2m_engine_get_u32(char *pathstr) +{ + u32_t value = 0; + + lwm2m_engine_get(pathstr, &value, 4); + return value; +} + +u64_t lwm2m_engine_get_u64(char *pathstr) +{ + u64_t value = 0; + + lwm2m_engine_get(pathstr, &value, 8); + return value; +} + +s8_t lwm2m_engine_get_s8(char *pathstr) +{ + s8_t value = 0; + + lwm2m_engine_get(pathstr, &value, 1); + return value; +} + +s16_t lwm2m_engine_get_s16(char *pathstr) +{ + s16_t value = 0; + + lwm2m_engine_get(pathstr, &value, 2); + return value; +} + +s32_t lwm2m_engine_get_s32(char *pathstr) +{ + s32_t value = 0; + + lwm2m_engine_get(pathstr, &value, 4); + return value; +} + +s64_t lwm2m_engine_get_s64(char *pathstr) +{ + s64_t value = 0; + + lwm2m_engine_get(pathstr, &value, 8); + return value; +} + +bool lwm2m_engine_get_bool(char *pathstr) +{ + return (lwm2m_engine_get_s8(pathstr) != 0); +} + +int lwm2m_engine_get_float32(char *pathstr, float32_value_t *buf) +{ + return lwm2m_engine_get(pathstr, buf, sizeof(float32_value_t)); +} + +int lwm2m_engine_get_float64(char *pathstr, float64_value_t *buf) +{ + return lwm2m_engine_get(pathstr, buf, sizeof(float64_value_t)); +} + +/* user callback functions */ +static int engine_get_resource(struct lwm2m_obj_path *path, + struct lwm2m_engine_res_inst **res) +{ + int i; + struct lwm2m_engine_obj_inst *obj_inst; + + if (!path) { + return -EINVAL; + } + + /* find obj_inst/res_id */ + obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id); + if (!obj_inst) { + SYS_LOG_ERR("obj instance %d/%d not found", + path->obj_id, path->obj_inst_id); + return -ENOENT; + } + + if (!obj_inst->resources || obj_inst->resource_count == 0) { + SYS_LOG_ERR("obj instance has no resources"); + return -EINVAL; + } + + for (i = 0; i < obj_inst->resource_count; i++) { + if (obj_inst->resources[i].res_id == path->res_id) { + *res = &obj_inst->resources[i]; + break; + } + } + + if (!*res) { + SYS_LOG_ERR("res instance %d not found", path->res_id); + return -ENOENT; + } + + return 0; +} + +static int engine_get_resource_from_pathstr(char *pathstr, + struct lwm2m_engine_res_inst **res) +{ + int ret; + struct lwm2m_obj_path path; + + memset(&path, 0, sizeof(path)); + ret = string_to_path(pathstr, &path, '/'); + if (ret < 0) { + return ret; + } + + if (path.level < 3) { + SYS_LOG_ERR("path must have 3 parts"); + return -EINVAL; + } + + return engine_get_resource(&path, res); +} + +int lwm2m_engine_register_read_callback(char *pathstr, + lwm2m_engine_get_data_cb_t cb) +{ + int ret; + struct lwm2m_engine_res_inst *res = NULL; + + ret = engine_get_resource_from_pathstr(pathstr, &res); + if (ret < 0) { + return ret; + } + + res->read_cb = cb; + return 0; +} + +int lwm2m_engine_register_pre_write_callback(char *pathstr, + lwm2m_engine_get_data_cb_t cb) +{ + int ret; + struct lwm2m_engine_res_inst *res = NULL; + + ret = engine_get_resource_from_pathstr(pathstr, &res); + if (ret < 0) { + return ret; + } + + res->pre_write_cb = cb; + return 0; +} + +int lwm2m_engine_register_post_write_callback(char *pathstr, + lwm2m_engine_set_data_cb_t cb) +{ + int ret; + struct lwm2m_engine_res_inst *res = NULL; + + ret = engine_get_resource_from_pathstr(pathstr, &res); + if (ret < 0) { + return ret; + } + + res->post_write_cb = cb; + return 0; +} + +int lwm2m_engine_register_exec_callback(char *pathstr, + lwm2m_engine_exec_cb_t cb) +{ + int ret; + struct lwm2m_engine_res_inst *res = NULL; + + ret = engine_get_resource_from_pathstr(pathstr, &res); + if (ret < 0) { + return ret; + } + + res->execute_cb = cb; + return 0; +} + +/* generic data handlers */ + +static int lwm2m_read_handler(struct lwm2m_engine_obj_inst *obj_inst, + struct lwm2m_engine_res_inst *res, + struct lwm2m_engine_obj_field *obj_field, + struct lwm2m_engine_context *context) +{ + struct lwm2m_output_context *out = context->out; + struct lwm2m_obj_path *path = context->path; + int i, loop_max = 1; + u16_t res_inst_id_tmp = 0; + void *data_ptr = NULL; + size_t data_len = 0; + + if (!obj_inst || !res || !obj_field || !context) { + return -EINVAL; + } + + /* setup initial data elements */ + data_ptr = res->data_ptr; + data_len = res->data_len; + + /* allow user to override data elements via callback */ + if (res->read_cb) { + data_ptr = res->read_cb(obj_inst->obj_inst_id, &data_len); + } + + if (!data_ptr || data_len == 0) { + return -EINVAL; + } + + if (res->multi_count_var != NULL) { + engine_put_begin_ri(out, path); + loop_max = *res->multi_count_var; + res_inst_id_tmp = path->res_inst_id; + } + + for (i = 0; i < loop_max; i++) { + if (res->multi_count_var != NULL) { + path->res_inst_id = (u16_t) i; + } + + switch (obj_field->data_type) { + + /* do nothing for OPAQUE (probably has a callback) */ + case LWM2M_RES_TYPE_OPAQUE: + break; + + /* TODO: handle multi count for string? */ + case LWM2M_RES_TYPE_STRING: + engine_put_string(out, path, (u8_t *)data_ptr, + strlen((u8_t *)data_ptr)); + break; + + case LWM2M_RES_TYPE_U64: + engine_put_s64(out, path, + (s64_t)((u64_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_U32: + case LWM2M_RES_TYPE_TIME: + engine_put_s32(out, path, + (s32_t)((u32_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_U16: + engine_put_s16(out, path, + (s16_t)((u16_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_U8: + engine_put_s8(out, path, + (s8_t)((u8_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_S64: + engine_put_s64(out, path, + ((s64_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_S32: + engine_put_s32(out, path, + ((s32_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_S16: + engine_put_s16(out, path, + ((s16_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_S8: + engine_put_s8(out, path, + ((s8_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_BOOL: + engine_put_bool(out, path, + ((bool *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_FLOAT32: + engine_put_float32fix(out, path, + &((float32_value_t *)data_ptr)[i]); + break; + + case LWM2M_RES_TYPE_FLOAT64: + engine_put_float64fix(out, path, + &((float64_value_t *)data_ptr)[i]); + break; + + default: + SYS_LOG_ERR("unknown obj data_type %d", + obj_field->data_type); + return -EINVAL; + + } + } + + if (res->multi_count_var != NULL) { + engine_put_end_ri(out, path); + path->res_inst_id = res_inst_id_tmp; + } + + return 0; +} + +/* This function is exposed for the content format writers */ +int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, + struct lwm2m_engine_res_inst *res, + struct lwm2m_engine_obj_field *obj_field, + struct lwm2m_engine_context *context) +{ + struct lwm2m_input_context *in = context->in; + struct lwm2m_obj_path *path = context->path; + s64_t temp64 = 0; + s32_t temp32 = 0; + void *data_ptr = NULL; + size_t data_len = 0; + size_t len = 0; + + if (!obj_inst || !res || !obj_field || !context) { + return -EINVAL; + } + + /* setup initial data elements */ + data_ptr = res->data_ptr; + data_len = res->data_len; + + /* 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) { + + /* do nothing for OPAQUE (probably has a callback) */ + case LWM2M_RES_TYPE_OPAQUE: + data_ptr = in->inbuf; + len = in->insize; + break; + + case LWM2M_RES_TYPE_STRING: + engine_get_string(in, (u8_t *)data_ptr, data_len); + len = strlen((char *)data_ptr); + break; + + case LWM2M_RES_TYPE_U64: + engine_get_s64(in, &temp64); + *(u64_t *)data_ptr = temp64; + len = 8; + break; + + case LWM2M_RES_TYPE_U32: + case LWM2M_RES_TYPE_TIME: + engine_get_s32(in, &temp32); + *(u32_t *)data_ptr = temp32; + len = 4; + break; + + case LWM2M_RES_TYPE_U16: + engine_get_s32(in, &temp32); + *(u16_t *)data_ptr = temp32; + len = 2; + break; + + case LWM2M_RES_TYPE_U8: + engine_get_s32(in, &temp32); + *(u8_t *)data_ptr = temp32; + len = 1; + break; + + case LWM2M_RES_TYPE_S64: + engine_get_s64(in, (s64_t *)data_ptr); + len = 8; + break; + + case LWM2M_RES_TYPE_S32: + engine_get_s32(in, (s32_t *)data_ptr); + len = 4; + break; + + case LWM2M_RES_TYPE_S16: + engine_get_s32(in, &temp32); + *(s16_t *)data_ptr = temp32; + len = 2; + break; + + case LWM2M_RES_TYPE_S8: + engine_get_s32(in, &temp32); + *(s8_t *)data_ptr = temp32; + len = 1; + break; + + case LWM2M_RES_TYPE_BOOL: + engine_get_bool(in, (bool *)data_ptr); + len = 1; + break; + + case LWM2M_RES_TYPE_FLOAT32: + engine_get_float32fix(in, + (float32_value_t *)data_ptr); + len = 4; + break; + + case LWM2M_RES_TYPE_FLOAT64: + engine_get_float64fix(in, + (float64_value_t *)data_ptr); + len = 8; + break; + + default: + SYS_LOG_ERR("unknown obj data_type %d", + obj_field->data_type); + return -EINVAL; + + } + } + + if (res->post_write_cb) { + /* ignore return value here */ + res->post_write_cb(obj_inst->obj_inst_id, data_ptr, len, + false, 0); + } + + NOTIFY_OBSERVER_PATH(path); + + return 0; +} + +static int lwm2m_write_attr_handler(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + if (!obj || !context) { + return -EINVAL; + } + + /* TODO: set parameters on resource for notification */ + + return 0; +} + +static int lwm2m_exec_handler(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + struct lwm2m_obj_path *path = context->path; + struct lwm2m_engine_obj_inst *obj_inst; + struct lwm2m_engine_res_inst *res = NULL; + int ret; + + if (!obj || !context) { + return -EINVAL; + } + + obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id); + if (!obj_inst) { + return -ENOENT; + } + + ret = engine_get_resource(path, &res); + if (ret < 0) { + return ret; + } + + if (res->execute_cb) { + return res->execute_cb(obj_inst->obj_inst_id); + } + + /* TODO: something else to handle for execute? */ + return -ENOENT; +} + +static int lwm2m_delete_handler(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + struct lwm2m_obj_path *path = context->path; + struct lwm2m_engine_obj_inst *obj_inst; + + SYS_LOG_DBG(">> DELETE [path:%u/%u/%u(%u)]", + path->obj_id, path->obj_inst_id, path->res_id, path->level); + + if (!obj || !context) { + return -EINVAL; + } + + obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id); + if (!obj_inst) { + return -ENOENT; + } + + engine_unregister_obj_inst(obj_inst); + obj->instance_count--; +#ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT + engine_trigger_update(); +#endif + + if (obj->delete_cb) { + return obj->delete_cb(path->obj_inst_id); + } + + return 0; +} + +/* + * ZoAP API needs to create the net_pkt buffer in the correct order. + * This function performs last minute verification that outbuf is initialized. + */ +static void outbuf_init_check(struct lwm2m_output_context *out) +{ + if (!out->outbuf) { + out->outbuf = zoap_packet_get_payload(out->out_zpkt, + &out->outsize); + } +} + +#define MATCH_NONE 0 +#define MATCH_ALL 1 +#define MATCH_SINGLE 2 + +static int do_read_op(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + struct lwm2m_output_context *out = context->out; + struct lwm2m_obj_path *path = context->path; + struct lwm2m_engine_obj_inst *obj_inst; + int ret = 0, pos = 0, index, match_type; + u8_t num_read = 0; + u8_t initialized; + struct lwm2m_engine_res_inst *res; + struct lwm2m_engine_obj_field *obj_field; + u16_t temp_res_id; + + obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id); + if (!obj_inst) { + return -ENOENT; + } + + while (obj_inst) { + if (!obj_inst->resources || obj_inst->resource_count == 0) { + continue; + } + + match_type = MATCH_NONE; + /* check obj_inst path for at least partial match */ + if (path->obj_id == obj_inst->obj->obj_id && + path->obj_inst_id == obj_inst->obj_inst_id) { + if (path->level > 2) { + match_type = MATCH_SINGLE; + } else { + match_type = MATCH_ALL; + } + } + + if (match_type == MATCH_NONE) { + continue; + } + + /* save path's res_id because we may need to change it below */ + temp_res_id = path->res_id; + initialized = 0; + + for (index = 0; index < obj_inst->resource_count; index++) { + res = &obj_inst->resources[index]; + + /* + * On a MATCH_ALL loop, we need to set path's res_id + * for lwm2m_read_handler to read this specific + * resource. + */ + if (match_type == MATCH_ALL) { + path->res_id = res->res_id; + } else if (path->res_id != res->res_id) { + continue; + } + + obj_field = lwm2m_get_engine_obj_field(obj_inst->obj, + res->res_id); + if (!obj_field) { + ret = -ENOENT; + } else if ((obj_field->permissions & + LWM2M_PERM_R) != LWM2M_PERM_R) { + ret = -EPERM; + } else { + /* outbuf init / formatter startup if needed */ + if (!initialized) { + outbuf_init_check(out); + engine_put_begin(out, path); + initialized = 1; + } + + /* perform read operation on this resource */ + ret = lwm2m_read_handler(obj_inst, res, + obj_field, context); + if (ret < 0) { + /* What to do here? */ + SYS_LOG_ERR("READ OP failed: %d", ret); + } else { + num_read += 1; + pos = out->outlen; + } + } + + /* on single read break if errors */ + if (ret < 0 && match_type == MATCH_SINGLE) { + break; + } + + /* when reading multiple resources ignore return code */ + ret = 0; + } + + /* restore path's res_id in case it was changed */ + path->res_id = temp_res_id; + + /* if we wrote anything, finish formatting */ + if (initialized) { + engine_put_end(out, path); + pos = out->outlen; + } + + /* advance to the next object instance */ + obj_inst = next_engine_obj_inst(obj_inst, path->obj_id, + path->obj_inst_id); + } + + /* did not read anything even if we should have - on single item */ + if (ret == 0 && num_read == 0 && path->level == 3) { + return -ENOENT; + } + + out->outlen = pos; + return ret; +} + +static int do_discover_op(struct lwm2m_engine_context *context) +{ + struct lwm2m_output_context *out = context->out; + struct lwm2m_engine_obj_inst *obj_inst; + int i = 0; + + /* set output content-format */ + zoap_add_option_int(out->out_zpkt, + ZOAP_OPTION_CONTENT_FORMAT, + LWM2M_FORMAT_APP_LINK_FORMAT); + + /* init the outbuffer */ + out->outbuf = zoap_packet_get_payload(out->out_zpkt, + &out->outsize); + + /* ,**;ct=40 */ + memcpy(out->outbuf, DISCOVER_PREFACE, strlen(DISCOVER_PREFACE)); + out->outlen += strlen(DISCOVER_PREFACE); + + SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, node) { + /* avoid discovery for security and server objects */ + if (obj_inst->obj->obj_id <= LWM2M_OBJECT_SERVER_ID) { + continue; + } + + out->outlen += sprintf(&out->outbuf[out->outlen], ",", + obj_inst->obj->obj_id, + obj_inst->obj_inst_id); + + for (i = 0; i < obj_inst->resource_count; i++) { + out->outlen += sprintf(&out->outbuf[out->outlen], + ",", + obj_inst->obj->obj_id, + obj_inst->obj_inst_id, + obj_inst->resources[i].res_id); + } + } + + out->outbuf[out->outlen] = '\0'; + return 0; +} + +int lwm2m_get_or_create_engine_obj(struct lwm2m_engine_context *context, + struct lwm2m_engine_obj_inst **obj_inst, + u8_t *created) +{ + struct lwm2m_obj_path *path = context->path; + int ret = 0; + + if (created) { + *created = 0; + } + + *obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id); + if (!*obj_inst) { + ret = lwm2m_create_obj_inst(path->obj_id, path->obj_inst_id, + obj_inst); + if (ret < 0) { + return ret; + } + + zoap_header_set_code(context->in->in_zpkt, + ZOAP_RESPONSE_CODE_CREATED); + /* set created flag to one */ + if (created) { + *created = 1; + } + } + + return ret; +} + +static int do_write_op(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context, + u16_t format) +{ + switch (format) { + + case LWM2M_FORMAT_PLAIN_TEXT: + case LWM2M_FORMAT_OMA_PLAIN_TEXT: + return do_write_op_plain_text(obj, context); + + case LWM2M_FORMAT_OMA_TLV: + case LWM2M_FORMAT_OMA_OLD_TLV: + return do_write_op_tlv(obj, context); + +#ifdef CONFIG_LWM2M_RW_JSON_SUPPORT + case LWM2M_FORMAT_OMA_JSON: + case LWM2M_FORMAT_OMA_OLD_JSON: + return do_write_op_json(obj, context); +#endif + + default: + SYS_LOG_ERR("Unsupported format: %u", format); + return -EINVAL; + + } +} + +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) +{ + int r; + u8_t code; + struct zoap_option options[4]; + struct lwm2m_engine_obj *obj; + const u8_t *token; + u8_t tkl = 0; + u16_t format, accept; + struct lwm2m_input_context in; + struct lwm2m_output_context out; + struct lwm2m_obj_path path; + struct lwm2m_engine_context context; + int observe = -1; /* default to -1, 0 = ENABLE, 1 = DISABLE */ + bool discover = false; + + /* setup engine context */ + memset(&context, 0, sizeof(struct lwm2m_engine_context)); + context.in = ∈ + context.out = &out; + context.path = &path; + engine_clear_context(&context); + + /* set ZoAP request / response */ + in.in_zpkt = request; + out.out_zpkt = response; + + /* set default reader/writer */ + in.reader = &plain_text_reader; + out.writer = &plain_text_writer; + + /* parse the URL path into components */ + r = zoap_find_options(in.in_zpkt, ZOAP_OPTION_URI_PATH, options, 4); + if (r > 0) { + /* check for .well-known/core URI query (DISCOVER) */ + if (r == 2 && + (options[0].len == 11 && + strncmp(options[0].value, ".well-known", 11) == 0) && + (options[1].len == 4 && + strncmp(options[1].value, "core", 4) == 0)) { + discover = true; + } else { + zoap_options_to_path(options, r, &path); + } + } + + /* read Content Format */ + r = zoap_find_options(in.in_zpkt, ZOAP_OPTION_CONTENT_FORMAT, + options, 1); + if (r > 0) { + format = zoap_option_value_to_int(&options[0]); + } else { + SYS_LOG_DBG("No content-format given. Assume text plain."); + format = LWM2M_FORMAT_OMA_PLAIN_TEXT; + } + + /* read Accept */ + r = zoap_find_options(in.in_zpkt, ZOAP_OPTION_ACCEPT, options, 1); + if (r > 0) { + accept = zoap_option_value_to_int(&options[0]); + } else { + SYS_LOG_DBG("No Accept header: use same as content-format(%d)", + format); + accept = format; + } + + /* TODO: Handle bootstrap deleted -- re-add when DTLS support ready */ + + code = zoap_header_get_code(in.in_zpkt); + + /* find registered obj */ + obj = get_engine_obj(path.obj_id); + if (!obj) { + /* No matching object found - ignore request */ + return -ENOENT; + } + + format = select_reader(&in, format); + accept = select_writer(&out, accept); + + /* set the operation */ + switch (code & ZOAP_REQUEST_MASK) { + + case ZOAP_METHOD_GET: + if (discover || format == LWM2M_FORMAT_APP_LINK_FORMAT) { + context.operation = LWM2M_OP_DISCOVER; + accept = LWM2M_FORMAT_APP_LINK_FORMAT; + } else { + context.operation = LWM2M_OP_READ; + } + /* check for observe */ + observe = get_observe_option(in.in_zpkt); + zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_CONTENT); + break; + + case ZOAP_METHOD_POST: + if (path.level < 2) { + /* write/create a object instance */ + context.operation = LWM2M_OP_WRITE; + } else { + context.operation = LWM2M_OP_EXECUTE; + } + zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_CHANGED); + break; + + case ZOAP_METHOD_PUT: + context.operation = LWM2M_OP_WRITE; + zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_CHANGED); + break; + + case ZOAP_METHOD_DELETE: + context.operation = LWM2M_OP_DELETE; + zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_DELETED); + break; + + default: + break; + } + + /* set response token */ + token = zoap_header_get_token(in.in_zpkt, &tkl); + if (tkl) { + zoap_header_set_token(out.out_zpkt, token, tkl); + } + + in.inpos = 0; + in.inbuf = zoap_packet_get_payload(in.in_zpkt, &in.insize); + + /* TODO: check for block transfer? */ + + switch (context.operation) { + + case LWM2M_OP_READ: + if (observe == 0) { + /* add new observer */ + if (token) { + r = zoap_add_option_int(out.out_zpkt, + ZOAP_OPTION_OBSERVE, 1); + if (r) { + SYS_LOG_ERR("OBSERVE option error: %d", + r); + } + + r = engine_add_observer( + net_pkt_context(in.in_zpkt->pkt), + from_addr, token, tkl, &path); + if (r < 0) { + SYS_LOG_ERR("add OBSERVE error: %d", r); + } + } else { + SYS_LOG_ERR("OBSERVE request missing token"); + } + } else if (observe == 1) { + /* use token from this request */ + token = zoap_header_get_token(in.in_zpkt, &tkl); + /* remove observer */ + r = engine_remove_observer(token, tkl); + if (r < 0) { + SYS_LOG_ERR("remove obserer error: %d", r); + } + } + + /* set output content-format */ + r = zoap_add_option_int(out.out_zpkt, + ZOAP_OPTION_CONTENT_FORMAT, accept); + if (r > 0) { + SYS_LOG_ERR("Error setting response content-format: %d", + r); + } + + r = do_read_op(obj, &context); + break; + + case LWM2M_OP_DISCOVER: + r = do_discover_op(&context); + break; + + case LWM2M_OP_WRITE: + r = do_write_op(obj, &context, format); + break; + + case LWM2M_OP_WRITE_ATTR: + r = lwm2m_write_attr_handler(obj, &context); + break; + + case LWM2M_OP_EXECUTE: + r = lwm2m_exec_handler(obj, &context); + break; + + case LWM2M_OP_DELETE: + r = lwm2m_delete_handler(obj, &context); + break; + + default: + SYS_LOG_ERR("Unknown operation: %u", context.operation); + return -EINVAL; + } + + if (r == 0) { + /* TODO: Handle blockwise 1 */ + + if (out.outlen > 0) { + SYS_LOG_DBG("replying with %u bytes", out.outlen); + zoap_packet_set_used(out.out_zpkt, out.outlen); + } else { + SYS_LOG_DBG("no data in reply"); + } + } 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 { + /* Failed to handle the request */ + zoap_header_set_code(out.out_zpkt, + ZOAP_RESPONSE_CODE_INTERNAL_ERROR); + r = 0; + } + } + + return r; +} + +void lwm2m_udp_receive(struct net_context *ctx, struct net_pkt *pkt, + struct zoap_pending *zpendings, int num_zpendings, + struct zoap_reply *zreplies, int num_zreplies, + int (*udp_request_handler)(struct zoap_packet *, + struct zoap_packet *, + struct sockaddr *)) +{ + struct net_udp_hdr hdr, *udp_hdr; + struct zoap_pending *pending; + struct zoap_reply *reply; + struct zoap_packet response; + struct sockaddr from_addr; + struct zoap_packet response2; + struct net_pkt *pkt2; + int header_len, r; + const u8_t *token; + u8_t tkl; + + udp_hdr = net_udp_get_hdr(pkt, &hdr); + if (!udp_hdr) { + SYS_LOG_ERR("Invalid UDP data"); + return; + } + + /* Save the from address */ +#if defined(CONFIG_NET_IPV6) + if (net_pkt_family(pkt) == AF_INET6) { + net_ipaddr_copy(&net_sin6(&from_addr)->sin6_addr, + &NET_IPV6_HDR(pkt)->src); + net_sin6(&from_addr)->sin6_port = udp_hdr->src_port; + net_sin6(&from_addr)->sin6_family = AF_INET6; + } +#endif + +#if defined(CONFIG_NET_IPV4) + if (net_pkt_family(pkt) == AF_INET) { + net_ipaddr_copy(&net_sin(&from_addr)->sin_addr, + &NET_IPV4_HDR(pkt)->src); + net_sin(&from_addr)->sin_port = udp_hdr->src_port; + net_sin(&from_addr)->sin_family = AF_INET; + } +#endif + + /* + * zoap expects that buffer->data starts at the + * beginning of the CoAP header + */ + header_len = net_pkt_appdata(pkt) - pkt->frags->data; + net_buf_pull(pkt->frags, header_len); + + r = zoap_packet_parse(&response, pkt); + if (r < 0) { + SYS_LOG_ERR("Invalid data received (err:%d)", r); + goto cleanup; + } + + token = zoap_header_get_token(&response, &tkl); + pending = zoap_pending_received(&response, zpendings, num_zpendings); + if (pending) { + /* TODO: If necessary cancel retransmissions */ + } + + SYS_LOG_DBG("checking for reply from [%s]", + lwm2m_sprint_ip_addr(&from_addr)); + reply = zoap_response_received(&response, &from_addr, + zreplies, num_zreplies); + if (!reply) { + /* + * If no normal response handler is found, then this is + * a new request coming from the server. Let's look + * at registered objects to find a handler. + */ + if (udp_request_handler && + zoap_header_get_type(&response) == ZOAP_TYPE_CON) { + /* Create a response packet if we reach this point */ + r = lwm2m_init_message(ctx, &response2, &pkt2, + ZOAP_TYPE_ACK, + zoap_header_get_code(&response), + zoap_header_get_id(&response), + NULL, -1); + if (r < 0) { + if (pkt2) { + net_pkt_unref(pkt2); + } + goto cleanup; + } + + /* + * The "response" here is actually a new request + */ + r = udp_request_handler(&response, &response2, + &from_addr); + if (r < 0) { + SYS_LOG_ERR("Request handler error: %d", r); + } else { + r = net_context_sendto(pkt2, &from_addr, + NET_SOCKADDR_MAX_SIZE, + NULL, K_NO_WAIT, NULL, + NULL); + if (r < 0) { + SYS_LOG_ERR("Err sending response: %d", + r); + } + } + } else { + SYS_LOG_ERR("No handler for response"); + } + } else { + SYS_LOG_DBG("reply handled reply:%p", reply); + zoap_reply_clear(reply); + } + +cleanup: + if (pkt) { + net_pkt_unref(pkt); + } +} + +static void udp_receive(struct net_context *ctx, struct net_pkt *pkt, + int status, void *user_data) +{ + lwm2m_udp_receive(ctx, pkt, pendings, NUM_PENDINGS, + replies, NUM_REPLIES, handle_request); +} + +static void retransmit_request(struct k_work *work) +{ + struct zoap_pending *pending; + int r; + + pending = zoap_pending_next_to_expire(pendings, NUM_PENDINGS); + if (!pending) { + return; + } + + r = net_context_sendto(pending->pkt, &pending->addr, + NET_SOCKADDR_MAX_SIZE, + NULL, K_NO_WAIT, NULL, NULL); + if (r < 0) { + return; + } + + if (!zoap_pending_cycle(pending)) { + zoap_pending_clear(pending); + return; + } + + k_delayed_work_submit(&retransmit_work, pending->timeout); +} + +static int notify_message_reply_cb(const struct zoap_packet *response, + struct zoap_reply *reply, + const struct sockaddr *from) +{ + int ret = 0; + u8_t type, code; + + type = zoap_header_get_type(response); + code = zoap_header_get_code(response); + + SYS_LOG_DBG("NOTIFY ACK type:%u code:%d.%d reply_token:'%s'", + type, + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code), + sprint_token(reply->token, reply->tkl)); + + /* remove observer on ZOAP_TYPE_RESET */ + if (type == ZOAP_TYPE_RESET) { + if (reply->tkl > 0) { + ret = engine_remove_observer(reply->token, reply->tkl); + if (ret) { + SYS_LOG_ERR("remove obserer error: %d", ret); + } + } else { + SYS_LOG_ERR("notify reply missing token -- ignored."); + } + } + + return 0; +} + +static int generate_notify_message(struct observe_node *obs, + bool manual_trigger) +{ + struct net_pkt *pkt = NULL; + struct zoap_pending *pending = NULL; + struct zoap_reply *reply = NULL; + struct zoap_packet request; + struct lwm2m_engine_obj_inst *obj_inst; + struct lwm2m_output_context out; + struct lwm2m_engine_context context; + struct lwm2m_obj_path path; + int ret = 0; + + /* setup engine context */ + memset(&context, 0, sizeof(struct lwm2m_engine_context)); + context.out = &out; + engine_clear_context(&context); + /* dont clear the path */ + memcpy(&path, &obs->path, sizeof(struct lwm2m_obj_path)); + context.path = &path; + context.operation = LWM2M_OP_READ; + out.out_zpkt = &request; + + SYS_LOG_DBG("[%s] NOTIFY MSG START: %u/%u/%u(%u) token:'%s' [%s] %lld", + manual_trigger ? "MANUAL" : "AUTO", + obs->path.obj_id, + obs->path.obj_inst_id, + obs->path.res_id, + obs->path.level, + sprint_token(obs->token, obs->tkl), + lwm2m_sprint_ip_addr(&obs->addr), + k_uptime_get()); + + obj_inst = get_engine_obj_inst(obs->path.obj_id, + obs->path.obj_inst_id); + if (!obj_inst) { + SYS_LOG_ERR("unable to get engine obj for %u/%u", + obs->path.obj_id, + obs->path.obj_inst_id); + return -EINVAL; + } + + ret = lwm2m_init_message(obs->net_ctx, out.out_zpkt, &pkt, + ZOAP_TYPE_CON, ZOAP_RESPONSE_CODE_CONTENT, + 0, obs->token, obs->tkl); + if (ret) { + goto cleanup; + } + + /* each notification should increment the obs counter */ + obs->counter++; + ret = zoap_add_option_int(out.out_zpkt, ZOAP_OPTION_OBSERVE, + obs->counter); + if (ret) { + SYS_LOG_ERR("OBSERVE option error: %d", ret); + goto cleanup; + } + + /* TODO: save the accept-format from original request */ + + /* set the output writer */ + select_writer(&out, LWM2M_FORMAT_OMA_TLV); + + /* set response content-format */ + ret = zoap_add_option_int(out.out_zpkt, ZOAP_OPTION_CONTENT_FORMAT, + LWM2M_FORMAT_OMA_TLV); + if (ret > 0) { + SYS_LOG_ERR("error setting content-format (err:%d)", ret); + goto cleanup; + } + + ret = do_read_op(obj_inst->obj, &context); + if (ret == 0) { + if (out.outlen > 0) { + zoap_packet_set_used(out.out_zpkt, out.outlen); + } else { + SYS_LOG_DBG("no data in reply"); + } + } else { + SYS_LOG_ERR("error in multi-format read (err:%d)", ret); + goto cleanup; + } + + pending = lwm2m_init_message_pending(out.out_zpkt, + &obs->addr, + pendings, NUM_PENDINGS); + if (!pending) { + ret = -ENOMEM; + goto cleanup; + } + + reply = zoap_reply_next_unused(replies, NUM_REPLIES); + if (!reply) { + SYS_LOG_ERR("No resources for waiting for replies."); + ret = -ENOMEM; + goto cleanup; + } + + zoap_reply_init(reply, &request); + reply->reply = notify_message_reply_cb; + + ret = net_context_sendto(pkt, &obs->addr, NET_SOCKADDR_MAX_SIZE, + NULL, 0, NULL, NULL); + if (ret < 0) { + SYS_LOG_ERR("Error sending LWM2M packet (err:%d).", ret); + goto cleanup; + } + SYS_LOG_DBG("NOTIFY MSG: SENT"); + + zoap_pending_cycle(pending); + k_delayed_work_submit(&retransmit_work, pending->timeout); + return ret; + +cleanup: + lwm2m_init_message_cleanup(pkt, pending, reply); + return ret; +} + +/* TODO: this needs to be triggered via work_queue */ +static void lwm2m_engine_service(void) +{ + struct observe_node *obs; + s64_t timestamp; + + while (true) { + /* + * 1. scan the observer list + * 2. For each notify event found, scan the observer list + * 3. For each observer match, generate a NOTIFY message, + * attaching the notify response handler + */ + timestamp = k_uptime_get(); + SYS_SLIST_FOR_EACH_CONTAINER(&engine_observer_list, obs, node) { + /* + * manual notify requirements: + * - event_timestamp > last_timestamp + * - current timestamp > last_timestamp + min_period_sec + */ + if (obs->event_timestamp > obs->last_timestamp && + timestamp > obs->last_timestamp + + K_SECONDS(obs->min_period_sec)) { + obs->last_timestamp = k_uptime_get(); + generate_notify_message(obs, true); + + /* + * automatic time-based notify requirements: + * - current timestamp > last_timestamp + max_period_sec + */ + } else if (timestamp > obs->last_timestamp + + K_SECONDS(obs->min_period_sec)) { + obs->last_timestamp = k_uptime_get(); + generate_notify_message(obs, false); + } + + } + + k_sleep(K_MSEC(ENGINE_UPDATE_INTERVAL)); + } +} + +int lwm2m_engine_start(struct net_context *net_ctx) +{ + int ret = 0; + + /* set callback */ + ret = net_context_recv(net_ctx, udp_receive, 0, NULL); + if (ret) { + SYS_LOG_ERR("Could not set receive for net context (err:%d)", + ret); + } + + return ret; +} + +static int lwm2m_engine_init(struct device *dev) +{ + /* start thread to handle OBSERVER / NOTIFY events */ + k_thread_create(&engine_thread_data, + &engine_thread_stack[0], + K_THREAD_STACK_SIZEOF(engine_thread_stack), + (k_thread_entry_t) lwm2m_engine_service, + NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + k_delayed_work_init(&retransmit_work, retransmit_request); + SYS_LOG_DBG("LWM2M engine thread started"); + return 0; +} + +SYS_INIT(lwm2m_engine_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.h b/subsys/net/lib/lwm2m/lwm2m_engine.h new file mode 100644 index 0000000000000..2d46e38add0d1 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_engine.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef LWM2M_ENGINE_H +#define LWM2M_ENGINE_H + +#include "lwm2m_object.h" + +#define ZOAP_RESPONSE_CODE_CLASS(x) (x >> 5) +#define ZOAP_RESPONSE_CODE_DETAIL(x) (x & 0x1F) + +/* TODO: */ +#define NOTIFY_OBSERVER(o, i, r) lwm2m_notify_observer(o, i, r) +#define NOTIFY_OBSERVER_PATH(path) lwm2m_notify_observer_path(path) + +char *lwm2m_sprint_ip_addr(const struct sockaddr *addr); + +int lwm2m_notify_observer(u16_t obj_id, u16_t obj_inst_id, u16_t res_id); +int lwm2m_notify_observer_path(struct lwm2m_obj_path *path); + +void lwm2m_register_obj(struct lwm2m_engine_obj *obj); +void lwm2m_unregister_obj(struct lwm2m_engine_obj *obj); +struct lwm2m_engine_obj_field * +lwm2m_get_engine_obj_field(struct lwm2m_engine_obj *obj, int res_id); +int lwm2m_create_obj_inst(u16_t obj_id, u16_t obj_inst_id, + struct lwm2m_engine_obj_inst **obj_inst); +int lwm2m_delete_obj_inst(u16_t obj_id, u16_t obj_inst_id); +int lwm2m_get_or_create_engine_obj(struct lwm2m_engine_context *context, + struct lwm2m_engine_obj_inst **obj_inst, + u8_t *created); + +int lwm2m_init_message(struct net_context *net_ctx, struct zoap_packet *zpkt, + struct net_pkt **pkt, u8_t type, u8_t code, u16_t mid, + const u8_t *token, u8_t tkl); +struct zoap_pending *lwm2m_init_message_pending(struct zoap_packet *zpkt, + struct sockaddr *addr, + struct zoap_pending *zpendings, + int num_zpendings); +void lwm2m_init_message_cleanup(struct net_pkt *pkt, + struct zoap_pending *pending, + struct zoap_reply *reply); + +u16_t lwm2m_get_rd_data(u8_t *client_data, u16_t size); + +int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, + struct lwm2m_engine_res_inst *res, + struct lwm2m_engine_obj_field *obj_field, + struct lwm2m_engine_context *context); + +void lwm2m_udp_receive(struct net_context *ctx, struct net_pkt *pkt, + struct zoap_pending *zpendings, int num_zpendings, + struct zoap_reply *zreplies, int num_zreplies, + int (*udp_request_handler)(struct zoap_packet *request, + struct zoap_packet *response, + struct sockaddr *from_addr)); + +#endif /* LWM2M_ENGINE_H */ diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_device.c b/subsys/net/lib/lwm2m/lwm2m_obj_device.c new file mode 100644 index 0000000000000..23e2752150008 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_obj_device.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * TODO: + * - Implement UTC_OFFSET & TIMEZONE + * - Configurable CURRENT_TIME notification delay + */ + +#define SYS_LOG_DOMAIN "lwm2m_obj_device" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" + +/* Device resource IDs */ +#define DEVICE_MANUFACTURER_ID 0 +#define DEVICE_MODEL_NUMBER_ID 1 +#define DEVICE_SERIAL_NUMBER_ID 2 +#define DEVICE_FIRMWARE_VERSION_ID 3 +#define DEVICE_REBOOT_ID 4 +#define DEVICE_FACTORY_DEFAULT_ID 5 +#define DEVICE_AVAILABLE_POWER_SOURCES_ID 6 +#define DEVICE_POWER_SOURCE_VOLTAGE_ID 7 +#define DEVICE_POWER_SOURCE_CURRENT_ID 8 +#define DEVICE_BATTERY_LEVEL_ID 9 +#define DEVICE_MEMORY_FREE_ID 10 +#define DEVICE_ERROR_CODE_ID 11 +#define DEVICE_RESET_ERROR_CODE_ID 12 +#define DEVICE_CURRENT_TIME_ID 13 +#define DEVICE_UTC_OFFSET_ID 14 +#define DEVICE_TIMEZONE_ID 15 +#define DEVICE_SUPPORTED_BINDING_MODES_ID 16 +#define DEVICE_TYPE_ID 17 +#define DEVICE_HARDWARE_VERSION_ID 18 +#define DEVICE_SOFTWARE_VERSION_ID 19 +#define DEVICE_BATTERY_STATUS_ID 20 +#define DEVICE_MEMORY_TOTAL_ID 21 + +#define DEVICE_MAX_ID 22 + +#ifdef CONFIG_LWM2M_DEVICE_ERROR_CODE_MAX +#define DEVICE_ERROR_CODE_MAX CONFIG_LWM2M_DEVICE_ERROR_CODE_MAX +#else +#define DEVICE_ERROR_CODE_MAX 10 +#endif + +#ifdef CONFIG_LWM2M_DEVICE_PWRSRC_MAX +#define DEVICE_PWRSRC_MAX CONFIG_LWM2M_DEVICE_PWRSRC_MAX +#else +#define DEVICE_PWRSRC_MAX 5 +#endif + +#define DEVICE_STRING_LONG 32 +#define DEVICE_STRING_SHORT 8 + +/* periodic notification thread */ +static K_THREAD_STACK_DEFINE(device_thread_stack, 512); +static struct k_thread device_thread_data; + +/* resource state variables */ +static u8_t manufacturer[DEVICE_STRING_LONG]; +static u8_t model_no[DEVICE_STRING_LONG]; +static u8_t serial_no[DEVICE_STRING_LONG]; +static u8_t firmware_version[DEVICE_STRING_SHORT]; +static s8_t pwrsrc_available[DEVICE_PWRSRC_MAX]; +static s32_t pwrsrc_voltage_mv[DEVICE_PWRSRC_MAX]; +static s32_t pwrsrc_current_ma[DEVICE_PWRSRC_MAX]; +static u8_t battery_level; +static s32_t mem_free_kb; +static u8_t error_code_list[DEVICE_ERROR_CODE_MAX]; +static s32_t time_temp; +static u32_t time_offset; +static u8_t binding_mode[DEVICE_STRING_SHORT]; +static u8_t device_type[DEVICE_STRING_LONG]; +static u8_t hardware_version[DEVICE_STRING_LONG]; +static u8_t software_version[DEVICE_STRING_LONG]; +static u8_t battery_status; +static s32_t mem_total_kb; + +static u8_t pwrsrc_count; +static u8_t error_code_count; + +/* only 1 instance of device object exists */ +static struct lwm2m_engine_obj device; +static struct lwm2m_engine_obj_field fields[] = { + OBJ_FIELD_DATA(DEVICE_MANUFACTURER_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_MODEL_NUMBER_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_SERIAL_NUMBER_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_FIRMWARE_VERSION_ID, R, STRING), + OBJ_FIELD_EXECUTE(DEVICE_REBOOT_ID), + OBJ_FIELD_EXECUTE(DEVICE_FACTORY_DEFAULT_ID), + OBJ_FIELD_MULTI_DATA(DEVICE_AVAILABLE_POWER_SOURCES_ID, R, U8, + DEVICE_PWRSRC_MAX), + OBJ_FIELD_MULTI_DATA(DEVICE_POWER_SOURCE_VOLTAGE_ID, R, S32, + DEVICE_PWRSRC_MAX), + OBJ_FIELD_MULTI_DATA(DEVICE_POWER_SOURCE_CURRENT_ID, R, S32, + DEVICE_PWRSRC_MAX), + OBJ_FIELD_DATA(DEVICE_BATTERY_LEVEL_ID, R, U8), + OBJ_FIELD_DATA(DEVICE_MEMORY_FREE_ID, R, S32), + OBJ_FIELD_MULTI_DATA(DEVICE_ERROR_CODE_ID, R, U8, + DEVICE_ERROR_CODE_MAX), + OBJ_FIELD_EXECUTE(DEVICE_RESET_ERROR_CODE_ID), + OBJ_FIELD_DATA(DEVICE_CURRENT_TIME_ID, RW, TIME), + OBJ_FIELD_DATA(DEVICE_SUPPORTED_BINDING_MODES_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_TYPE_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_HARDWARE_VERSION_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_SOFTWARE_VERSION_ID, R, STRING), + OBJ_FIELD_DATA(DEVICE_BATTERY_STATUS_ID, R, U8), + OBJ_FIELD_DATA(DEVICE_MEMORY_TOTAL_ID, R, S32) +}; + +static struct lwm2m_engine_obj_inst inst; +static struct lwm2m_engine_res_inst res[DEVICE_MAX_ID]; + +/* callbacks */ + +static int reboot_cb(u16_t obj_inst_id) +{ + SYS_LOG_DBG("REBOOT"); + return -EPERM; +} + +static int factory_default_cb(u16_t obj_inst_id) +{ + SYS_LOG_DBG("FACTORY_DEFAULT"); + return -EPERM; +} + +static int reset_error_list_cb(u16_t obj_inst_id) +{ + error_code_count = 0; + return 0; +} + +static void *current_time_read_cb(u16_t obj_inst_id, size_t *data_len) +{ + time_temp = time_offset + (k_uptime_get() / 1000); + *data_len = sizeof(time_temp); + + return &time_temp; +} + +static void *current_time_pre_write_cb(u16_t obj_inst_id, size_t *data_len) +{ + *data_len = sizeof(time_temp); + return &time_temp; +} + +static int current_time_post_write_cb(u16_t obj_inst_id, + u8_t *data, u16_t data_len, + bool last_block, size_t total_size) +{ + if (data_len == 4) { + time_offset = *(s32_t *)data - (s32_t)(k_uptime_get() / 1000); + return 1; + } + + SYS_LOG_ERR("unknown size %u", data_len); + return 0; +} + +/* special setter functions */ + +int lwm2m_device_add_pwrsrc(u8_t pwrsrc_type) +{ + int index; + + if (pwrsrc_type < 0 || pwrsrc_type >= LWM2M_DEVICE_PWR_SRC_TYPE_MAX) { + SYS_LOG_ERR("power source id %d is invalid", + pwrsrc_type); + return -EINVAL; + } + + for (index = 0; index < DEVICE_PWRSRC_MAX; index++) { + if (pwrsrc_available[index] < 0) { + break; + } + } + + if (index >= DEVICE_PWRSRC_MAX) { + return -ENOMEM; + } + + pwrsrc_available[index] = pwrsrc_type; + pwrsrc_voltage_mv[index] = 0; + pwrsrc_current_ma[index] = 0; + pwrsrc_count++; + NOTIFY_OBSERVER(LWM2M_OBJECT_DEVICE_ID, 0, + DEVICE_AVAILABLE_POWER_SOURCES_ID); + return index; +} + +/* + * TODO: this will disable the index, but current printing function expects + * all indexes to be in order up to pwrsrc_count + */ +int lwm2m_device_remove_pwrsrc(int index) +{ + if (index < 0 || index >= DEVICE_PWRSRC_MAX) { + SYS_LOG_ERR("index is out of range: %d", index); + return -EINVAL; + } + + if (pwrsrc_available[index] < 0) { + SYS_LOG_ERR("Power source index %d isn't registered", index); + return -EINVAL; + } + + pwrsrc_available[index] = -1; + pwrsrc_voltage_mv[index] = 0; + pwrsrc_current_ma[index] = 0; + pwrsrc_count--; + NOTIFY_OBSERVER(LWM2M_OBJECT_DEVICE_ID, 0, + DEVICE_AVAILABLE_POWER_SOURCES_ID); + return 0; +} + +int lwm2m_device_set_pwrsrc_voltage_mv(int index, int voltage_mv) +{ + if (index < 0 || index >= DEVICE_PWRSRC_MAX) { + SYS_LOG_ERR("index is out of range: %d", index); + return -EINVAL; + } + + if (pwrsrc_available[index] < 0) { + SYS_LOG_ERR("Power source index %d isn't registered.", index); + return -EINVAL; + } + + pwrsrc_voltage_mv[index] = voltage_mv; + NOTIFY_OBSERVER(LWM2M_OBJECT_DEVICE_ID, 0, + DEVICE_POWER_SOURCE_VOLTAGE_ID); + return 0; +} + +int lwm2m_device_set_pwrsrc_current_ma(int index, int current_ma) +{ + if (index < 0 || index >= DEVICE_PWRSRC_MAX) { + SYS_LOG_ERR("index is out of range: %d", index); + return -EINVAL; + } + + if (pwrsrc_available[index] < 0) { + SYS_LOG_ERR("Power source index %d isn't registered.", index); + return -EINVAL; + } + + pwrsrc_current_ma[index] = current_ma; + NOTIFY_OBSERVER(LWM2M_OBJECT_DEVICE_ID, 0, + DEVICE_POWER_SOURCE_CURRENT_ID); + return 0; +} + +/* error code function */ + +int lwm2m_device_add_err(u8_t error_code) +{ + if (error_code_count < DEVICE_ERROR_CODE_MAX) { + error_code_list[error_code_count] = error_code; + error_code_count++; + return 0; + } + + return -ENOMEM; +} + +static void device_periodic_service(void) +{ + while (true) { + /* TODO: make this delay configurable */ + k_sleep(K_SECONDS(10)); + NOTIFY_OBSERVER(LWM2M_OBJECT_DEVICE_ID, 0, + DEVICE_CURRENT_TIME_ID); + } +} + +static struct lwm2m_engine_obj_inst *device_create(u16_t obj_inst_id) +{ + int i = 0; + + /* initialize instance resource data */ + INIT_OBJ_RES_DATA(res, i, DEVICE_MANUFACTURER_ID, + manufacturer, DEVICE_STRING_LONG); + INIT_OBJ_RES_DATA(res, i, DEVICE_MODEL_NUMBER_ID, + model_no, DEVICE_STRING_LONG); + INIT_OBJ_RES_DATA(res, i, DEVICE_SERIAL_NUMBER_ID, + serial_no, DEVICE_STRING_LONG); + INIT_OBJ_RES_DATA(res, i, DEVICE_FIRMWARE_VERSION_ID, + firmware_version, DEVICE_STRING_SHORT); + INIT_OBJ_RES_EXECUTE(res, i, DEVICE_REBOOT_ID, reboot_cb); + INIT_OBJ_RES_EXECUTE(res, i, DEVICE_FACTORY_DEFAULT_ID, + factory_default_cb); + INIT_OBJ_RES_MULTI_DATA(res, i, DEVICE_AVAILABLE_POWER_SOURCES_ID, + &pwrsrc_count, pwrsrc_available, + sizeof(*pwrsrc_available)); + INIT_OBJ_RES_MULTI_DATA(res, i, DEVICE_POWER_SOURCE_VOLTAGE_ID, + &pwrsrc_count, pwrsrc_voltage_mv, + sizeof(*pwrsrc_voltage_mv)); + INIT_OBJ_RES_MULTI_DATA(res, i, DEVICE_POWER_SOURCE_CURRENT_ID, + &pwrsrc_count, pwrsrc_current_ma, + sizeof(*pwrsrc_current_ma)); + INIT_OBJ_RES_DATA(res, i, DEVICE_BATTERY_LEVEL_ID, + &battery_level, sizeof(battery_level)); + INIT_OBJ_RES_DATA(res, i, DEVICE_MEMORY_FREE_ID, + &mem_free_kb, sizeof(mem_free_kb)); + INIT_OBJ_RES_MULTI_DATA(res, i, DEVICE_ERROR_CODE_ID, + &error_code_count, error_code_list, + sizeof(*error_code_list)); + INIT_OBJ_RES_EXECUTE(res, i, DEVICE_RESET_ERROR_CODE_ID, + reset_error_list_cb); + INIT_OBJ_RES(res, i, DEVICE_CURRENT_TIME_ID, 0, NULL, 0, + current_time_read_cb, current_time_pre_write_cb, + current_time_post_write_cb, NULL); + INIT_OBJ_RES_DATA(res, i, DEVICE_SUPPORTED_BINDING_MODES_ID, + binding_mode, DEVICE_STRING_SHORT); + INIT_OBJ_RES_DATA(res, i, DEVICE_TYPE_ID, + device_type, DEVICE_STRING_LONG); + INIT_OBJ_RES_DATA(res, i, DEVICE_HARDWARE_VERSION_ID, + hardware_version, DEVICE_STRING_LONG); + INIT_OBJ_RES_DATA(res, i, DEVICE_SOFTWARE_VERSION_ID, + software_version, DEVICE_STRING_LONG); + INIT_OBJ_RES_DATA(res, i, DEVICE_BATTERY_STATUS_ID, + &battery_status, sizeof(battery_status)); + INIT_OBJ_RES_DATA(res, i, DEVICE_MEMORY_TOTAL_ID, + &mem_total_kb, sizeof(mem_total_kb)); + + inst.resources = res; + inst.resource_count = i; + SYS_LOG_DBG("Create LWM2M device instance: %d", obj_inst_id); + return &inst; +} + +static int lwm2m_device_init(struct device *dev) +{ + struct lwm2m_engine_obj_inst *obj_inst = NULL; + int ret = 0, i; + + /* Set default values */ + time_offset = 0; + mem_total_kb = 0; + mem_free_kb = -1; + pwrsrc_count = 0; + error_code_count = 0; + /* currently only support UDP binding mode (no SMS or Queue mode) */ + strcpy(binding_mode, "U"); + + for (i = 0; i < DEVICE_PWRSRC_MAX; i++) { + pwrsrc_available[i] = -1; + } + + /* initialize the device field data */ + device.obj_id = LWM2M_OBJECT_DEVICE_ID; + device.fields = fields; + device.field_count = sizeof(fields) / sizeof(*fields); + device.max_instance_count = 1; + device.create_cb = device_create; + lwm2m_register_obj(&device); + + /* auto create the only instance */ + ret = lwm2m_create_obj_inst(LWM2M_OBJECT_DEVICE_ID, 0, &obj_inst); + if (ret < 0) { + SYS_LOG_DBG("Create LWM2M instance 0 error: %d", ret); + } + + /* start thread to handle OBSERVER / NOTIFY events */ + k_thread_create(&device_thread_data, + &device_thread_stack[0], + K_THREAD_STACK_SIZEOF(device_thread_stack), + (k_thread_entry_t) device_periodic_service, + NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + return ret; +} + +SYS_INIT(lwm2m_device_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c new file mode 100644 index 0000000000000..7c611bae0f330 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * 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 +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" + +/* Firmware resource IDs */ +#define FIRMWARE_PACKAGE_ID 0 +#define FIRMWARE_PACKAGE_URI_ID 1 /* TODO */ +#define FIRMWARE_UPDATE_ID 2 +#define FIRMWARE_STATE_ID 3 +#define FIRMWARE_UPDATE_RESULT_ID 5 +#define FIRMWARE_PACKAGE_NAME_ID 6 /* TODO */ +#define FIRMWARE_PACKAGE_VERSION_ID 7 /* TODO */ +#define FIRMWARE_UPDATE_PROTO_SUPPORT_ID 8 /* TODO */ +#define FIRMWARE_UPDATE_DELIV_METHOD_ID 9 + +#define FIRMWARE_MAX_ID 10 + +#define DELIVERY_METHOD_PULL_ONLY 0 +#define DELIVERY_METHOD_PUSH_ONLY 1 +#define DELIVERY_METHOD_BOTH 2 + +#define PACKAGE_URI_LEN 255 + +/* resource state variables */ +static u8_t update_state; +static u8_t update_result; +static u8_t delivery_method; +static char package_uri[PACKAGE_URI_LEN]; + +/* only 1 instance of firmware object exists */ +static struct lwm2m_engine_obj firmware; +static struct lwm2m_engine_obj_field fields[] = { + OBJ_FIELD(FIRMWARE_PACKAGE_ID, W, OPAQUE, 0), + OBJ_FIELD(FIRMWARE_PACKAGE_URI_ID, RW, STRING, 0), + OBJ_FIELD_EXECUTE(FIRMWARE_UPDATE_ID), + OBJ_FIELD_DATA(FIRMWARE_STATE_ID, R, U8), + OBJ_FIELD_DATA(FIRMWARE_UPDATE_RESULT_ID, R, U8), + OBJ_FIELD_DATA(FIRMWARE_UPDATE_DELIV_METHOD_ID, R, U8) +}; + +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; + +#ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT +extern int lwm2m_firmware_start_transfer(char *package_uri); +#endif + +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"); + if (write_cb) { + write_cb(obj_inst_id, data, data_len, last_block, total_size); + return 1; + } + + return 0; +} + +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); + return 1; +#endif + return 0; +} + +void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb) +{ + write_cb = cb; +} + +lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void) +{ + return write_cb; +} + +static struct lwm2m_engine_obj_inst *firmware_create(u16_t obj_inst_id) +{ + int i = 0; + + /* initialize instance resource data */ + INIT_OBJ_RES(res, i, FIRMWARE_PACKAGE_ID, 0, NULL, 0, + NULL, NULL, package_write_cb, NULL); + 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_DATA(res, i, FIRMWARE_STATE_ID, + &update_state, sizeof(update_state)); + INIT_OBJ_RES_DATA(res, i, FIRMWARE_UPDATE_RESULT_ID, + &update_result, sizeof(update_result)); + INIT_OBJ_RES_DATA(res, i, FIRMWARE_UPDATE_DELIV_METHOD_ID, + &delivery_method, sizeof(delivery_method)); + + inst.resources = res; + inst.resource_count = i; + SYS_LOG_DBG("Create LWM2M firmware instance: %d", obj_inst_id); + return &inst; +} + +static int lwm2m_firmware_init(struct device *dev) +{ + struct lwm2m_engine_obj_inst *obj_inst = NULL; + int ret = 0; + + /* Set default values */ + package_uri[0] = '\0'; + 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; +#endif + + firmware.obj_id = LWM2M_OBJECT_FIRMWARE_ID; + firmware.fields = fields; + firmware.field_count = sizeof(fields) / sizeof(*fields); + firmware.max_instance_count = 1; + firmware.create_cb = firmware_create; + lwm2m_register_obj(&firmware); + + /* auto create the only instance */ + ret = lwm2m_create_obj_inst(LWM2M_OBJECT_FIRMWARE_ID, 0, &obj_inst); + if (ret < 0) { + SYS_LOG_DBG("Create LWM2M instance 0 error: %d", ret); + } + + return ret; +} + +SYS_INIT(lwm2m_firmware_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c new file mode 100644 index 0000000000000..f17b065965e25 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_obj_firmware_pull.c @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * 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 "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 net_context *firmware_net_ctx; +static struct k_delayed_work retransmit_work; + +#define NUM_PENDINGS CONFIG_LWM2M_ENGINE_MAX_PENDING +#define NUM_REPLIES CONFIG_LWM2M_ENGINE_MAX_REPLIES +static struct zoap_pending pendings[NUM_PENDINGS]; +static struct zoap_reply replies[NUM_REPLIES]; +static struct zoap_block_context firmware_block_ctx; + +static void +firmware_udp_receive(struct net_context *ctx, struct net_pkt *pkt, int status, + void *user_data) +{ + lwm2m_udp_receive(ctx, pkt, pendings, NUM_PENDINGS, + replies, NUM_REPLIES, NULL); +} + +static void retransmit_request(struct k_work *work) +{ + struct zoap_pending *pending; + int r; + + pending = zoap_pending_next_to_expire(pendings, NUM_PENDINGS); + if (!pending) { + return; + } + + r = net_context_sendto(pending->pkt, &pending->addr, + NET_SOCKADDR_MAX_SIZE, + NULL, K_NO_WAIT, NULL, NULL); + if (r < 0) { + return; + } + + if (!zoap_pending_cycle(pending)) { + zoap_pending_clear(pending); + return; + } + + k_delayed_work_submit(&retransmit_work, pending->timeout); +} + +static int transfer_request(struct zoap_block_context *ctx, + const u8_t *token, u8_t tkl, + zoap_reply_t reply_cb) +{ + struct zoap_packet request; + struct net_pkt *pkt = NULL; + struct zoap_pending *pending = NULL; + struct zoap_reply *reply = NULL; + int ret; + + ret = lwm2m_init_message(firmware_net_ctx, &request, &pkt, + ZOAP_TYPE_CON, ZOAP_METHOD_GET, + 0, token, tkl); + if (ret) { + 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; + } + + ret = zoap_add_block2_option(&request, ctx); + if (ret) { + SYS_LOG_ERR("Unable to add block2 option."); + goto cleanup; + } + + pending = lwm2m_init_message_pending(&request, &firmware_addr, + pendings, NUM_PENDINGS); + if (!pending) { + ret = -ENOMEM; + goto cleanup; + } + + /* set the reply handler */ + if (reply_cb) { + reply = zoap_reply_next_unused(replies, NUM_REPLIES); + if (!reply) { + SYS_LOG_ERR("No resources for waiting for replies."); + ret = -ENOMEM; + goto cleanup; + } + + zoap_reply_init(reply, &request); + reply->reply = reply_cb; + } + + /* send request */ + ret = net_context_sendto(pkt, &firmware_addr, NET_SOCKADDR_MAX_SIZE, + NULL, 0, NULL, NULL); + if (ret < 0) { + SYS_LOG_ERR("Error sending LWM2M packet (err:%d).", + ret); + goto cleanup; + } + + zoap_pending_cycle(pending); + k_delayed_work_submit(&retransmit_work, pending->timeout); + return 0; + +cleanup: + lwm2m_init_message_cleanup(pkt, pending, reply); + return ret; +} + +static int +do_firmware_transfer_reply_cb(const struct zoap_packet *response, + struct zoap_reply *reply, + const struct sockaddr *from) +{ + int ret; + size_t transfer_offset = 0; + const u8_t *token; + u8_t tkl; + u16_t payload_len; + u8_t *payload; + struct zoap_packet *check_response = (struct zoap_packet *)response; + lwm2m_engine_set_data_cb_t callback; + + SYS_LOG_DBG("TRANSFER REPLY"); + + ret = zoap_update_from_block(check_response, &firmware_block_ctx); + if (ret < 0) { + SYS_LOG_ERR("Error from block update: %d", ret); + return ret; + } + + /* TODO: 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); + } + } + + /* TODO: Determine actual completion criteria */ + if (transfer_offset > 0) { + token = zoap_header_get_token(check_response, &tkl); + ret = transfer_request(&firmware_block_ctx, token, tkl, + do_firmware_transfer_reply_cb); + } + + return ret; +} + +static void firmware_transfer(struct k_work *work) +{ +#if defined(CONFIG_NET_IPV6) + static struct sockaddr_in6 any_addr6 = { .sin6_addr = IN6ADDR_ANY_INIT, + .sin6_family = AF_INET6 }; +#endif +#if defined(CONFIG_NET_IPV4) + static struct sockaddr_in any_addr4 = { .sin_addr = INADDR_ANY_INIT, + .sin_family = AF_INET }; +#endif + struct net_if *iface; + int ret, port, family; + + /* 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.family = family; + /* HACK: use firmware_uri directly as IP address */ + net_addr_pton(firmware_addr.family, firmware_uri, + &net_sin6(&firmware_addr)->sin6_addr); + net_sin6(&firmware_addr)->sin6_port = htons(5685); + } +#endif + +#if defined(CONFIG_NET_IPV4) + if (family == AF_INET) { + firmware_addr.family = family; + net_addr_pton(firmware_addr.family, firmware_uri, + &net_sin(&firmware_addr)->sin_addr); + net_sin(&firmware_addr)->sin_port = htons(5685); + } +#endif + + ret = net_context_get(firmware_addr.family, SOCK_DGRAM, IPPROTO_UDP, + &firmware_net_ctx); + if (ret) { + NET_ERR("Could not get an UDP context (err:%d)", ret); + return; + } + + iface = net_if_get_default(); + if (!iface) { + NET_ERR("Could not find default interface"); + goto cleanup; + } + +#if defined(CONFIG_NET_IPV6) + if (firmware_addr.family == AF_INET6) { + ret = net_context_bind(firmware_net_ctx, + (struct sockaddr *)&any_addr6, + sizeof(any_addr6)); + } +#endif + +#if defined(CONFIG_NET_IPV4) + if (firmware_addr.family == AF_INET) { + ret = net_context_bind(firmware_net_ctx, + (struct sockaddr *)&any_addr4, + sizeof(any_addr4)); + } +#endif + + if (ret) { + NET_ERR("Could not bind the UDP context (err:%d)", ret); + goto cleanup; + } + + SYS_LOG_DBG("Attached to port: %d", 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); + goto cleanup; + } + + /* reset block transfer context */ +#if defined(CONFIG_NET_L2_BLUETOOTH) + zoap_block_transfer_init(&firmware_block_ctx, ZOAP_BLOCK_64, 0); +#else + zoap_block_transfer_init(&firmware_block_ctx, ZOAP_BLOCK_256, 0); +#endif + + transfer_request(&firmware_block_ctx, NULL, 0, + do_firmware_transfer_reply_cb); + return; + +cleanup: + if (firmware_net_ctx) { + net_context_put(firmware_net_ctx); + } +} + +/* TODO: */ +int lwm2m_firmware_cancel_transfer(void) +{ + return 0; +} + +int lwm2m_firmware_start_transfer(char *package_uri) +{ + /* free up old context */ + if (firmware_net_ctx) { + 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); + + /* start file transfer work */ + strncpy(firmware_uri, package_uri, PACKAGE_URI_LEN - 1); + k_work_submit(&firmware_work); + return 0; + } + + return -1; +} diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_security.c b/subsys/net/lib/lwm2m/lwm2m_obj_security.c new file mode 100644 index 0000000000000..a0d77a24dfea0 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_obj_security.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define SYS_LOG_DOMAIN "lwm2m_obj_security" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" + +/* Security resource IDs */ +#define SECURITY_SERVER_URI_ID 0 +#define SECURITY_BOOTSTRAP_FLAG_ID 1 +#define SECURITY_MODE_ID 2 +#define SECURITY_CLIENT_PK_ID 3 +#define SECURITY_SERVER_PK_ID 4 +#define SECURITY_SECRET_KEY_ID 5 +#define SECURITY_SHORT_SERVER_ID 10 + +#define SECURITY_MAX_ID 7 + +#define MAX_INSTANCE_COUNT CONFIG_LWM2M_SECURITY_INSTANCE_COUNT + +#define SECURITY_URI_LEN 255 + +/* resource state variables */ +static char security_uri[MAX_INSTANCE_COUNT][SECURITY_URI_LEN]; +static bool bootstrap_flag[MAX_INSTANCE_COUNT]; +static u8_t security_mode[MAX_INSTANCE_COUNT]; +static u16_t short_server_id[MAX_INSTANCE_COUNT]; + +static struct lwm2m_engine_obj security; +static struct lwm2m_engine_obj_field fields[] = { + OBJ_FIELD_DATA(SECURITY_SERVER_URI_ID, RW, STRING), + OBJ_FIELD_DATA(SECURITY_BOOTSTRAP_FLAG_ID, W, BOOL), + OBJ_FIELD_DATA(SECURITY_MODE_ID, W, U8), + OBJ_FIELD_DATA(SECURITY_CLIENT_PK_ID, W, OPAQUE), /* TODO */ + OBJ_FIELD_DATA(SECURITY_SERVER_PK_ID, W, OPAQUE), /* TODO */ + OBJ_FIELD_DATA(SECURITY_SECRET_KEY_ID, W, OPAQUE), /* TODO */ + OBJ_FIELD_DATA(SECURITY_SHORT_SERVER_ID, W, U16) /* TODO */ +}; + +static struct lwm2m_engine_obj_inst inst[MAX_INSTANCE_COUNT]; +static struct lwm2m_engine_res_inst res[MAX_INSTANCE_COUNT][SECURITY_MAX_ID]; + +static struct lwm2m_engine_obj_inst *security_create(u16_t obj_inst_id) +{ + int index, i = 0; + + /* Check that there is no other instance with this ID */ + for (index = 0; index < MAX_INSTANCE_COUNT; index++) { + if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) { + SYS_LOG_ERR("Can not create instance - " + "already existing: %u", obj_inst_id); + return NULL; + } + } + + for (index = 0; index < MAX_INSTANCE_COUNT; index++) { + if (!inst[index].obj) { + break; + } + } + + if (index >= MAX_INSTANCE_COUNT) { + SYS_LOG_ERR("Can not create instance - " + "no more room: %u", obj_inst_id); + return NULL; + } + + /* default values */ + security_uri[index][0] = '\0'; + bootstrap_flag[index] = 0; + security_mode[index] = 0; + short_server_id[index] = 0; + + /* initialize instance resource data */ + INIT_OBJ_RES_DATA(res[index], i, SECURITY_SERVER_URI_ID, + security_uri[index], SECURITY_URI_LEN); + INIT_OBJ_RES_DATA(res[index], i, SECURITY_BOOTSTRAP_FLAG_ID, + &bootstrap_flag[index], sizeof(*bootstrap_flag)); + INIT_OBJ_RES_DATA(res[index], i, SECURITY_MODE_ID, + &security_mode[index], sizeof(*security_mode)); + /* TODO: */ + INIT_OBJ_RES_DUMMY(res[index], i, SECURITY_CLIENT_PK_ID); + INIT_OBJ_RES_DUMMY(res[index], i, SECURITY_SERVER_PK_ID), + INIT_OBJ_RES_DUMMY(res[index], i, SECURITY_SECRET_KEY_ID), + INIT_OBJ_RES_DATA(res[index], i, SECURITY_SHORT_SERVER_ID, + &short_server_id[index], sizeof(*short_server_id)); + + inst[index].resources = res[index]; + inst[index].resource_count = i; + SYS_LOG_DBG("Create LWM2M security instance: %d", obj_inst_id); + return &inst[index]; +} + +static int lwm2m_security_init(struct device *dev) +{ + struct lwm2m_engine_obj_inst *obj_inst = NULL; + int ret = 0; + + /* Set default values */ + memset(inst, 0, sizeof(*inst) * MAX_INSTANCE_COUNT); + memset(res, 0, sizeof(struct lwm2m_engine_res_inst) * + MAX_INSTANCE_COUNT * SECURITY_MAX_ID); + + security.obj_id = LWM2M_OBJECT_SECURITY_ID; + security.fields = fields; + security.field_count = sizeof(fields) / sizeof(*fields); + security.max_instance_count = MAX_INSTANCE_COUNT; + security.create_cb = security_create; + lwm2m_register_obj(&security); + + /* auto create the first instance */ + ret = lwm2m_create_obj_inst(LWM2M_OBJECT_SECURITY_ID, 0, &obj_inst); + if (ret < 0) { + SYS_LOG_ERR("Create LWM2M security instance 0 error: %d", ret); + } + + return ret; +} + +SYS_INIT(lwm2m_security_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/subsys/net/lib/lwm2m/lwm2m_obj_server.c b/subsys/net/lib/lwm2m/lwm2m_obj_server.c new file mode 100644 index 0000000000000..46f941e730637 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_obj_server.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define SYS_LOG_DOMAIN "lwm2m_obj_server" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" +#ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT +#include "lwm2m_rd_client.h" +#endif + +/* Server resource IDs */ +#define SERVER_SHORT_SERVER_ID 0 +#define SERVER_LIFETIME_ID 1 +#define SERVER_DEFAULT_MIN_PERIOD_ID 2 +#define SERVER_DEFAULT_MAX_PERIOD_ID 3 +#define SERVER_DISABLE_ID 4 +#define SERVER_DISABLE_TIMEOUT_ID 5 +#define SERVER_STORE_NOTIFY_ID 6 +#define SERVER_TRANSPORT_BINDING_ID 7 +#define SERVER_REG_UPDATE_TRIGGER_ID 8 + +#define SERVER_MAX_ID 9 + +/* Server flags */ +#define SERVER_FLAG_DISABLED 1 +#define SERVER_FLAG_STORE_NOTIFY 2 + +#define MAX_INSTANCE_COUNT CONFIG_LWM2M_SERVER_INSTANCE_COUNT + +#define TRANSPORT_BINDING_LEN 4 + +/* resource state variables */ +static u16_t server_id[MAX_INSTANCE_COUNT]; +static u32_t lifetime[MAX_INSTANCE_COUNT]; +static u32_t default_min_period[MAX_INSTANCE_COUNT]; +static u32_t default_max_period[MAX_INSTANCE_COUNT]; +static u8_t server_flag_disabled[MAX_INSTANCE_COUNT]; +static u32_t disabled_timeout[MAX_INSTANCE_COUNT]; +static u8_t server_flag_store_notify[MAX_INSTANCE_COUNT]; +static char transport_binding[MAX_INSTANCE_COUNT][TRANSPORT_BINDING_LEN]; + +static struct lwm2m_engine_obj server; +static struct lwm2m_engine_obj_field fields[] = { + OBJ_FIELD_DATA(SERVER_SHORT_SERVER_ID, R, U16), + OBJ_FIELD_DATA(SERVER_LIFETIME_ID, RW, U32), + OBJ_FIELD_DATA(SERVER_DEFAULT_MIN_PERIOD_ID, RW, U32), + OBJ_FIELD_DATA(SERVER_DEFAULT_MAX_PERIOD_ID, RW, U32), + OBJ_FIELD_EXECUTE(SERVER_DISABLE_ID), + OBJ_FIELD_DATA(SERVER_DISABLE_TIMEOUT_ID, RW, U32), + OBJ_FIELD_DATA(SERVER_STORE_NOTIFY_ID, RW, U8), + /* Mark Transport Binding RO as we only support UDP atm */ + OBJ_FIELD_DATA(SERVER_TRANSPORT_BINDING_ID, R, STRING), + OBJ_FIELD_EXECUTE(SERVER_REG_UPDATE_TRIGGER_ID), +}; + +static struct lwm2m_engine_obj_inst inst[MAX_INSTANCE_COUNT]; +static struct lwm2m_engine_res_inst res[MAX_INSTANCE_COUNT][SERVER_MAX_ID]; + +static int disable_cb(u16_t obj_inst_id) +{ + int i; + + SYS_LOG_DBG("DISABLE %d", obj_inst_id); + for (i = 0; i < MAX_INSTANCE_COUNT; i++) { + if (inst[i].obj && inst[i].obj_inst_id == obj_inst_id) { + server_flag_disabled[i] = 1; + return 0; + } + } + + return -ENOENT; +} + +static int update_trigger_cb(u16_t obj_inst_id) +{ +#ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT + engine_trigger_update(); + return 0; +#else + return -EPERM; +#endif +} + +static struct lwm2m_engine_obj_inst *server_create(u16_t obj_inst_id) +{ + int index, i = 0; + + /* Check that there is no other instance with this ID */ + for (index = 0; index < MAX_INSTANCE_COUNT; index++) { + if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) { + SYS_LOG_ERR("Can not create instance - " + "already existing: %u", obj_inst_id); + return NULL; + } + } + + for (index = 0; index < MAX_INSTANCE_COUNT; index++) { + if (!inst[index].obj) { + break; + } + } + + if (index >= MAX_INSTANCE_COUNT) { + SYS_LOG_ERR("Can not create instance - " + "no more room: %u", obj_inst_id); + return NULL; + } + + /* Set default values */ + server_flag_disabled[index] = 0; + server_flag_store_notify[index] = 0; + server_id[index] = index + 1; + lifetime[index] = CONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME; + default_min_period[index] = 0; + default_max_period[index] = 0; + disabled_timeout[index] = 86400; + strcpy(transport_binding[index], "U"); + + /* initialize instance resource data */ + INIT_OBJ_RES_DATA(res[index], i, SERVER_SHORT_SERVER_ID, + &server_id[index], sizeof(*server_id)); + INIT_OBJ_RES_DATA(res[index], i, SERVER_LIFETIME_ID, + &lifetime[index], sizeof(*lifetime)); + INIT_OBJ_RES_DATA(res[index], i, SERVER_DEFAULT_MIN_PERIOD_ID, + &default_min_period[index], + sizeof(*default_min_period)); + INIT_OBJ_RES_DATA(res[index], i, SERVER_DEFAULT_MAX_PERIOD_ID, + &default_max_period[index], + sizeof(*default_max_period)); + INIT_OBJ_RES_EXECUTE(res[index], i, SERVER_DISABLE_ID, disable_cb); + INIT_OBJ_RES_DATA(res[index], i, SERVER_DISABLE_TIMEOUT_ID, + &disabled_timeout[index], + sizeof(*disabled_timeout)); + INIT_OBJ_RES_DATA(res[index], i, SERVER_STORE_NOTIFY_ID, + &server_flag_store_notify[index], + sizeof(*server_flag_store_notify)); + /* Mark Transport Binding RO as we only support UDP atm */ + INIT_OBJ_RES_DATA(res[index], i, SERVER_TRANSPORT_BINDING_ID, + transport_binding[index], TRANSPORT_BINDING_LEN); + INIT_OBJ_RES_EXECUTE(res[index], i, SERVER_REG_UPDATE_TRIGGER_ID, + update_trigger_cb); + + inst[index].resources = res[index]; + inst[index].resource_count = i; + SYS_LOG_DBG("Create LWM2M server instance: %d", obj_inst_id); + return &inst[index]; +} + +static int lwm2m_server_init(struct device *dev) +{ + struct lwm2m_engine_obj_inst *obj_inst = NULL; + int ret = 0; + + /* Set default values */ + memset(inst, 0, sizeof(*inst) * MAX_INSTANCE_COUNT); + memset(res, 0, sizeof(struct lwm2m_engine_res_inst) * + MAX_INSTANCE_COUNT * SERVER_MAX_ID); + + server.obj_id = LWM2M_OBJECT_SERVER_ID; + server.fields = fields; + server.field_count = sizeof(fields) / sizeof(*fields); + server.max_instance_count = MAX_INSTANCE_COUNT; + server.create_cb = server_create; + lwm2m_register_obj(&server); + + /* auto create the first instance */ + ret = lwm2m_create_obj_inst(LWM2M_OBJECT_SERVER_ID, 0, &obj_inst); + if (ret < 0) { + SYS_LOG_ERR("Create LWM2M server instance 0 error: %d", ret); + } + + return ret; +} + +SYS_INIT(lwm2m_server_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/subsys/net/lib/lwm2m/lwm2m_object.h b/subsys/net/lib/lwm2m/lwm2m_object.h new file mode 100644 index 0000000000000..68ffbba0e15f0 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_object.h @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2015, Yanzi Networks AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Authors: + * Joakim Eriksson + * Niclas Finne + */ + +#ifndef LWM2M_OBJECT_H_ +#define LWM2M_OBJECT_H_ + +/* stdint conversions */ +#include +#include +#include +#include +#include +#include +#include + +/* #####/###/#####/### + NULL */ +#define MAX_RESOURCE_LEN 20 + +/* operations */ +#define LWM2M_OP_NONE 0 +#define LWM2M_OP_READ 1 +#define LWM2M_OP_DISCOVER 2 +#define LWM2M_OP_WRITE 3 +#define LWM2M_OP_WRITE_ATTR 4 +#define LWM2M_OP_EXECUTE 5 +#define LWM2M_OP_DELETE 6 + +/* operation permission bits */ +#define LWM2M_OP_BIT(op) (1 << (op - 1)) + +/* resource permissions */ +#define LWM2M_PERM_R LWM2M_OP_BIT(LWM2M_OP_READ) +#define LWM2M_PERM_W LWM2M_OP_BIT(LWM2M_OP_WRITE) +#define LWM2M_PERM_X LWM2M_OP_BIT(LWM2M_OP_EXECUTE) +#define LWM2M_PERM_RW (LWM2M_OP_BIT(LWM2M_OP_READ) | \ + LWM2M_OP_BIT(LWM2M_OP_WRITE)) +#define LWM2M_PERM_RWX (LWM2M_OP_BIT(LWM2M_OP_READ) | \ + LWM2M_OP_BIT(LWM2M_OP_WRITE) | \ + LWM2M_OP_BIT(LWM2M_OP_EXECUTE)) + +/* resource types */ +#define LWM2M_RES_TYPE_NONE 0 +#define LWM2M_RES_TYPE_OPAQUE 1 +#define LWM2M_RES_TYPE_STRING 2 +#define LWM2M_RES_TYPE_UINT64 3 +#define LWM2M_RES_TYPE_U64 3 +#define LWM2M_RES_TYPE_UINT 4 +#define LWM2M_RES_TYPE_U32 4 +#define LWM2M_RES_TYPE_U16 5 +#define LWM2M_RES_TYPE_U8 6 +#define LWM2M_RES_TYPE_INT64 7 +#define LWM2M_RES_TYPE_S64 7 +#define LWM2M_RES_TYPE_INT 8 +#define LWM2M_RES_TYPE_S32 8 +#define LWM2M_RES_TYPE_S16 9 +#define LWM2M_RES_TYPE_S8 10 +#define LWM2M_RES_TYPE_BOOL 11 +#define LWM2M_RES_TYPE_TIME 12 +#define LWM2M_RES_TYPE_FLOAT32 13 +#define LWM2M_RES_TYPE_FLOAT64 14 + +/* remember that we have already output a value - can be between two block's */ +#define WRITER_OUTPUT_VALUE 1 +#define WRITER_RESOURCE_INSTANCE 2 + +struct lwm2m_engine_obj; +struct lwm2m_engine_context; + +/* path representing object instances */ +struct lwm2m_obj_path { + u16_t obj_id; + u16_t obj_inst_id; + u16_t res_id; + u16_t res_inst_id; + u8_t level; /* 0/1/2/3 = 3 = resource */ +}; + +#define OBJ_FIELD(res_id, perm, type, multi_max) \ + { res_id, LWM2M_PERM_ ## perm, LWM2M_RES_TYPE_ ## type, multi_max } + +#define OBJ_FIELD_DATA(res_id, perm, type) \ + OBJ_FIELD(res_id, perm, type, 1) + +#define OBJ_FIELD_MULTI_DATA(res_id, perm, type, multi_max) \ + OBJ_FIELD(res_id, perm, type, multi_max) + +#define OBJ_FIELD_EXECUTE(res_id) \ + OBJ_FIELD(res_id, X, NONE, 0) + +struct lwm2m_engine_obj_field { + u16_t res_id; + u8_t permissions; + u8_t data_type; + u8_t multi_max_count; +}; + +typedef struct lwm2m_engine_obj_inst * + (*lwm2m_engine_obj_create_cb_t)(u16_t obj_inst_id); +typedef int (*lwm2m_engine_obj_delete_cb_t)(u16_t obj_inst_id); + +struct lwm2m_engine_obj { + sys_snode_t node; + u16_t obj_id; + struct lwm2m_engine_obj_field *fields; + u16_t field_count; + u16_t instance_count; + u16_t max_instance_count; + lwm2m_engine_obj_create_cb_t create_cb; + lwm2m_engine_obj_delete_cb_t delete_cb; +}; + +#define INIT_OBJ_RES(res_var, index_var, id_val, multi_var, \ + data_val, data_val_len, r_cb, pre_w_cb, post_w_cb, ex_cb) \ + res_var[index_var].res_id = id_val; \ + res_var[index_var].multi_count_var = multi_var; \ + res_var[index_var].data_ptr = data_val; \ + res_var[index_var].data_len = data_val_len; \ + res_var[index_var].read_cb = r_cb; \ + res_var[index_var].pre_write_cb = pre_w_cb; \ + res_var[index_var].post_write_cb = post_w_cb; \ + res_var[index_var++].execute_cb = ex_cb + +#define INIT_OBJ_RES_MULTI_DATA(res_var, index_var, id_val, multi_var, \ + data_val, data_val_len) \ + INIT_OBJ_RES(res_var, index_var, id_val, multi_var, data_val, \ + data_val_len, NULL, NULL, NULL, NULL) + +#define INIT_OBJ_RES_DATA(res_var, index_var, id_val, data_val, data_val_len) \ + INIT_OBJ_RES_MULTI_DATA(res_var, index_var, id_val, NULL, \ + data_val, data_val_len) + +#define INIT_OBJ_RES_DUMMY(res_var, index_var, id_val) \ + INIT_OBJ_RES_MULTI_DATA(res_var, index_var, id_val, NULL, NULL, 0) + +#define INIT_OBJ_RES_EXECUTE(res_var, index_var, id_val, ex_cb) \ + INIT_OBJ_RES(res_var, index_var, id_val, NULL, NULL, 0, \ + NULL, NULL, NULL, ex_cb) + +struct lwm2m_engine_res_inst { + char path[MAX_RESOURCE_LEN]; /* 3/0/0 */ + u16_t res_id; + u8_t *multi_count_var; + void *data_ptr; + size_t data_len; + /* runtime field attributes (WRITE_ATTR) */ + + /* callbacks set by user code on obj instance */ + lwm2m_engine_get_data_cb_t read_cb; + lwm2m_engine_get_data_cb_t pre_write_cb; + lwm2m_engine_set_data_cb_t post_write_cb; + lwm2m_engine_exec_cb_t execute_cb; +}; + +struct lwm2m_engine_obj_inst { + sys_snode_t node; + char path[MAX_RESOURCE_LEN]; /* 3/0 */ + struct lwm2m_engine_obj *obj; + u16_t obj_inst_id; + struct lwm2m_engine_res_inst *resources; + u16_t resource_count; +}; + +struct lwm2m_output_context { + struct zoap_packet *out_zpkt; + u8_t writer_flags; /* flags for reader/writer */ + u8_t *outbuf; + u16_t outsize; + u32_t outlen; + u8_t mark_pos_ri; /* mark pos for last resource instance */ + const struct lwm2m_writer *writer; +}; + +struct lwm2m_input_context { + struct zoap_packet *in_zpkt; + u8_t *inbuf; + u16_t insize; + s32_t inpos; + u16_t last_value_len; + const struct lwm2m_reader *reader; +}; + +/* LWM2M format writer for the various formats supported */ +struct lwm2m_writer { + size_t (*put_begin)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path); + size_t (*put_end)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path); + size_t (*put_begin_ri)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path); + size_t (*put_end_ri)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path); + size_t (*put_s8)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s8_t value); + size_t (*put_s16)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s16_t value); + size_t (*put_s32)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s32_t value); + size_t (*put_s64)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s64_t value); + size_t (*put_string)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + const char *value, size_t strlen); + size_t (*put_float32fix)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float32_value_t *value); + size_t (*put_float64fix)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float64_value_t *value); + size_t (*put_bool)(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + bool value); +}; + +struct lwm2m_reader { + size_t (*get_s32)(struct lwm2m_input_context *in, + s32_t *value); + size_t (*get_s64)(struct lwm2m_input_context *in, + s64_t *value); + size_t (*get_string)(struct lwm2m_input_context *in, + u8_t *value, size_t strlen); + size_t (*get_float32fix)(struct lwm2m_input_context *in, + float32_value_t *value); + size_t (*get_float64fix)(struct lwm2m_input_context *in, + float64_value_t *value); + size_t (*get_bool)(struct lwm2m_input_context *in, + bool *value); +}; + +/* LWM2M engine context */ +struct lwm2m_engine_context { + struct lwm2m_input_context *in; + struct lwm2m_output_context *out; + struct lwm2m_obj_path *path; + u8_t operation; +}; + +/* inline multi-format write / read functions */ + +static inline size_t engine_put_begin(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + if (out->writer->put_begin) { + return out->writer->put_begin(out, path); + } + + return 0; +} + +static inline size_t engine_put_end(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + if (out->writer->put_end) { + return out->writer->put_end(out, path); + } + + return 0; +} + +static inline size_t engine_put_begin_ri(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + if (out->writer->put_begin_ri) { + return out->writer->put_begin_ri(out, path); + } + + return 0; +} + +static inline size_t engine_put_end_ri(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + if (out->writer->put_end_ri) { + return out->writer->put_end_ri(out, path); + } + + return 0; +} + +static inline size_t engine_put_s8(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s8_t value) +{ + return out->writer->put_s8(out, path, value); +} + +static inline size_t engine_put_s16(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s16_t value) +{ + return out->writer->put_s16(out, path, value); +} + +static inline size_t engine_put_s32(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s32_t value) +{ + return out->writer->put_s32(out, path, value); +} + +static inline size_t engine_put_s64(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + s64_t value) +{ + return out->writer->put_s64(out, path, value); +} + +static inline size_t engine_put_string(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + const char *value, size_t strlen) +{ + return out->writer->put_string(out, path, value, strlen); +} + +static inline size_t engine_put_float32fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float32_value_t *value) +{ + return out->writer->put_float32fix(out, path, value); +} + +static inline size_t engine_put_float64fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float64_value_t *value) +{ + return out->writer->put_float64fix(out, path, value); +} + +static inline size_t engine_put_bool(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + bool value) +{ + return out->writer->put_bool(out, path, value); +} + +static inline size_t engine_get_s32(struct lwm2m_input_context *in, + s32_t *value) +{ + return in->reader->get_s32(in, value); +} + +static inline size_t engine_get_s64(struct lwm2m_input_context *in, + s64_t *value) +{ + return in->reader->get_s64(in, value); +} + +static inline size_t engine_get_string(struct lwm2m_input_context *in, + u8_t *value, size_t strlen) +{ + return in->reader->get_string(in, value, strlen); +} + +static inline size_t engine_get_float32fix(struct lwm2m_input_context *in, + float32_value_t *value) +{ + return in->reader->get_float32fix(in, value); +} + +static inline size_t engine_get_float64fix(struct lwm2m_input_context *in, + float64_value_t *value) +{ + return in->reader->get_float64fix(in, value); +} + +static inline size_t engine_get_bool(struct lwm2m_input_context *in, + bool *value) +{ + return in->reader->get_bool(in, value); +} + +#endif /* LWM2M_OBJECT_H_ */ diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.c b/subsys/net/lib/lwm2m/lwm2m_rd_client.c new file mode 100644 index 0000000000000..700747a035aa5 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.c @@ -0,0 +1,877 @@ +/* + * Copyright (c) 2015, Yanzi Networks AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original authors: + * Joakim Eriksson + * Niclas Finne + * Joel Hoglund + */ + +#define SYS_LOG_DOMAIN "lib/lwm2m_rd_client" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" + +#define LWM2M_RD_CLIENT_URI "rd" + +#define SECONDS_TO_UPDATE_EARLY 2 +#define STATE_MACHINE_UPDATE_INTERVAL 500 + +#define LWM2M_PEER_PORT CONFIG_LWM2M_PEER_PORT +#define LWM2M_BOOTSTRAP_PORT CONFIG_LWM2M_BOOTSTRAP_PORT + +/* Leave room for 32 hexadeciaml digits (UUID) + NULL */ +#define CLIENT_EP_LEN 33 + +/* The states for the RD client state machine */ +/* + * When node is unregistered it ends up in UNREGISTERED + * and this is going to be there until use X or Y kicks it + * back into INIT again + */ +enum sm_engine_state { + ENGINE_INIT, + ENGINE_DO_BOOTSTRAP, + ENGINE_BOOTSTRAP_SENT, + ENGINE_BOOTSTRAP_DONE, + ENGINE_DO_REGISTRATION, + ENGINE_REGISTRATION_SENT, + ENGINE_REGISTRATION_DONE, + ENGINE_UPDATE_SENT, + ENGINE_DEREGISTER, + ENGINE_DEREGISTER_SENT, + ENGINE_DEREGISTER_FAILED, + ENGINE_DEREGISTERED +}; + +struct lwm2m_rd_client_info { + u16_t lifetime; + struct net_context *net_ctx; + struct sockaddr bs_server; + struct sockaddr reg_server; + u8_t engine_state; + u8_t use_bootstrap; + u8_t has_bs_server_info; + u8_t use_registration; + u8_t has_registration_info; + u8_t registered; + u8_t bootstrapped; /* bootstrap done */ + u8_t trigger_update; + + s64_t last_update; + + char ep_name[CLIENT_EP_LEN]; + char server_ep[CLIENT_EP_LEN]; +}; + +static K_THREAD_STACK_DEFINE(lwm2m_rd_client_thread_stack, + CONFIG_LWM2M_RD_CLIENT_STACK_SIZE); +struct k_thread lwm2m_rd_client_thread_data; + +/* HACK: remove when engine transactions are ready */ +#define NUM_PENDINGS CONFIG_LWM2M_ENGINE_MAX_PENDING +#define NUM_REPLIES CONFIG_LWM2M_ENGINE_MAX_REPLIES + +extern struct zoap_pending pendings[NUM_PENDINGS]; +extern struct zoap_reply replies[NUM_REPLIES]; +extern struct k_delayed_work retransmit_work; + +/* buffers */ +static char query_buffer[64]; /* allocate some data for queries and updates */ +static u8_t client_data[256]; /* allocate some data for the RD */ + +#define CLIENT_INSTANCE_COUNT CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT +static struct lwm2m_rd_client_info clients[CLIENT_INSTANCE_COUNT]; +static int client_count; + +static void set_sm_state(int index, u8_t state) +{ + /* TODO: add locking? */ + clients[index].engine_state = state; +} + +static u8_t get_sm_state(int index) +{ + /* TODO: add locking? */ + return clients[index].engine_state; +} + +static int find_clients_index(const struct sockaddr *addr, + bool check_bs_server) +{ + int index = -1, i; + + for (i = 0; i < client_count; i++) { + if (check_bs_server) { + if (memcmp(addr, &clients[i].bs_server, + sizeof(addr)) == 0) { + index = i; + break; + } + } else { + if (memcmp(addr, &clients[i].reg_server, + sizeof(addr)) == 0) { + index = i; + break; + } + } + } + + return index; +} + +/* force re-update with remote peer(s) */ +void engine_trigger_update(void) +{ + int index; + + for (index = 0; index < client_count; index++) { + /* TODO: add locking? */ + clients[index].trigger_update = 1; + } +} + +/* state machine reply callbacks */ + +static int do_bootstrap_reply_cb(const struct zoap_packet *response, + struct zoap_reply *reply, + const struct sockaddr *from) +{ + u8_t code; + int index; + + code = zoap_header_get_code(response); + SYS_LOG_DBG("Bootstrap callback (code:%u.%u)", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + + index = find_clients_index(from, true); + if (index < 0) { + SYS_LOG_ERR("Bootstrap client index not found."); + return 0; + } + + if (code == ZOAP_RESPONSE_CODE_CHANGED) { + SYS_LOG_DBG("Considered done!"); + set_sm_state(index, ENGINE_BOOTSTRAP_DONE); + } else if (code == ZOAP_RESPONSE_CODE_NOT_FOUND) { + SYS_LOG_ERR("Failed: NOT_FOUND. Not Retrying."); + set_sm_state(index, ENGINE_DO_REGISTRATION); + } else { + /* TODO: Read payload for error message? */ + SYS_LOG_ERR("Failed with code %u.%u. Retrying ...", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + set_sm_state(index, ENGINE_INIT); + } + + return 0; +} + +static int do_registration_reply_cb(const struct zoap_packet *response, + struct zoap_reply *reply, + const struct sockaddr *from) +{ + struct zoap_option options[2]; + u8_t code; + int ret, index; + + code = zoap_header_get_code(response); + SYS_LOG_DBG("Registration callback (code:%u.%u)", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + + index = find_clients_index(from, false); + if (index < 0) { + SYS_LOG_ERR("Registration client index not found."); + return 0; + } + + /* check state and possibly set registration to done */ + if (code == ZOAP_RESPONSE_CODE_CREATED) { + ret = zoap_find_options(response, ZOAP_OPTION_LOCATION_PATH, + options, 2); + if (ret < 0) { + return ret; + } + + if (ret < 2) { + SYS_LOG_ERR("Unexpected endpoint data returned."); + return -EINVAL; + } + + /* option[0] should be "rd" */ + + if (options[1].len + 1 > sizeof(clients[index].server_ep)) { + SYS_LOG_ERR("Unexpected length of query: " + "%u (expected %zu)\n", + options[1].len, + sizeof(clients[index].server_ep)); + return -EINVAL; + } + + memcpy(clients[index].server_ep, options[1].value, + options[1].len); + clients[index].server_ep[options[1].len] = '\0'; + set_sm_state(index, ENGINE_REGISTRATION_DONE); + clients[index].registered = 1; + SYS_LOG_INF("Registration Done (EP='%s')", + clients[index].server_ep); + + return 0; + } else if (code == ZOAP_RESPONSE_CODE_NOT_FOUND) { + SYS_LOG_ERR("Failed: NOT_FOUND. Not Retrying."); + set_sm_state(index, ENGINE_REGISTRATION_DONE); + return 0; + } + + /* TODO: Read payload for error message? */ + /* Possible error response codes: 4.00 Bad request & 4.03 Forbidden */ + SYS_LOG_ERR("failed with code %u.%u. Re-init network", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + set_sm_state(index, ENGINE_INIT); + return 0; +} + +static int do_update_reply_cb(const struct zoap_packet *response, + struct zoap_reply *reply, + const struct sockaddr *from) +{ + u8_t code; + int index; + + code = zoap_header_get_code(response); + SYS_LOG_DBG("Update callback (code:%u.%u)", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + + index = find_clients_index(from, false); + if (index < 0) { + SYS_LOG_ERR("Registration client index not found."); + return 0; + } + + /* If NOT_FOUND just continue on */ + if ((code == ZOAP_RESPONSE_CODE_CHANGED) || + (code == ZOAP_RESPONSE_CODE_CREATED)) { + set_sm_state(index, ENGINE_REGISTRATION_DONE); + SYS_LOG_DBG("Update Done"); + return 0; + } + + /* TODO: Read payload for error message? */ + /* Possible error response codes: 4.00 Bad request & 4.04 Not Found */ + SYS_LOG_ERR("Failed with code %u.%u. Retrying registration", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + clients[index].registered = 0; + set_sm_state(index, ENGINE_DO_REGISTRATION); + + return 0; +} + +static int do_deregister_reply_cb(const struct zoap_packet *response, + struct zoap_reply *reply, + const struct sockaddr *from) +{ + u8_t code; + int index; + + code = zoap_header_get_code(response); + SYS_LOG_DBG("Deregister callback (code:%u.%u)", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + + index = find_clients_index(from, false); + if (index < 0) { + SYS_LOG_ERR("Registration clients index not found."); + return 0; + } + + if (code == ZOAP_RESPONSE_CODE_DELETED) { + clients[index].registered = 0; + SYS_LOG_DBG("Deregistration success"); + set_sm_state(index, ENGINE_DEREGISTERED); + } else { + SYS_LOG_ERR("failed with code %u.%u", + ZOAP_RESPONSE_CODE_CLASS(code), + ZOAP_RESPONSE_CODE_DETAIL(code)); + if (get_sm_state(index) == ENGINE_DEREGISTER_SENT) { + set_sm_state(index, ENGINE_DEREGISTER_FAILED); + } + } + + return 0; +} + +/* state machine step functions */ + +static int sm_do_init(int index) +{ + SYS_LOG_DBG("RD Client started with endpoint " + "'%s' and client lifetime %d", + clients[index].ep_name, + clients[index].lifetime); + /* Zephyr has joined network already */ + clients[index].has_registration_info = 1; + clients[index].registered = 0; + clients[index].bootstrapped = 0; + clients[index].trigger_update = 0; +#if defined(CONFIG_LWM2M_BOOTSTRAP_SERVER) + clients[index].use_bootstrap = 1; +#else + clients[index].use_registration = 1; +#endif + if (clients[index].lifetime == 0) { + clients[index].lifetime = CONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME; + } + /* Do bootstrap or registration */ + if (clients[index].use_bootstrap) { + set_sm_state(index, ENGINE_DO_BOOTSTRAP); + } else { + set_sm_state(index, ENGINE_DO_REGISTRATION); + } + + return 0; +} + +static int sm_do_bootstrap(int index) +{ + struct zoap_packet request; + struct net_pkt *pkt = NULL; + struct zoap_pending *pending = NULL; + struct zoap_reply *reply = NULL; + int ret = 0; + + if (clients[index].use_bootstrap && + clients[index].bootstrapped == 0 && + clients[index].has_bs_server_info) { + + ret = lwm2m_init_message(clients[index].net_ctx, + &request, &pkt, ZOAP_TYPE_CON, + ZOAP_METHOD_POST, 0, NULL, 0); + if (ret) { + goto cleanup; + } + + zoap_add_option(&request, ZOAP_OPTION_URI_PATH, + "bs", strlen("bs")); + + snprintf(query_buffer, sizeof(query_buffer) - 1, + "ep=%s", clients[index].ep_name); + zoap_add_option(&request, ZOAP_OPTION_URI_QUERY, + query_buffer, strlen(query_buffer)); + + pending = lwm2m_init_message_pending(&request, + &clients[index].bs_server, + pendings, NUM_PENDINGS); + if (!pending) { + ret = -ENOMEM; + goto cleanup; + } + + reply = zoap_reply_next_unused(replies, NUM_REPLIES); + if (!reply) { + SYS_LOG_ERR("No resources for waiting for replies."); + ret = -ENOMEM; + goto cleanup; + } + + zoap_reply_init(reply, &request); + reply->reply = do_bootstrap_reply_cb; + + /* log the bootstrap attempt */ + SYS_LOG_DBG("Register ID with bootstrap server [%s] as '%s'", + lwm2m_sprint_ip_addr(&clients[index].bs_server), + query_buffer); + + ret = net_context_sendto(pkt, &clients[index].bs_server, + NET_SOCKADDR_MAX_SIZE, + NULL, 0, NULL, NULL); + if (ret < 0) { + SYS_LOG_ERR("Error sending LWM2M packet (err:%d).", + ret); + goto cleanup; + } + + zoap_pending_cycle(pending); + k_delayed_work_submit(&retransmit_work, pending->timeout); + set_sm_state(index, ENGINE_BOOTSTRAP_SENT); + } + return ret; + +cleanup: + lwm2m_init_message_cleanup(pkt, pending, reply); + return ret; +} + +static int sm_bootstrap_done(int index) +{ + /* TODO: Fix this */ + /* check that we should still use bootstrap */ + if (clients[index].use_bootstrap) { +#ifdef CONFIG_LWM2M_SECURITY_OBJ_SUPPORT + int i; + + SYS_LOG_DBG("*** Bootstrap - checking for server info ..."); + + /* get the server URI */ + if (sec_data->server_uri_len > 0) { + /* TODO: Write endpoint parsing function */ +#if 0 + if (!parse_endpoint(sec_data->server_uri, + sec_data->server_uri_len, + &clients[index].reg_server)) { +#else + if (true) { +#endif + SYS_LOG_ERR("Failed to parse URI!"); + } else { + clients[index].has_registration_info = 1; + clients[index].registered = 0; + clients[index].bootstrapped++; + } + } else { + SYS_LOG_ERR("** failed to parse URI"); + } + + /* if we did not register above - then fail this and restart */ + if (clients[index].bootstrapped == 0) { + /* Not ready - Retry with the bootstrap server again */ + set_sm_state(index, ENGINE_DO_BOOTSTRAP); + } else { + set_sm_state(index, ENGINE_DO_REGISTRATION); + } + } else { +#endif + set_sm_state(index, ENGINE_DO_REGISTRATION); + } + + return 0; +} + +static int sm_send_registration(int index, bool send_obj_support_data, + zoap_reply_t reply_cb) +{ + struct zoap_packet request; + struct net_pkt *pkt = NULL; + struct zoap_pending *pending = NULL; + struct zoap_reply *reply = NULL; + u8_t *payload; + u16_t client_data_len, len; + int ret = 0; + + /* remember the last reg time */ + clients[index].last_update = k_uptime_get(); + ret = lwm2m_init_message(clients[index].net_ctx, + &request, &pkt, ZOAP_TYPE_CON, + ZOAP_METHOD_POST, 0, NULL, 0); + if (ret) { + goto cleanup; + } + + zoap_add_option(&request, ZOAP_OPTION_URI_PATH, + LWM2M_RD_CLIENT_URI, + strlen(LWM2M_RD_CLIENT_URI)); + + if (!clients[index].registered) { + /* include client endpoint in URI QUERY on 1st registration */ + snprintf(query_buffer, sizeof(query_buffer) - 1, + "ep=%s", clients[index].ep_name); + zoap_add_option(&request, ZOAP_OPTION_URI_QUERY, + query_buffer, strlen(query_buffer)); + } else { + /* include server endpoint in URI PATH otherwise */ + zoap_add_option(&request, ZOAP_OPTION_URI_PATH, + clients[index].server_ep, + strlen(clients[index].server_ep)); + } + + snprintf(query_buffer, sizeof(query_buffer) - 1, + "lt=%d", clients[index].lifetime); + zoap_add_option(&request, ZOAP_OPTION_URI_QUERY, + query_buffer, strlen(query_buffer)); + /* TODO: add supported binding query string */ + + if (send_obj_support_data) { + /* generate the rd data */ + client_data_len = lwm2m_get_rd_data(client_data, + sizeof(client_data)); + payload = zoap_packet_get_payload(&request, &len); + if (!payload) { + ret = -EINVAL; + goto cleanup; + } + + memcpy(payload, client_data, client_data_len); + ret = zoap_packet_set_used(&request, client_data_len); + if (ret) { + goto cleanup; + } + } + + pending = lwm2m_init_message_pending(&request, + &clients[index].reg_server, + pendings, NUM_PENDINGS); + if (!pending) { + ret = -ENOMEM; + goto cleanup; + } + + reply = zoap_reply_next_unused(replies, NUM_REPLIES); + if (!reply) { + SYS_LOG_ERR("No resources for waiting for replies."); + ret = -ENOMEM; + goto cleanup; + } + + zoap_reply_init(reply, &request); + reply->reply = reply_cb; + + /* log the registration attempt */ + SYS_LOG_DBG("registration sent [%s]", + lwm2m_sprint_ip_addr(&clients[index].reg_server)); + + ret = net_context_sendto(pkt, &clients[index].reg_server, + NET_SOCKADDR_MAX_SIZE, + NULL, 0, NULL, NULL); + if (ret < 0) { + SYS_LOG_ERR("Error sending LWM2M packet (err:%d).", + ret); + goto cleanup; + } + + zoap_pending_cycle(pending); + k_delayed_work_submit(&retransmit_work, pending->timeout); + return ret; + +cleanup: + lwm2m_init_message_cleanup(pkt, pending, reply); + return ret; +} + +static int sm_do_registration(int index) +{ + int ret = 0; + + if (clients[index].use_registration && + !clients[index].registered && + clients[index].has_registration_info) { + ret = sm_send_registration(index, true, + do_registration_reply_cb); + if (!ret) { + set_sm_state(index, ENGINE_REGISTRATION_SENT); + } else { + SYS_LOG_ERR("Registration err: %d", ret); + } + } + + return ret; +} + +static int sm_registration_done(int index) +{ + int ret = 0; + bool forced_update; + + /* check for lifetime seconds - 1 so that we can update early */ + if (clients[index].registered && + (clients[index].trigger_update || + ((clients[index].lifetime - SECONDS_TO_UPDATE_EARLY) <= + (k_uptime_get() - clients[index].last_update) / 1000))) { + forced_update = clients[index].trigger_update; + clients[index].trigger_update = 0; + ret = sm_send_registration(index, forced_update, + do_update_reply_cb); + if (!ret) { + set_sm_state(index, ENGINE_UPDATE_SENT); + } else { + SYS_LOG_ERR("Registration update err: %d", ret); + } + } + + return ret; +} + +static int sm_do_deregister(int index) +{ + struct zoap_packet request; + struct net_pkt *pkt = NULL; + struct zoap_pending *pending = NULL; + struct zoap_reply *reply = NULL; + int ret; + + ret = lwm2m_init_message(clients[index].net_ctx, + &request, &pkt, ZOAP_TYPE_CON, + ZOAP_METHOD_DELETE, 0, NULL, 0); + if (ret) { + goto cleanup; + } + + zoap_add_option(&request, ZOAP_OPTION_URI_PATH, + clients[index].server_ep, + strlen(clients[index].server_ep)); + + pending = lwm2m_init_message_pending(&request, + &clients[index].reg_server, + pendings, NUM_PENDINGS); + if (!pending) { + ret = -ENOMEM; + goto cleanup; + } + + reply = zoap_reply_next_unused(replies, NUM_REPLIES); + if (!reply) { + SYS_LOG_ERR("No resources for waiting for replies."); + ret = -ENOMEM; + goto cleanup; + } + + zoap_reply_init(reply, &request); + reply->reply = do_deregister_reply_cb; + + SYS_LOG_INF("Deregister from '%s'", clients[index].server_ep); + + ret = net_context_sendto(pkt, &clients[index].reg_server, + NET_SOCKADDR_MAX_SIZE, + NULL, 0, NULL, NULL); + if (ret < 0) { + SYS_LOG_ERR("Error sending LWM2M packet (err:%d).", + ret); + goto cleanup; + } + + zoap_pending_cycle(pending); + k_delayed_work_submit(&retransmit_work, pending->timeout); + set_sm_state(index, ENGINE_DEREGISTER_SENT); + return ret; + +cleanup: + lwm2m_init_message_cleanup(pkt, pending, reply); + return ret; +} + +static void lwm2m_rd_client_service(void) +{ + int index; + + while (true) { + for (index = 0; index < client_count; index++) { + switch (get_sm_state(index)) { + + case ENGINE_INIT: + sm_do_init(index); + break; + + case ENGINE_DO_BOOTSTRAP: + sm_do_bootstrap(index); + break; + + case ENGINE_BOOTSTRAP_SENT: + /* wait for bootstrap to be done */ + break; + + case ENGINE_BOOTSTRAP_DONE: + sm_bootstrap_done(index); + break; + + case ENGINE_DO_REGISTRATION: + sm_do_registration(index); + break; + + case ENGINE_REGISTRATION_SENT: + /* wait registration to be done */ + break; + + case ENGINE_REGISTRATION_DONE: + sm_registration_done(index); + break; + + case ENGINE_UPDATE_SENT: + /* wait update to be done */ + break; + + case ENGINE_DEREGISTER: + sm_do_deregister(index); + break; + + case ENGINE_DEREGISTER_SENT: + break; + + case ENGINE_DEREGISTER_FAILED: + break; + + case ENGINE_DEREGISTERED: + break; + + default: + SYS_LOG_ERR("Unhandled state: %d", + get_sm_state(index)); + + } + + k_yield(); + } + + /* + * TODO: calculate the diff between the start of the loop + * and subtract that from the update interval + */ + k_sleep(K_MSEC(STATE_MACHINE_UPDATE_INTERVAL)); + } +} + +static bool peer_addr_exist(struct sockaddr *peer_addr) +{ + bool ret = false; + int i; + + /* look for duplicate peer_addr */ + for (i = 0; i < client_count; i++) { +#if defined(CONFIG_NET_IPV6) + if (peer_addr->family == AF_INET6 && net_ipv6_addr_cmp( + &net_sin6(&clients[i].bs_server)->sin6_addr, + &net_sin6(peer_addr)->sin6_addr)) { + ret = true; + break; + } + + if (peer_addr->family == AF_INET6 && net_ipv6_addr_cmp( + &net_sin6(&clients[i].reg_server)->sin6_addr, + &net_sin6(peer_addr)->sin6_addr)) { + ret = true; + break; + } +#endif + +#if defined(CONFIG_NET_IPV4) + if (peer_addr->family == AF_INET && net_ipv4_addr_cmp( + &net_sin(&clients[i].bs_server)->sin_addr, + &net_sin(peer_addr)->sin_addr)) { + ret = true; + break; + } + + if (peer_addr->family == AF_INET && net_ipv4_addr_cmp( + &net_sin(&clients[i].reg_server)->sin_addr, + &net_sin(peer_addr)->sin_addr)) { + ret = true; + break; + } +#endif + } + + return ret; +} + +static void set_ep_ports(int index) +{ +#if defined(CONFIG_NET_IPV6) + if (clients[index].bs_server.family == AF_INET6) { + net_sin6(&clients[index].bs_server)->sin6_port = + htons(LWM2M_BOOTSTRAP_PORT); + } + + if (clients[index].reg_server.family == AF_INET6) { + net_sin6(&clients[index].reg_server)->sin6_port = + htons(LWM2M_PEER_PORT); + } +#endif + +#if defined(CONFIG_NET_IPV4) + if (clients[index].bs_server.family == AF_INET) { + net_sin(&clients[index].bs_server)->sin_port = + htons(LWM2M_BOOTSTRAP_PORT); + } + + if (clients[index].reg_server.family == AF_INET) { + net_sin(&clients[index].reg_server)->sin_port = + htons(LWM2M_PEER_PORT); + } +#endif +} + +int lwm2m_rd_client_start(struct net_context *net_ctx, + struct sockaddr *peer_addr, + const char *ep_name) +{ + int index; + + if (client_count + 1 > CLIENT_INSTANCE_COUNT) { + return -ENOMEM; + } + + if (peer_addr_exist(peer_addr)) { + return -EEXIST; + } + + /* Server Peer IP information */ + /* TODO: use server URI data from security */ + index = client_count; + client_count++; + clients[index].net_ctx = net_ctx; + memcpy(&clients[index].reg_server, peer_addr, sizeof(struct sockaddr)); + memcpy(&clients[index].bs_server, peer_addr, sizeof(struct sockaddr)); + set_ep_ports(index); + 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); + + return 0; +} + +static int lwm2m_rd_client_init(struct device *dev) +{ + k_thread_create(&lwm2m_rd_client_thread_data, + &lwm2m_rd_client_thread_stack[0], + K_THREAD_STACK_SIZEOF(lwm2m_rd_client_thread_stack), + (k_thread_entry_t) lwm2m_rd_client_service, + NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + SYS_LOG_DBG("LWM2M RD client thread started"); + return 0; +} + +SYS_INIT(lwm2m_rd_client_init, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.h b/subsys/net/lib/lwm2m/lwm2m_rd_client.h new file mode 100644 index 0000000000000..6d839720e8d92 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016, SICS Swedish ICT AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LWM2M_RD_CLIENT_H +#define LWM2M_RD_CLIENT_H + +void engine_trigger_update(void); + +#endif /* LWM2M_RD_CLIENT_H */ diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_json.c b/subsys/net/lib/lwm2m/lwm2m_rw_json.c new file mode 100644 index 0000000000000..c38eda40bf42f --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_json.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2016, Eistec AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Authors: + * Joakim NohlgÃ¥rd + * Joakim Eriksson added JSON reader parts + */ + +/* + * Zephyr Contribution by Michael Scott + * - Zephyr code style changes / code cleanup + * - Move to Zephyr APIs where possible + * - Convert to Zephyr int/uint types + * - Remove engine dependency (replace with writer/reader context) + * - Add write / read int64 functions + */ + +/* + * TODO: + * - Debug formatting errors in Leshan + * - Replace magic #'s with defines + * - Research using Zephyr JSON lib for json_next_token() + */ + +#define SYS_LOG_DOMAIN "lib/lwm2m_json" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include + +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_rw_json.h" +#include "lwm2m_rw_plain_text.h" +#include "lwm2m_engine.h" + +#define T_NONE 0 +#define T_STRING_B 1 +#define T_STRING 2 +#define T_NAME 4 +#define T_VNUM 5 +#define T_OBJ 6 +#define T_VAL 7 + +#define SEPARATOR(f) ((f & WRITER_OUTPUT_VALUE) ? "," : "") + +/* writer modes */ +#define MODE_NONE 0 +#define MODE_INSTANCE 1 +#define MODE_VALUE 2 +#define MODE_READY 3 + +/* Simlified JSON style reader for reading in values from a LWM2M JSON string */ +int json_next_token(struct lwm2m_input_context *in, struct json_data *json) +{ + int pos; + u8_t type = T_NONE; + u8_t vpos_start = 0; + u8_t vpos_end = 0; + u8_t cont; + u8_t wscount = 0; + u8_t c; + + json->name_len = 0; + json->value_len = 0; + cont = 1; + pos = in->inpos; + + /* We will be either at start, or at a specific position */ + while (pos < in->insize && cont) { + c = in->inbuf[pos++]; + + switch (c) { + + case '{': + type = T_OBJ; + break; + + case '}': + case ',': + if (type == T_VAL || type == T_STRING) { + json->value = &in->inbuf[vpos_start]; + json->value_len = vpos_end - vpos_start - + wscount; + type = T_NONE; + cont = 0; + } + + wscount = 0; + break; + + case '\\': + /* stuffing */ + if (pos < in->insize) { + pos++; + vpos_end = pos; + } + + break; + + case '"': + if (type == T_STRING_B) { + type = T_STRING; + vpos_end = pos - 1; + wscount = 0; + } else { + type = T_STRING_B; + vpos_start = pos; + } + + break; + + case ':': + if (type == T_STRING) { + json->name = &in->inbuf[vpos_start]; + json->name_len = vpos_end - vpos_start; + vpos_start = vpos_end = pos; + type = T_VAL; + } else { + /* Could be in string or at illegal pos */ + if (type != T_STRING_B) { + SYS_LOG_ERR("ERROR - illegal ':'"); + } + } + + break; + + /* ignore whitespace */ + case ' ': + case '\n': + case '\t': + if (type != T_STRING_B) { + if (vpos_start == pos - 1) { + vpos_start = pos; + } else { + wscount++; + } + } + + /* fallthrough */ + + default: + vpos_end = pos; + + } + } + + if (cont == 0 && pos < in->insize) { + in->inpos = pos; + } + + /* OK if cont == 0 othewise we failed */ + return (cont == 0 && pos < in->insize); +} + +static size_t put_begin(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + int len; + + len = snprintf(&out->outbuf[out->outlen], + out->outsize - out->outlen, + "{\"bn\":\"/%u/%u/\",\"e\":[", + path->obj_id, path->obj_inst_id); + out->writer_flags = 0; /* set flags to zero */ + if (len < 0 || len >= out->outsize) { + return 0; + } + + out->outlen += len; + return (size_t)len; +} + +static size_t put_end(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + int len; + + len = snprintf(&out->outbuf[out->outlen], + out->outsize - out->outlen, "]}"); + if (len < 0 || len >= (out->outsize - out->outlen)) { + return 0; + } + + out->outlen += len; + return (size_t)len; +} + +static size_t put_begin_ri(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + out->writer_flags |= WRITER_RESOURCE_INSTANCE; + return 0; +} + +static size_t put_end_ri(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + out->writer_flags &= ~WRITER_RESOURCE_INSTANCE; + return 0; +} + +static size_t put_s32(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s32_t value) +{ + u8_t *outbuf; + size_t outlen; + char *sep; + int len; + + outbuf = &out->outbuf[out->outlen]; + outlen = out->outsize - out->outlen; + sep = SEPARATOR(out->writer_flags); + if (out->writer_flags & WRITER_RESOURCE_INSTANCE) { + len = snprintf(outbuf, outlen, "%s{\"n\":\"%u/%u\",\"v\":%d}", + sep, path->res_id, path->res_inst_id, value); + } else { + len = snprintf(outbuf, outlen, "%s{\"n\":\"%u\",\"v\":%d}", + sep, path->res_id, value); + } + + if (len < 0 || len >= outlen) { + return 0; + } + + SYS_LOG_DBG("JSON: Write int:%s", outbuf); + out->writer_flags |= WRITER_OUTPUT_VALUE; + out->outlen += len; + return (size_t)len; +} + +static size_t put_s16(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s16_t value) +{ + return put_s32(out, path, (s32_t)value); +} + +static size_t put_s8(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s8_t value) +{ + return put_s32(out, path, (s32_t)value); +} + +static size_t put_s64(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s64_t value) +{ + u8_t *outbuf; + size_t outlen; + char *sep; + int len; + + outbuf = &out->outbuf[out->outlen]; + outlen = out->outsize - out->outlen; + sep = SEPARATOR(out->writer_flags); + if (out->writer_flags & WRITER_RESOURCE_INSTANCE) { + len = snprintf(outbuf, outlen, "%s{\"n\":\"%u/%u\",\"v\":%lld}", + sep, path->res_id, path->res_inst_id, value); + } else { + len = snprintf(outbuf, outlen, "%s{\"n\":\"%u\",\"v\":%lld}", + sep, path->res_id, value); + } + + if (len < 0 || len >= outlen) { + return 0; + } + + SYS_LOG_DBG("JSON: Write int:%s", outbuf); + out->writer_flags |= WRITER_OUTPUT_VALUE; + out->outlen += len; + return (size_t)len; +} + +static size_t put_string(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + const char *value, size_t strlen) +{ + u8_t *outbuf; + size_t outlen; + char *sep; + size_t i; + size_t len = 0; + int res; + + outbuf = &out->outbuf[out->outlen]; + outlen = out->outsize - out->outlen; + sep = SEPARATOR(out->writer_flags); + if (out->writer_flags & WRITER_RESOURCE_INSTANCE) { + res = snprintf(outbuf, outlen, "%s{\"n\":\"%u/%u\",\"sv\":\"", + sep, path->res_id, path->res_inst_id); + } else { + res = snprintf(outbuf, outlen, "%s{\"n\":\"%u\",\"sv\":\"", + sep, path->res_id); + } + + if (res < 0 || res >= outlen) { + return 0; + } + + len += res; + for (i = 0; i < strlen && len < outlen; ++i) { + /* Escape special characters */ + /* TODO: Handle UTF-8 strings */ + if (value[i] < '\x20') { + res = snprintf(&outbuf[len], outlen - len, "\\x%x", + value[i]); + + if (res < 0 || res >= (outlen - len)) { + return 0; + } + + len += res; + continue; + } else if (value[i] == '"' || value[i] == '\\') { + outbuf[len] = '\\'; + ++len; + if (len >= outlen) { + return 0; + } + } + + outbuf[len] = value[i]; + ++len; + if (len >= outlen) { + return 0; + } + } + + res = snprintf(&outbuf[len], outlen - len, "\"}"); + if (res < 0 || res >= (outlen - len)) { + return 0; + } + + SYS_LOG_DBG("JSON: Write string:%s", outbuf); + len += res; + out->writer_flags |= WRITER_OUTPUT_VALUE; + out->outlen += len; + return len; +} + +static size_t put_float32fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float32_value_t *value) +{ + u8_t *outbuf; + size_t outlen; + char *sep; + size_t len = 0; + int res; + + outbuf = &out->outbuf[out->outlen]; + outlen = out->outsize - out->outlen; + sep = SEPARATOR(out->writer_flags); + if (out->writer_flags & WRITER_RESOURCE_INSTANCE) { + res = snprintf(outbuf, outlen, "%s{\"n\":\"%u/%u\",\"v\":", + sep, path->res_id, path->res_inst_id); + } else { + res = snprintf(outbuf, outlen, "%s{\"n\":\"%u\",\"v\":", + sep, path->res_id); + } + + if (res <= 0 || res >= outlen) { + return 0; + } + + len += res; + outlen -= res; + res = plain_text_put_float32fix(&outbuf[len], outlen, value); + if (res <= 0 || res >= outlen) { + return 0; + } + + len += res; + outlen -= res; + res = snprintf(&outbuf[len], outlen, "}"); + if (res <= 0 || res >= outlen) { + return 0; + } + + len += res; + out->writer_flags |= WRITER_OUTPUT_VALUE; + out->outlen += len; + return len; +} + +static size_t put_float64fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float64_value_t *value) +{ + u8_t *outbuf; + size_t outlen; + char *sep; + size_t len = 0; + int res; + + outbuf = &out->outbuf[out->outlen]; + outlen = out->outsize - out->outlen; + sep = SEPARATOR(out->writer_flags); + if (out->writer_flags & WRITER_RESOURCE_INSTANCE) { + res = snprintf(outbuf, outlen, "%s{\"n\":\"%u/%u\",\"v\":", + sep, path->res_id, path->res_inst_id); + } else { + res = snprintf(outbuf, outlen, "%s{\"n\":\"%u\",\"v\":", + sep, path->res_id); + } + + if (res <= 0 || res >= outlen) { + return 0; + } + + len += res; + outlen -= res; + res = plain_text_put_float64fix(&outbuf[len], outlen, value); + if (res <= 0 || res >= outlen) { + return 0; + } + + len += res; + outlen -= res; + res = snprintf(&outbuf[len], outlen, "}"); + if (res <= 0 || res >= outlen) { + return 0; + } + + len += res; + out->writer_flags |= WRITER_OUTPUT_VALUE; + out->outlen += len; + return len; +} + +static size_t put_bool(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + bool value) +{ + u8_t *outbuf; + size_t outlen; + char *sep; + int len; + + outbuf = &out->outbuf[out->outlen]; + outlen = out->outsize - out->outlen; + sep = SEPARATOR(out->writer_flags); + if (out->writer_flags & WRITER_RESOURCE_INSTANCE) { + len = snprintf(outbuf, outlen, "%s{\"n\":\"%u/%u\",\"bv\":%s}", + sep, path->res_id, path->res_inst_id, + value ? "true" : "false"); + } else { + len = snprintf(outbuf, outlen, "%s{\"n\":\"%u\",\"bv\":%s}", + sep, path->res_id, value ? "true" : "false"); + } + + if (len < 0 || len >= outlen) { + return 0; + } + + SYS_LOG_DBG("JSON: Write bool:%s", outbuf); + out->writer_flags |= WRITER_OUTPUT_VALUE; + out->outlen += len; + return (size_t)len; +} + +const struct lwm2m_writer json_writer = { + put_begin, + put_end, + put_begin_ri, + put_end_ri, + put_s8, + put_s16, + put_s32, + put_s64, + put_string, + put_float32fix, + put_float64fix, + put_bool +}; + +static int parse_path(const u8_t *strpath, u16_t strlen, + struct lwm2m_obj_path *path) +{ + int ret = 0; + int pos = 0; + u16_t val; + u8_t c = 0; + + do { + val = 0; + c = strpath[pos]; + /* we should get a value first - consume all numbers */ + while (pos < strlen && c >= '0' && c <= '9') { + val = val * 10 + (c - '0'); + c = strpath[++pos]; + } + + /* + * Slash will mote thing forward + * and the end will be when pos == pl + */ + if (c == '/' || pos == strlen) { + SYS_LOG_DBG("Setting %u = %u", ret, val); + if (ret == 0) { + path->obj_id = val; + } else if (ret == 1) { + path->obj_inst_id = val; + } else if (ret == 2) { + path->res_id = val; + } + + ret++; + pos++; + } else { + SYS_LOG_ERR("Error: illegal char '%c' at pos:%d", + c, pos); + return -1; + } + } while (pos < strlen); + + return ret; +} + +int do_write_op_json(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + struct lwm2m_input_context *in = context->in; + struct lwm2m_obj_path *path = context->path; + struct lwm2m_engine_obj_field *obj_field; + struct lwm2m_engine_obj_inst *obj_inst = NULL; + struct lwm2m_engine_res_inst *res = NULL; + struct json_data json; + u8_t olv = 0; + u8_t *inbuf, created; + int inpos, i, r; + size_t insize; + u8_t mode = MODE_NONE; + + olv = path->level; + inbuf = in->inbuf; + inpos = in->inpos; + insize = in->insize; + + while (json_next_token(in, &json)) { + i = 0; + created = 0; + if (json.name[0] == 'n') { + path->level = parse_path(json.value, json.value_len, + path); + if (i > 0) { + r = lwm2m_get_or_create_engine_obj(context, + &obj_inst, + &created); + if (r < 0) { + return r; + } + mode |= MODE_INSTANCE; + } + } else { + /* HACK: assume value node: can it be anything else? */ + mode |= MODE_VALUE; + /* update values */ + inbuf = in->inbuf; + inpos = in->inpos; + in->inbuf = json.value; + in->inpos = 0; + in->insize = json.value_len; + } + + if (mode == MODE_READY) { + if (!obj_inst) { + return -EINVAL; + } + + obj_field = lwm2m_get_engine_obj_field(obj, + path->res_id); + if (!obj_field) { + return -EINVAL; + } + + if ((obj_field->permissions & LWM2M_PERM_W) != + LWM2M_PERM_W) { + return -EPERM; + } + + if (!obj_inst->resources || + obj_inst->resource_count == 0) { + return -EINVAL; + } + + for (i = 0; i < obj_inst->resource_count; i++) { + if (obj_inst->resources[i].res_id == + path->res_id) { + res = &obj_inst->resources[i]; + break; + } + } + + if (!res) { + return -ENOENT; + } + + lwm2m_write_handler(obj_inst, res, obj_field, context); + + mode = MODE_NONE; + in->inbuf = inbuf; + in->inpos = inpos; + in->insize = insize; + path->level = olv; + } + } + + return 0; +} diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_json.h b/subsys/net/lib/lwm2m/lwm2m_rw_json.h new file mode 100644 index 0000000000000..6a9c654df2517 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_json.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016, Eistec AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Author: + * Joakim NohlgÃ¥rd + */ + +#ifndef LWM2M_RW_JSON_H_ +#define LWM2M_RW_JSON_H_ + +#include "lwm2m_object.h" + +struct json_data { + u8_t type; /* S,B,V */ + u8_t *name; + u8_t *value; + u8_t name_len; + u8_t value_len; +}; + +extern const struct lwm2m_writer json_writer; + +int json_next_token(struct lwm2m_input_context *in, struct json_data *json); + +int do_write_op_json(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context); + +#endif /* LWM2M_RW_JSON_H_ */ diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c new file mode 100644 index 0000000000000..870ba4033a487 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c @@ -0,0 +1,707 @@ +/* + * Copyright (c) 2015, Yanzi Networks AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Authors: + * Joakim Eriksson + * Niclas Finne + */ + +/* + * Zephyr Contribution by Michael Scott + * - Zephyr code style changes / code cleanup + * - Move to Zephyr APIs where possible + * - Convert to Zephyr int/uint types + * - Remove engine dependency (replace with writer context) + * - Add write int64 function + */ + +/* + * TODO: + * - Lots of byte-order API clean up + * - Var / parameter type cleanup + * - Replace magic #'s with defines + */ + +#define SYS_LOG_DOMAIN "lib/lwm2m_oma_tlv" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include +#include +#include +#include + +#include "lwm2m_rw_oma_tlv.h" +#include "lwm2m_engine.h" + +static u8_t get_len_type(const struct oma_tlv *tlv) +{ + if (tlv->length < 8) { + return 0; + } else if (tlv->length < 0x100) { + return 1; + } else if (tlv->length < 0x10000) { + return 2; + } + + return 3; +} + +static u8_t tlv_calc_type(u8_t flags) +{ + return flags & WRITER_RESOURCE_INSTANCE ? + OMA_TLV_TYPE_RESOURCE_INSTANCE : OMA_TLV_TYPE_RESOURCE; +} + +static u16_t tlv_calc_id(u8_t flags, struct lwm2m_obj_path *path) +{ + return flags & WRITER_RESOURCE_INSTANCE ? + path->res_inst_id : path->res_id; +} + +static void tlv_setup(struct oma_tlv *tlv, u8_t type, u16_t id, + u32_t len, const u8_t *value) +{ + if (tlv) { + tlv->type = type; + tlv->id = id; + tlv->length = len; + tlv->value = value; + } +} + +static size_t oma_tlv_put(const struct oma_tlv *tlv, u8_t *buffer, size_t len) +{ + int pos; + u8_t len_type; + + /* len type is the same as number of bytes required for length */ + len_type = get_len_type(tlv); + pos = 1 + len_type; + /* ensure that we do not write too much */ + if (len < tlv->length + pos) { + SYS_LOG_ERR("OMA-TLV: Could not write the TLV - buffer overflow" + " (len:%zd < tlv->length:%d + pos:%d)", + len, tlv->length, pos); + return 0; + } + + /* first type byte in TLV header */ + buffer[0] = (tlv->type << 6) | + (tlv->id > 255 ? (1 << 5) : 0) | + (len_type << 3) | + (len_type == 0 ? tlv->length : 0); + pos = 1; + + /* The ID */ + if (tlv->id > 255) { + buffer[pos++] = (tlv->id >> 8) & 0xff; + } + + buffer[pos++] = tlv->id & 0xff; + + /* Add length if needed - unrolled loop ? */ + if (len_type > 2) { + buffer[pos++] = (tlv->length >> 16) & 0xff; + } + + if (len_type > 1) { + buffer[pos++] = (tlv->length >> 8) & 0xff; + } + + if (len_type > 0) { + buffer[pos++] = tlv->length & 0xff; + } + + /* finally add the value */ + if (tlv->value != NULL && tlv->length > 0) { + memcpy(&buffer[pos], tlv->value, tlv->length); + } + + /* TODO: Add debug print of TLV */ + return pos + tlv->length; +} + +size_t oma_tlv_get(struct oma_tlv *tlv, const u8_t *buffer, size_t len) +{ + u8_t len_type; + u8_t len_pos = 1; + size_t tlv_len; + + tlv->type = (buffer[0] >> 6) & 3; + len_type = (buffer[0] >> 3) & 3; + len_pos = 1 + (((buffer[0] & (1 << 5)) != 0) ? 2 : 1); + tlv->id = buffer[1]; + + /* if len_pos > 2 it means that there are more ID to read */ + if (len_pos > 2) { + tlv->id = (tlv->id << 8) + buffer[2]; + } + + if (len_type == 0) { + tlv_len = buffer[0] & 7; + } else { + /* read the length */ + tlv_len = 0; + while (len_type > 0) { + tlv_len = tlv_len << 8 | buffer[len_pos++]; + len_type--; + } + } + + /* and read out the data??? */ + tlv->length = tlv_len; + tlv->value = &buffer[len_pos]; + return len_pos + tlv_len; +} + +static size_t put_begin_ri(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + /* set some flags in state */ + struct oma_tlv tlv; + size_t len; + + out->writer_flags |= WRITER_RESOURCE_INSTANCE; + tlv_setup(&tlv, OMA_TLV_TYPE_MULTI_RESOURCE, path->res_id, 8, NULL); + + /* we remove the nonsense payload here (len = 8) */ + len = oma_tlv_put(&tlv, &out->outbuf[out->outlen], + out->outsize - out->outlen) - 8; + /* + * store position for deciding where to re-write the TLV when we + * know the length - NOTE: either this or memmov of buffer later... + */ + out->mark_pos_ri = out->outlen; + out->outlen += len; + return len; +} + +static size_t put_end_ri(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path) +{ + /* clear out state info */ + int pos = 2; /* this is the length pos */ + size_t len; + + out->writer_flags &= ~WRITER_RESOURCE_INSTANCE; + if (path->res_id > 0xff) { + pos++; + } + + len = out->outlen - out->mark_pos_ri; + + /* update the length byte... Assume TLV header is pos + 1 bytes. */ + out->outbuf[pos + out->mark_pos_ri] = len - (pos + 1); + return 0; +} + +static size_t put_s8(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s8_t value) +{ + size_t len; + struct oma_tlv tlv; + + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + tlv_calc_id(out->writer_flags, path), sizeof(value), + (u8_t *)&value); + + len = oma_tlv_put(&tlv, &out->outbuf[out->outlen], + out->outsize - out->outlen); + out->outlen += len; + return len; +} + +static size_t put_s16(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s16_t value) +{ + size_t len; + struct oma_tlv tlv; + s16_t net_value; + + net_value = sys_cpu_to_be16(value); + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + tlv_calc_id(out->writer_flags, path), sizeof(net_value), + (u8_t *)&net_value); + + len = oma_tlv_put(&tlv, &out->outbuf[out->outlen], + out->outsize - out->outlen); + out->outlen += len; + return len; +} + +static size_t put_s32(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s32_t value) +{ + size_t len; + struct oma_tlv tlv; + s32_t net_value; + + net_value = sys_cpu_to_be32(value); + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + tlv_calc_id(out->writer_flags, path), sizeof(net_value), + (u8_t *)&net_value); + + len = oma_tlv_put(&tlv, &out->outbuf[out->outlen], + out->outsize - out->outlen); + out->outlen += len; + return len; +} + +static size_t put_s64(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s64_t value) +{ + size_t len; + struct oma_tlv tlv; + s64_t net_value; + + net_value = sys_cpu_to_be64(value); + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + tlv_calc_id(out->writer_flags, path), sizeof(net_value), + (u8_t *)&net_value); + + len = oma_tlv_put(&tlv, &out->outbuf[out->outlen], + out->outsize - out->outlen); + out->outlen += len; + return len; +} + +static size_t put_string(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + const char *value, size_t strlen) +{ + size_t len; + struct oma_tlv tlv; + + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + path->res_id, (u32_t)strlen, + (u8_t *)value); + len = oma_tlv_put(&tlv, &out->outbuf[out->outlen], + out->outsize - out->outlen); + out->outlen += len; + return len; +} + +/* use binary32 format */ +static size_t put_float32fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float32_value_t *value) +{ + size_t len; + struct oma_tlv tlv; + u8_t *buffer = &out->outbuf[out->outlen]; + int e = 0; + s32_t val = 0; + s32_t v; + u8_t b[4]; + + /* + * TODO: Currently, there is no standard API for handling the decimal + * portion of the float32_value structure. In the future, we should use + * the value->val2 (decimal portion) to set the decimal mask and in the + * following binary float calculations. + * + * HACK BELOW: hard code the decimal mask to 0 (whole number) + */ + int bits = 0; + + v = value->val1; + if (v < 0) { + v = -v; + } + + while (v > 1) { + val = (val >> 1); + + if (v & 1) { + val = val | (1L << 22); + } + + v = (v >> 1); + e++; + } + + /* convert to the thing we should have */ + e = e - bits + 127; + if (value->val1 == 0) { + e = 0; + } + + /* is this the right byte order? */ + b[0] = (value->val1 < 0 ? 0x80 : 0) | (e >> 1); + b[1] = ((e & 1) << 7) | ((val >> 16) & 0x7f); + b[2] = (val >> 8) & 0xff; + b[3] = val & 0xff; + + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + tlv_calc_id(out->writer_flags, path), + 4, b); + len = oma_tlv_put(&tlv, buffer, out->outsize - out->outlen); + out->outlen += len; + + return len; +} + +static size_t put_float64fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float64_value_t *value) +{ + size_t len; + s64_t binary64 = 0, net_binary64 = 0; + struct oma_tlv tlv; + u8_t *buffer = &out->outbuf[out->outlen]; + + /* TODO */ + + net_binary64 = sys_cpu_to_be64(binary64); + tlv_setup(&tlv, tlv_calc_type(out->writer_flags), + tlv_calc_id(out->writer_flags, path), + sizeof(net_binary64), (u8_t *)&net_binary64); + len = oma_tlv_put(&tlv, buffer, out->outsize - out->outlen); + out->outlen += len; + return len; +} + +static size_t put_bool(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, bool value) +{ + return put_s8(out, path, value != 0 ? 1 : 0); +} + +static size_t get_s32(struct lwm2m_input_context *in, s32_t *value) +{ + struct oma_tlv tlv; + size_t size = oma_tlv_get(&tlv, in->inbuf, in->insize); + + *value = 0; + if (size > 0) { + switch (tlv.length) { + case 1: + *value = *(s8_t *)tlv.value; + break; + case 2: + *value = sys_cpu_to_be16(*(s16_t *)tlv.value); + break; + case 4: + *value = sys_cpu_to_be32(*(s32_t *)tlv.value); + break; + default: + SYS_LOG_ERR("invalid length: %u", tlv.length); + size = 0; + tlv.length = 0; + } + + in->last_value_len = tlv.length; + } + + return size; +} + +/* TODO: research to make sure this is correct */ +static size_t get_s64(struct lwm2m_input_context *in, s64_t *value) +{ + struct oma_tlv tlv; + size_t size = oma_tlv_get(&tlv, in->inbuf, in->insize); + + *value = 0; + if (size > 0) { + switch (tlv.length) { + case 1: + *value = *tlv.value; + break; + case 2: + *value = sys_cpu_to_be16(*(s16_t *)tlv.value); + break; + case 4: + *value = sys_cpu_to_be32(*(s32_t *)tlv.value); + break; + case 8: + *value = sys_cpu_to_be64(*(s64_t *)tlv.value); + break; + default: + SYS_LOG_ERR("invalid length: %u", tlv.length); + size = 0; + tlv.length = 0; + } + + in->last_value_len = tlv.length; + } + + return size; +} + +static size_t get_string(struct lwm2m_input_context *in, + u8_t *value, size_t strlen) +{ + struct oma_tlv tlv; + size_t size = oma_tlv_get(&tlv, in->inbuf, in->insize); + + if (size > 0) { + if (strlen <= tlv.length) { + /* + * The outbuffer can not contain the + * full string including ending zero + */ + return 0; + } + + memcpy(value, tlv.value, tlv.length); + value[tlv.length] = '\0'; + in->last_value_len = tlv.length; + } + + return size; +} + +/* convert float to fixpoint */ +static size_t get_float32fix(struct lwm2m_input_context *in, + float32_value_t *value) +{ + struct oma_tlv tlv; + size_t size = oma_tlv_get(&tlv, in->inbuf, in->insize); + int e; + s32_t val; + int sign = (tlv.value[0] & 0x80) != 0; + /* */ + int bits = 0; + + if (size > 0) { + /* TLV needs to be 4 bytes */ + e = ((tlv.value[0] << 1) & 0xff) | (tlv.value[1] >> 7); + val = (((long)tlv.value[1] & 0x7f) << 16) | + (tlv.value[2] << 8) | + tlv.value[3]; + e = e - 127 + bits; + + /* e is the number of times we need to roll the number */ + SYS_LOG_DBG("Actual e=%d", e); + e = e - 23; + SYS_LOG_DBG("E after sub %d", e); + val = val | 1L << 23; + if (e > 0) { + val = val << e; + } else { + val = val >> -e; + } + + value->val1 = sign ? -val : val; + + /* + * TODO: Currently, there is no standard API for handling the + * decimal portion of the float32_value structure. In the + * future, once that is settled, we should calculate + * value->val2 in the above float calculations. + * + * HACK BELOW: hard code the decimal value 0 + */ + value->val2 = 0; + in->last_value_len = tlv.length; + } + + return size; +} + +static size_t get_float64fix(struct lwm2m_input_context *in, + float64_value_t *value) +{ + struct oma_tlv tlv; + size_t size = oma_tlv_get(&tlv, in->inbuf, in->insize); + + if (size > 0) { + if (tlv.length != 8) { + SYS_LOG_ERR("invalid length: %u (not 8)", tlv.length); + return 0; + } + + /* TODO */ + + in->last_value_len = tlv.length; + } + + return size; +} + +static size_t get_bool(struct lwm2m_input_context *in, bool *value) +{ + struct oma_tlv tlv; + size_t size = oma_tlv_get(&tlv, in->inbuf, in->insize); + int i; + s32_t temp = 0; + + if (size > 0) { + /* will probably need to handle MSB as a sign bit? */ + for (i = 0; i < tlv.length; i++) { + temp = (temp << 8) | tlv.value[i]; + } + *value = (temp != 0); + in->last_value_len = tlv.length; + } + + return size; +} + +const struct lwm2m_writer oma_tlv_writer = { + NULL, + NULL, + put_begin_ri, + put_end_ri, + put_s8, + put_s16, + put_s32, + put_s64, + put_string, + put_float32fix, + put_float64fix, + put_bool +}; + +const struct lwm2m_reader oma_tlv_reader = { + get_s32, + get_s64, + get_string, + get_float32fix, + get_float64fix, + get_bool +}; + +static int do_write_op_tlv_item(struct lwm2m_engine_context *context, + u8_t *data, int len) +{ + struct lwm2m_input_context *in = context->in; + struct lwm2m_engine_obj_inst *obj_inst = NULL; + struct lwm2m_engine_res_inst *res = NULL; + struct lwm2m_engine_obj_field *obj_field = NULL; + u8_t created = 0; + int ret, i; + + in->inbuf = data; + in->inpos = 0; + in->insize = len; + + ret = lwm2m_get_or_create_engine_obj(context, &obj_inst, &created); + if (ret < 0) { + return ret; + } + + obj_field = lwm2m_get_engine_obj_field(obj_inst->obj, + context->path->res_id); + if (!obj_field) { + return -EINVAL; + } + + if ((obj_field->permissions & LWM2M_PERM_W) != LWM2M_PERM_W) { + return -EPERM; + } + + if (!obj_inst->resources || obj_inst->resource_count == 0) { + return -EINVAL; + } + + for (i = 0; i < obj_inst->resource_count; i++) { + if (obj_inst->resources[i].res_id == context->path->res_id) { + res = &obj_inst->resources[i]; + break; + } + } + + if (!res) { + return -ENOENT; + } + + return lwm2m_write_handler(obj_inst, res, obj_field, context); +} + +int do_write_op_tlv(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + struct lwm2m_input_context *in = context->in; + struct lwm2m_obj_path *path = context->path; + struct lwm2m_engine_obj_inst *obj_inst = NULL; + size_t len; + struct oma_tlv tlv; + int tlvpos = 0, ret; + + while (tlvpos < in->insize) { + len = oma_tlv_get(&tlv, &in->inbuf[tlvpos], + in->insize - tlvpos); + + SYS_LOG_DBG("Got TLV format First is: type:%d id:%d " + "len:%d (p:%d len:%d/%d)", + tlv.type, tlv.id, (int) tlv.length, + (int) tlvpos, (int) len, (int) in->insize); + + if (tlv.type == OMA_TLV_TYPE_OBJECT_INSTANCE) { + struct oma_tlv tlv2; + int len2; + int pos = 0; + + path->obj_inst_id = tlv.id; + if (tlv.length == 0) { + /* Create only - no data */ + ret = lwm2m_create_obj_inst(path->obj_id, + path->obj_inst_id, + &obj_inst); + if (ret < 0) { + return ret; + } + } + + while (pos < tlv.length && + (len2 = oma_tlv_get(&tlv2, &tlv.value[pos], + tlv.length - pos))) { + if (tlv2.type == OMA_TLV_TYPE_RESOURCE) { + path->res_id = tlv2.id; + path->level = 3; + /* ignore errors */ + do_write_op_tlv_item( + context, + (u8_t *)&tlv.value[pos], + len2); + } + + pos += len2; + } + + zoap_header_set_code(context->out->out_zpkt, + ZOAP_RESPONSE_CODE_CREATED); + } else if (tlv.type == OMA_TLV_TYPE_RESOURCE) { + path->res_id = tlv.id; + path->level = 3; + ret = do_write_op_tlv_item(context, + &in->inbuf[tlvpos], len); + if (ret < 0) { + return ret; + } + } + + tlvpos += len; + } + + return 0; +} diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h new file mode 100644 index 0000000000000..77c88fd72ef48 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015, Yanzi Networks AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Authors: + * Joakim Eriksson + * Niclas Finne + */ + +#ifndef LWM2M_RW_OMA_TLV_H_ +#define LWM2M_RW_OMA_TLV_H_ + +#include +#include + +#include "lwm2m_object.h" + +enum { + OMA_TLV_TYPE_OBJECT_INSTANCE = 0, + OMA_TLV_TYPE_RESOURCE_INSTANCE = 1, + OMA_TLV_TYPE_MULTI_RESOURCE = 2, + OMA_TLV_TYPE_RESOURCE = 3 +}; + +enum { + OMA_TLV_LEN_TYPE_NO_LEN = 0, + OMA_TLV_LEN_TYPE_8BIT_LEN = 1, + OMA_TLV_LEN_TYPE_16BIT_LEN = 2, + OMA_TLV_LEN_TYPE_24BIT_LEN = 3 +}; + +struct oma_tlv { + u8_t type; + u16_t id; /* can be 8-bit or 16-bit when serialized */ + u32_t length; + const u8_t *value; +}; + +extern const struct lwm2m_writer oma_tlv_writer; +extern const struct lwm2m_reader oma_tlv_reader; + +int do_write_op_tlv(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context); + +#endif /* LWM2M_RW_OMA_TLV_H_ */ diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c new file mode 100644 index 0000000000000..658dee4528772 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2015, Yanzi Networks AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Authors: + * Joakim Eriksson + * Niclas Finne + */ + +/* + * Zephyr Contribution by Michael Scott + * - Zephyr code style changes / code cleanup + * - Move to Zephyr APIs where possible + * - Convert to Zephyr int/uint types + * - Remove engine dependency (replace with writer context) + * - Add write int64 function + */ + +/* + * TODO: + * - Type cleanups + * - Cleanup integer parsing + */ + +#define SYS_LOG_DOMAIN "lib/lwm2m_plain_text" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_rw_plain_text.h" +#include "lwm2m_engine.h" + +size_t plain_text_put_float32fix(u8_t *outbuf, size_t outlen, + float32_value_t *value) +{ + int n, o = 0; + + if (outlen == 0) { + return 0; + } + + if (value->val1 < 0) { + *outbuf++ = '-'; + outlen--; + o = 1; + value->val1 = -value->val1; + } + + n = snprintf(outbuf, outlen, "%d.%d", value->val1, value->val2); + + if (n < 0 || n >= outlen) { + return 0; + } + + return n + o; +} + +size_t plain_text_put_float64fix(u8_t *outbuf, size_t outlen, + float64_value_t *value) +{ + int n, o = 0; + + if (outlen == 0) { + return 0; + } + + if (value->val1 < 0) { + *outbuf++ = '-'; + outlen--; + o = 1; + value->val1 = -value->val1; + } + + n = snprintf(outbuf, outlen, "%lld.%lld", value->val1, value->val2); + + if (n < 0 || n >= outlen) { + return 0; + } + + return n + o; +} + +static size_t put_s32(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s32_t value) +{ + int len; + + len = snprintf(&out->outbuf[out->outlen], out->outsize - out->outlen, + "%d", value); + if (len < 0 || len >= (out->outsize - out->outlen)) { + return 0; + } + + out->outlen += len; + return (size_t)len; +} + +static size_t put_s8(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s8_t value) +{ + return put_s32(out, path, (s32_t)value); +} + +static size_t put_s16(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s16_t value) +{ + return put_s32(out, path, (s32_t)value); +} + +static size_t put_s64(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, s64_t value) +{ + int len; + + len = snprintf(&out->outbuf[out->outlen], out->outsize - out->outlen, + "%lld", value); + if (len < 0 || len >= (out->outsize - out->outlen)) { + return 0; + } + + out->outlen += len; + return (size_t)len; +} + +static size_t put_float32fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float32_value_t *value) +{ + size_t len; + + len = plain_text_put_float32fix(&out->outbuf[out->outlen], + out->outsize - out->outlen, + value); + out->outlen += len; + return len; +} + +static size_t put_float64fix(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + float64_value_t *value) +{ + size_t len; + + len = plain_text_put_float64fix(&out->outbuf[out->outlen], + out->outsize - out->outlen, + value); + out->outlen += len; + return len; +} + +static size_t put_string(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + const char *value, size_t strlen) +{ + if (strlen >= (out->outsize - out->outlen)) { + return 0; + } + + memmove(&out->outbuf[out->outlen], value, strlen); + out->outbuf[strlen] = '\0'; + out->outlen += strlen; + return strlen; +} + +static size_t put_bool(struct lwm2m_output_context *out, + struct lwm2m_obj_path *path, + bool value) +{ + if ((out->outsize - out->outlen) > 0) { + if (value) { + out->outbuf[out->outlen] = '1'; + } else { + out->outbuf[out->outlen] = '0'; + } + + out->outlen += 1; + return 1; + } + + return 0; +} + +static size_t get_s32(struct lwm2m_input_context *in, s32_t *value) +{ + int i; + bool neg = false; + + *value = 0; + for (i = 0; i < in->insize; i++) { + if (isdigit(in->inbuf[i])) { + *value = *value * 10 + (in->inbuf[i] - '0'); + } else if (in->inbuf[i] == '-' && i == 0) { + neg = true; + } else { + break; + } + } + + if (neg) { + *value = -*value; + } + + in->last_value_len = i; + return i; +} + +static size_t get_s64(struct lwm2m_input_context *in, s64_t *value) +{ + int i; + bool neg = false; + + *value = 0; + for (i = 0; i < in->insize; i++) { + if (isdigit(in->inbuf[i])) { + *value = *value * 10 + (in->inbuf[i] - '0'); + } else if (in->inbuf[i] == '-' && i == 0) { + neg = true; + } else { + break; + } + } + + if (neg) { + *value = -*value; + } + + in->last_value_len = i; + return i; +} + +static size_t get_string(struct lwm2m_input_context *in, + u8_t *value, size_t strlen) +{ + /* The outbuffer can't contain the full string including ending zero */ + if (strlen <= in->insize) { + return 0; + } + + memcpy(value, in->inbuf, in->insize); + value[in->insize] = '\0'; + in->last_value_len = in->insize; + return in->insize; +} + +static size_t get_float32fix(struct lwm2m_input_context *in, + float32_value_t *value) +{ + int i; + bool dot = false, neg = false; + s32_t counter = 0; + + value->val1 = 0; + value->val2 = 0; + for (i = 0; i < in->insize; i++) { + if (isdigit(in->inbuf[i])) { + counter = counter * 10 + (in->inbuf[i] - '0'); + } else if (in->inbuf[i] == '-' && i == 0) { + neg = true; + } else if (in->inbuf[i] == '.' && !dot) { + value->val1 = counter; + counter = 0; + dot = true; + } else { + break; + } + } + + if (!dot) { + value->val1 = counter; + } else { + value->val2 = counter; + } + + SYS_LOG_DBG("READ FLOATFIX: '%s'(%d) => %d.%d", + in->inbuf, in->insize, value->val1, value->val2); + if (neg) { + value->val1 = -value->val1; + } + + in->last_value_len = i; + return i; +} + +static size_t get_float64fix(struct lwm2m_input_context *in, + float64_value_t *value) +{ + int i; + bool dot = false, neg = false; + s64_t counter = 0; + + value->val1 = 0; + value->val2 = 0; + for (i = 0; i < in->insize; i++) { + if (isdigit(in->inbuf[i])) { + counter = counter * 10 + (in->inbuf[i] - '0'); + } else if (in->inbuf[i] == '-' && i == 0) { + neg = true; + } else if (in->inbuf[i] == '.' && !dot) { + value->val1 = counter; + counter = 0; + dot = true; + } else { + break; + } + } + + if (!dot) { + value->val1 = counter; + } else { + value->val2 = counter; + } + + SYS_LOG_DBG("READ FLOATFIX: '%s'(%d) => %lld.%lld", + in->inbuf, in->insize, value->val1, value->val2); + if (neg) { + value->val1 = -value->val1; + } + + in->last_value_len = i; + return i; +} + +static size_t get_bool(struct lwm2m_input_context *in, + bool *value) +{ + if (in->insize > 0) { + if (*in->inbuf == '1' || *in->inbuf == '0') { + *value = (*in->inbuf == '1') ? true : false; + in->last_value_len = 1; + return 1; + } + } + + return 0; +} + +const struct lwm2m_writer plain_text_writer = { + NULL, + NULL, + NULL, + NULL, + put_s8, + put_s16, + put_s32, + put_s64, + put_string, + put_float32fix, + put_float64fix, + put_bool +}; + +const struct lwm2m_reader plain_text_reader = { + get_s32, + get_s64, + get_string, + get_float32fix, + get_float64fix, + get_bool +}; + +int do_write_op_plain_text(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context) +{ + struct lwm2m_obj_path *path = context->path; + struct lwm2m_engine_obj_inst *obj_inst = NULL; + struct lwm2m_engine_obj_field *obj_field; + struct lwm2m_engine_res_inst *res = NULL; + int ret, i; + u8_t created = 0; + + ret = lwm2m_get_or_create_engine_obj(context, &obj_inst, &created); + if (ret < 0) { + return ret; + } + + obj_field = lwm2m_get_engine_obj_field(obj, path->res_id); + if (!obj_field) { + return -EINVAL; + } + + if ((obj_field->permissions & LWM2M_PERM_W) != LWM2M_PERM_W) { + return -EPERM; + } + + if (!obj_inst->resources || obj_inst->resource_count == 0) { + return -EINVAL; + } + + for (i = 0; i < obj_inst->resource_count; i++) { + if (obj_inst->resources[i].res_id == path->res_id) { + res = &obj_inst->resources[i]; + break; + } + } + + if (!res) { + return -ENOENT; + } + + context->path->level = 3; + context->in->inpos = 0; + return lwm2m_write_handler(obj_inst, res, obj_field, context); +} diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h new file mode 100644 index 0000000000000..3c659537d3064 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015, Yanzi Networks AB. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Original Authors: + * Joakim Eriksson + * Niclas Finne + */ + +#ifndef LWM2M_RW_PLAIN_TEXT_H_ +#define LWM2M_RW_PLAIN_TEXT_H_ + +#include "lwm2m_object.h" + +extern const struct lwm2m_writer plain_text_writer; +extern const struct lwm2m_reader plain_text_reader; + +size_t plain_text_put_float32fix(u8_t *outbuf, size_t outlen, + float32_value_t *value); + +size_t plain_text_put_float64fix(u8_t *outbuf, size_t outlen, + float64_value_t *value); + +int do_write_op_plain_text(struct lwm2m_engine_obj *obj, + struct lwm2m_engine_context *context); + +#endif /* LWM2M_RW_PLAIN_TEXT_H_ */ From 408f43e09c45ee119cc4a26f24fad0f04fb76161 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 7 Jul 2017 11:04:04 -0700 Subject: [PATCH 4/9] samples: lwm2m: initial sample for LWM2M client This sample utilizes the new LwM2M library by setting up default values for LwM2M device and firmware objects and then establisting a connection to a LwM2M server (for example Leshan Demo Server) via the registration interface. To use QEMU for this purpose please see: doc/subsystems/networking/qemu_setup.rst NOTE: This sample currently does not demonstrate DTLS/bootstrap as neither of these is supported by the LwM2M library. Signed-off-by: Michael Scott --- samples/net/lwm2m_client/Makefile | 13 + samples/net/lwm2m_client/README.rst | 19 ++ samples/net/lwm2m_client/prj_frdm_k64f.conf | 43 +++ samples/net/lwm2m_client/prj_qemu_x86.conf | 43 +++ samples/net/lwm2m_client/sample.yaml | 8 + samples/net/lwm2m_client/src/Makefile | 9 + samples/net/lwm2m_client/src/lwm2m-client.c | 292 ++++++++++++++++++++ 7 files changed, 427 insertions(+) create mode 100644 samples/net/lwm2m_client/Makefile create mode 100644 samples/net/lwm2m_client/README.rst create mode 100644 samples/net/lwm2m_client/prj_frdm_k64f.conf create mode 100644 samples/net/lwm2m_client/prj_qemu_x86.conf create mode 100644 samples/net/lwm2m_client/sample.yaml create mode 100644 samples/net/lwm2m_client/src/Makefile create mode 100644 samples/net/lwm2m_client/src/lwm2m-client.c diff --git a/samples/net/lwm2m_client/Makefile b/samples/net/lwm2m_client/Makefile new file mode 100644 index 0000000000000..df07b43961752 --- /dev/null +++ b/samples/net/lwm2m_client/Makefile @@ -0,0 +1,13 @@ +# Makefile - LWM2M client test application + +# +# Copyright (c) 2017 Linaro +# +# SPDX-License-Identifier: Apache-2.0 +# + +BOARD ?= qemu_x86 +CONF_FILE ?= prj_$(BOARD).conf + +include $(ZEPHYR_BASE)/Makefile.inc +include $(ZEPHYR_BASE)/samples/net/common/Makefile.ipstack diff --git a/samples/net/lwm2m_client/README.rst b/samples/net/lwm2m_client/README.rst new file mode 100644 index 0000000000000..9ab66cbf08518 --- /dev/null +++ b/samples/net/lwm2m_client/README.rst @@ -0,0 +1,19 @@ +.. _lwm2m-client-sample: + +LWM2M client +############ + +Overview +******** + +TODO + +Building and Running +******************** + +TODO + +Sample output +============= + +TODO diff --git a/samples/net/lwm2m_client/prj_frdm_k64f.conf b/samples/net/lwm2m_client/prj_frdm_k64f.conf new file mode 100644 index 0000000000000..0216c76d4eb2f --- /dev/null +++ b/samples/net/lwm2m_client/prj_frdm_k64f.conf @@ -0,0 +1,43 @@ +CONFIG_NETWORKING=y +CONFIG_NET_LOG=y +CONFIG_NET_BUF_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_SYS_LOG_NET_BUF_LEVEL=2 +CONFIG_SYS_LOG_LWM2M_LEVEL=4 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_IPV6=y +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IPV4=y +CONFIG_NET_DHCPV4=n +CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT=2 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_INIT_STACKS=y +CONFIG_PRINTK=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_BUF_DATA_SIZE=384 +CONFIG_NET_PKT_RX_COUNT=50 +CONFIG_NET_PKT_TX_COUNT=50 +CONFIG_NET_BUF_RX_COUNT=50 +CONFIG_NET_BUF_TX_COUNT=50 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +CONFIG_NET_SHELL=y + +CONFIG_NET_APP=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_CLIENT=y +CONFIG_NET_APP_SETTINGS=y + +CONFIG_LWM2M=y +CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT=2 + +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2" + +CONFIG_NET_L2_ETHERNET=y diff --git a/samples/net/lwm2m_client/prj_qemu_x86.conf b/samples/net/lwm2m_client/prj_qemu_x86.conf new file mode 100644 index 0000000000000..181edb63996c6 --- /dev/null +++ b/samples/net/lwm2m_client/prj_qemu_x86.conf @@ -0,0 +1,43 @@ +CONFIG_NETWORKING=y +CONFIG_NET_LOG=y +CONFIG_NET_BUF_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_SYS_LOG_NET_BUF_LEVEL=2 +CONFIG_SYS_LOG_LWM2M_LEVEL=4 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_IPV6=y +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IPV4=y +CONFIG_NET_DHCPV4=n +CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT=2 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_INIT_STACKS=y +CONFIG_PRINTK=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_BUF_DATA_SIZE=384 +CONFIG_NET_PKT_RX_COUNT=50 +CONFIG_NET_PKT_TX_COUNT=50 +CONFIG_NET_BUF_RX_COUNT=50 +CONFIG_NET_BUF_TX_COUNT=50 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +CONFIG_NET_SHELL=y + +CONFIG_NET_APP=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_CLIENT=y +CONFIG_NET_APP_SETTINGS=y + +CONFIG_LWM2M=y +CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT=2 + +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2" + +CONFIG_NET_SLIP_TAP=y diff --git a/samples/net/lwm2m_client/sample.yaml b/samples/net/lwm2m_client/sample.yaml new file mode 100644 index 0000000000000..0e384ea0c0176 --- /dev/null +++ b/samples/net/lwm2m_client/sample.yaml @@ -0,0 +1,8 @@ +sample: + description: TBD + name: TBD +tests: +- test: + build_only: true + platform_whitelist: qemu_x86 + tags: net lwm2m diff --git a/samples/net/lwm2m_client/src/Makefile b/samples/net/lwm2m_client/src/Makefile new file mode 100644 index 0000000000000..26755c2c44d56 --- /dev/null +++ b/samples/net/lwm2m_client/src/Makefile @@ -0,0 +1,9 @@ +# +# Copyright (c) 2017 Linaro +# +# SPDX-License-Identifier: Apache-2.0 +# + +include $(ZEPHYR_BASE)/samples/net/common/Makefile.common + +obj-y = lwm2m-client.o diff --git a/samples/net/lwm2m_client/src/lwm2m-client.c b/samples/net/lwm2m_client/src/lwm2m-client.c new file mode 100644 index 0000000000000..30240339f953a --- /dev/null +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define SYS_LOG_DOMAIN "lwm2m-client" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 + +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_NET_L2_BLUETOOTH) +#include +#include +#endif + +#define APP_BANNER "Run LWM2M client" + +#if !defined(CONFIG_NET_APP_PEER_IPV4_ADDR) +#define CONFIG_NET_APP_PEER_IPV4_ADDR "" +#endif + +#if !defined(CONFIG_NET_APP_PEER_IPV6_ADDR) +#define CONFIG_NET_APP_PEER_IPV6_ADDR "" +#endif + +#define WAIT_TIME K_SECONDS(10) +#define CONNECT_TIME K_SECONDS(10) + +#define CLIENT_MANUFACTURER "Zephyr" +#define CLIENT_MODEL_NUMBER "OMA-LWM2M Sample Client" +#define CLIENT_SERIAL_NUMBER "345000123" +#define CLIENT_FIRMWARE_VER "1.0" +#define CLIENT_DEVICE_TYPE "OMA-LWM2M Client" +#define CLIENT_HW_VER "1.0.1" + +#define ENDPOINT_LEN 32 + +static int pwrsrc_bat; +static int pwrsrc_usb; +static int battery_voltage = 3800; +static int battery_current = 125; +static int usb_voltage = 5000; +static int usb_current = 900; + +#if defined(CONFIG_NET_IPV6) +static struct net_app_ctx udp6; +#endif +#if defined(CONFIG_NET_IPV4) +static struct net_app_ctx udp4; +#endif +static struct k_sem quit_lock; + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) +NET_PKT_TX_SLAB_DEFINE(lwm2m_tx_udp, 5); +NET_PKT_DATA_POOL_DEFINE(lwm2m_data_udp, 20); + +static struct k_mem_slab *tx_udp_slab(void) +{ + return &lwm2m_tx_udp; +} + +static struct net_buf_pool *data_udp_pool(void) +{ + return &lwm2m_data_udp; +} +#else +#define tx_udp_slab NULL +#define data_udp_pool NULL +#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ + +static int device_reboot_cb(u16_t obj_inst_id) +{ + SYS_LOG_INF("DEVICE: REBOOT"); + /* Add an error for testing */ + lwm2m_device_add_err(LWM2M_DEVICE_ERROR_LOW_POWER); + /* Change the battery voltage for testing */ + lwm2m_device_set_pwrsrc_voltage_mv(pwrsrc_bat, --battery_voltage); + + return 1; +} + +static int device_factory_default_cb(u16_t obj_inst_id) +{ + SYS_LOG_INF("DEVICE: FACTORY DEFAULT"); + /* Add an error for testing */ + lwm2m_device_add_err(LWM2M_DEVICE_ERROR_GPS_FAILURE); + /* Change the USB current for testing */ + lwm2m_device_set_pwrsrc_current_ma(pwrsrc_usb, --usb_current); + + return 1; +} + +static int firmware_update_cb(u16_t obj_inst_id) +{ + SYS_LOG_DBG("UPDATE"); + return 1; +} + +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) +{ + SYS_LOG_INF("FIRMWARE: BLOCK RECEIVED: len:%u last_block:%d", + data_len, last_block); + return 1; +} + +static int set_endpoint_name(char *ep_name, sa_family_t family) +{ + int ret; + + ret = snprintk(ep_name, ENDPOINT_LEN, "%s-%s-%u", + CONFIG_BOARD, (family == AF_INET6 ? "ipv6" : "ipv4"), + sys_rand32_get()); + if (ret < 0 || ret >= ENDPOINT_LEN) { + SYS_LOG_ERR("Can't fill name buffer"); + return -EINVAL; + } + + return 0; +} + +static int lwm2m_setup(void) +{ + /* setup SECURITY object */ + /* setup SERVER object */ + + /* setup DEVICE object */ + + lwm2m_engine_set_string("3/0/0", CLIENT_MANUFACTURER); + lwm2m_engine_set_string("3/0/1", CLIENT_MODEL_NUMBER); + lwm2m_engine_set_string("3/0/2", CLIENT_SERIAL_NUMBER); + lwm2m_engine_set_string("3/0/3", CLIENT_FIRMWARE_VER); + lwm2m_engine_register_exec_callback("3/0/4", device_reboot_cb); + lwm2m_engine_register_exec_callback("3/0/5", device_factory_default_cb); + lwm2m_engine_set_u8("3/0/9", 95); /* battery level */ + lwm2m_engine_set_u32("3/0/10", 15); /* mem free */ + lwm2m_engine_set_string("3/0/17", CLIENT_DEVICE_TYPE); + lwm2m_engine_set_string("3/0/18", CLIENT_HW_VER); + lwm2m_engine_set_u8("3/0/20", LWM2M_DEVICE_BATTERY_STATUS_CHARGING); + lwm2m_engine_set_u32("3/0/21", 25); /* mem total */ + + pwrsrc_bat = lwm2m_device_add_pwrsrc(LWM2M_DEVICE_PWR_SRC_TYPE_BAT_INT); + if (pwrsrc_bat < 0) { + SYS_LOG_ERR("LWM2M battery power source enable error (err:%d)", + pwrsrc_bat); + return pwrsrc_bat; + } + lwm2m_device_set_pwrsrc_voltage_mv(pwrsrc_bat, battery_voltage); + lwm2m_device_set_pwrsrc_current_ma(pwrsrc_bat, battery_current); + + pwrsrc_usb = lwm2m_device_add_pwrsrc(LWM2M_DEVICE_PWR_SRC_TYPE_USB); + if (pwrsrc_usb < 0) { + SYS_LOG_ERR("LWM2M usb power source enable error (err:%d)", + pwrsrc_usb); + return pwrsrc_usb; + } + lwm2m_device_set_pwrsrc_voltage_mv(pwrsrc_usb, usb_voltage); + lwm2m_device_set_pwrsrc_current_ma(pwrsrc_usb, usb_current); + + /* setup FIRMWARE object */ + + lwm2m_engine_register_post_write_callback("5/0/0", + firmware_block_received_cb); + lwm2m_firmware_set_write_cb(firmware_block_received_cb); + lwm2m_engine_register_exec_callback("5/0/2", firmware_update_cb); + + return 0; +} + +int setup_net_app_ctx(struct net_app_ctx *ctx, const char *peer) +{ + int ret; + + ret = net_app_init_udp_client(ctx, NULL, NULL, peer, + CONFIG_LWM2M_PEER_PORT, WAIT_TIME, NULL); + if (ret < 0) { + SYS_LOG_ERR("Cannot init UDP client (%d)", ret); + return ret; + } + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) + net_app_set_net_pkt_pool(ctx, tx_udp_slab, data_udp_pool); +#endif + + return ret; +} + +void main(void) +{ + int ret; + char ep_name[ENDPOINT_LEN]; + + SYS_LOG_INF(APP_BANNER); + + k_sem_init(&quit_lock, 0, UINT_MAX); + +#if defined(CONFIG_NET_L2_BLUETOOTH) + if (bt_enable(NULL)) { + SYS_LOG_ERR("Bluetooth init failed"); + return; + } + ipss_init(); + ipss_advertise(); +#endif + + ret = lwm2m_setup(); + if (ret < 0) { + SYS_LOG_ERR("Cannot setup LWM2M fields (%d)", ret); + return; + } + +#if defined(CONFIG_NET_IPV6) + ret = setup_net_app_ctx(&udp6, CONFIG_NET_APP_PEER_IPV6_ADDR); + if (ret < 0) { + goto cleanup_ipv6; + } + + ret = set_endpoint_name(ep_name, udp6.ipv6.local.family); + if (ret < 0) { + SYS_LOG_ERR("Cannot set IPv6 endpoint name (%d)", ret); + goto cleanup_ipv6; + } + + + ret = lwm2m_engine_start(udp6.ipv6.ctx); + if (ret < 0) { + SYS_LOG_ERR("Cannot init LWM2M IPv6 engine (%d)", ret); + goto cleanup_ipv6; + } + + ret = lwm2m_rd_client_start(udp6.ipv6.ctx, &udp6.ipv6.remote, + ep_name); + if (ret < 0) { + SYS_LOG_ERR("LWM2M init LWM2M IPv6 RD client error (%d)", + ret); + goto cleanup_ipv6; + } + + SYS_LOG_INF("IPv6 setup complete."); +#endif + +#if defined(CONFIG_NET_IPV4) + ret = setup_net_app_ctx(&udp4, CONFIG_NET_APP_PEER_IPV4_ADDR); + if (ret < 0) { + goto cleanup_ipv4; + } + + ret = set_endpoint_name(ep_name, udp4.ipv4.local.family); + if (ret < 0) { + SYS_LOG_ERR("Cannot set IPv4 endpoint name (%d)", ret); + goto cleanup_ipv4; + } + + ret = lwm2m_engine_start(udp4.ipv4.ctx); + if (ret < 0) { + SYS_LOG_ERR("Cannot init LWM2M IPv4 engine (%d)", ret); + goto cleanup_ipv4; + } + + ret = lwm2m_rd_client_start(udp4.ipv4.ctx, &udp4.ipv4.remote, + ep_name); + if (ret < 0) { + SYS_LOG_ERR("LWM2M init LWM2M IPv4 RD client error (%d)", + ret); + goto cleanup_ipv4; + } + + SYS_LOG_INF("IPv4 setup complete."); +#endif + + k_sem_take(&quit_lock, K_FOREVER); + +#if defined(CONFIG_NET_IPV4) +cleanup_ipv4: + net_app_close(&udp4); + net_app_release(&udp4); +#endif + +#if defined(CONFIG_NET_IPV6) +cleanup_ipv6: + net_app_close(&udp6); + net_app_release(&udp6); +#endif +} From 4d28996c8782cdb3d98f15b1a417ef5d830c762a Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 7 Jul 2017 11:04:05 -0700 Subject: [PATCH 5/9] net: lwm2m: add IPSO support w/ temperature sensor object IPSO Smart Objects are a set of template objects based on the LwM2M object framework which are designed to represent standard hardware such as temperature and humidity sensors or light controls. Let's add a place for these objects to live as well as an initial temperature sensor object. Signed-off-by: Michael Scott --- include/net/lwm2m.h | 4 + subsys/net/lib/lwm2m/Kconfig | 6 + subsys/net/lib/lwm2m/Kconfig.ipso | 34 ++++ subsys/net/lib/lwm2m/Makefile | 3 + subsys/net/lib/lwm2m/ipso_temp_sensor.c | 219 ++++++++++++++++++++++++ 5 files changed, 266 insertions(+) create mode 100644 subsys/net/lib/lwm2m/Kconfig.ipso create mode 100644 subsys/net/lib/lwm2m/ipso_temp_sensor.c diff --git a/include/net/lwm2m.h b/include/net/lwm2m.h index 73f9490fa774b..8dace2724987b 100644 --- a/include/net/lwm2m.h +++ b/include/net/lwm2m.h @@ -20,6 +20,10 @@ #define LWM2M_OBJECT_LOCATION_ID 6 #define LWM2M_OBJECT_CONNECTIVITY_STATISTICS_ID 7 +/* IPSO Alliance Objects */ + +#define IPSO_OBJECT_TEMP_SENSOR_ID 3303 + /* callback can return 1 if handled (don't update value) */ typedef void *(*lwm2m_engine_get_data_cb_t)(u16_t obj_inst_id, size_t *data_len); diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index 8d12c5d5b0e7d..9ce8589568424 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -141,4 +141,10 @@ config LWM2M_DEVICE_ERROR_CODE_MAX This value sets the maximum number of error codes that the device object will store before ignoring new values. +menu "IPSO Alliance Smart Object Support" + +source "subsys/net/lib/lwm2m/Kconfig.ipso" + +endmenu + endif # LWM2M diff --git a/subsys/net/lib/lwm2m/Kconfig.ipso b/subsys/net/lib/lwm2m/Kconfig.ipso new file mode 100644 index 0000000000000..578bbcf849ac5 --- /dev/null +++ b/subsys/net/lib/lwm2m/Kconfig.ipso @@ -0,0 +1,34 @@ +# +# Copyright (c) 2017 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig LWM2M_IPSO_SUPPORT + bool "IPSO Alliance Smart Object Support" + default n + depends on LWM2M + help + This option adds general support for IPSO objects + +if LWM2M_IPSO_SUPPORT + +config LWM2M_IPSO_TEMP_SENSOR + bool "IPSO Temperature Sensor Support" + default n + help + This IPSO object should be used with a temperature sensor to + report a temperature measurement. It also provides resources for + minimum/maximum measured values and the minimum/maximum range + that can be measured by the temperature sensor. + +config LWM2M_IPSO_TEMP_SENSOR_INSTANCE_COUNT + int "Maximum # of IPSO Temperature Sensor object instances" + default 1 + depends on LWM2M_IPSO_TEMP_SENSOR + range 1 20 + help + This setting establishes the total count of IPSO Temperature + Sensor instances available to the LWM2M client. + +endif # LWM2M_LWM2M_IPSO_SUPPORT diff --git a/subsys/net/lib/lwm2m/Makefile b/subsys/net/lib/lwm2m/Makefile index 63ad267a9ca8a..12f4ef28cd247 100644 --- a/subsys/net/lib/lwm2m/Makefile +++ b/subsys/net/lib/lwm2m/Makefile @@ -25,3 +25,6 @@ obj-$(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT) += lwm2m_obj_firmware_pull.o # JSON Support obj-$(CONFIG_LWM2M_RW_JSON_SUPPORT) += lwm2m_rw_json.o + +# IPSO Objects +obj-$(CONFIG_LWM2M_IPSO_TEMP_SENSOR) += ipso_temp_sensor.o diff --git a/subsys/net/lib/lwm2m/ipso_temp_sensor.c b/subsys/net/lib/lwm2m/ipso_temp_sensor.c new file mode 100644 index 0000000000000..d5d4b174306dc --- /dev/null +++ b/subsys/net/lib/lwm2m/ipso_temp_sensor.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Source material for IPSO Temperature Sensor object (3303): + * https://github.com/IPSO-Alliance/pub/blob/master/docs/IPSO-Smart-Objects.pdf + * Section: "10. IPSO Object: Temperature" + */ + +#define SYS_LOG_DOMAIN "ipso_temp_sensor" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL +#include +#include +#include +#include + +#include "lwm2m_object.h" +#include "lwm2m_engine.h" + +/* Server resource IDs */ +#define TEMP_SENSOR_VALUE_ID 5700 +#define TEMP_UNITS_ID 5701 +#define TEMP_MIN_MEASURED_VALUE_ID 5601 +#define TEMP_MAX_MEASURED_VALUE_ID 5602 +#define TEMP_MIN_RANGE_VALUE_ID 5603 +#define TEMP_MAX_RANGE_VALUE_ID 5604 +#define TEMP_RESET_MIN_MAX_MEASURED_VALUES_ID 5605 + +#define TEMP_MAX_ID 7 + +#define MAX_INSTANCE_COUNT CONFIG_LWM2M_IPSO_TEMP_SENSOR_INSTANCE_COUNT + +#define TEMP_STRING_SHORT 8 + +/* resource state variables */ +static float32_value_t sensor_value[MAX_INSTANCE_COUNT]; +static char units[MAX_INSTANCE_COUNT][TEMP_STRING_SHORT]; +static float32_value_t min_measured_value[MAX_INSTANCE_COUNT]; +static float32_value_t max_measured_value[MAX_INSTANCE_COUNT]; +static float32_value_t min_range_value[MAX_INSTANCE_COUNT]; +static float32_value_t max_range_value[MAX_INSTANCE_COUNT]; + +static struct lwm2m_engine_obj temp_sensor; +static struct lwm2m_engine_obj_field fields[] = { + OBJ_FIELD_DATA(TEMP_SENSOR_VALUE_ID, R, FLOAT32), + OBJ_FIELD_DATA(TEMP_UNITS_ID, R, STRING), + OBJ_FIELD_DATA(TEMP_MIN_MEASURED_VALUE_ID, R, FLOAT32), + OBJ_FIELD_DATA(TEMP_MAX_MEASURED_VALUE_ID, R, FLOAT32), + OBJ_FIELD_DATA(TEMP_MIN_RANGE_VALUE_ID, R, FLOAT32), + OBJ_FIELD_DATA(TEMP_MAX_RANGE_VALUE_ID, R, FLOAT32), + OBJ_FIELD_EXECUTE(TEMP_RESET_MIN_MAX_MEASURED_VALUES_ID), +}; + +static struct lwm2m_engine_obj_inst inst[MAX_INSTANCE_COUNT]; +static struct lwm2m_engine_res_inst res[MAX_INSTANCE_COUNT][TEMP_MAX_ID]; + +static int reset_min_max_measured_values_cb(u16_t obj_inst_id) +{ + int i; + + SYS_LOG_DBG("RESET MIN/MAX %d", obj_inst_id); + for (i = 0; i < MAX_INSTANCE_COUNT; i++) { + if (inst[i].obj && inst[i].obj_inst_id == obj_inst_id) { + min_measured_value[i].val1 = 0; + min_measured_value[i].val2 = 0; + max_measured_value[i].val1 = 0; + max_measured_value[i].val2 = 0; + return 0; + } + } + + return -ENOENT; +} + +static int sensor_value_write_cb(u16_t obj_inst_id, + u8_t *data, u16_t data_len, + bool last_block, size_t total_size) +{ + int i; + bool update_min = false; + bool update_max = false; + + for (i = 0; i < MAX_INSTANCE_COUNT; i++) { + if (inst[i].obj && inst[i].obj_inst_id == obj_inst_id) { + /* update min / max */ + if (sensor_value[i].val1 < min_measured_value[i].val1) { + update_min = true; + } else if (sensor_value[i].val1 == + min_measured_value[i].val1 && + sensor_value[i].val2 < + min_measured_value[i].val2) { + update_min = true; + } + + if (sensor_value[i].val1 > min_measured_value[i].val1) { + update_max = true; + } else if (sensor_value[i].val1 == + min_measured_value[i].val1 && + sensor_value[i].val2 > + min_measured_value[i].val2) { + update_max = true; + } + + if (update_min) { + min_measured_value[i].val1 = + sensor_value[i].val1; + min_measured_value[i].val2 = + sensor_value[i].val2; + NOTIFY_OBSERVER(IPSO_OBJECT_TEMP_SENSOR_ID, + obj_inst_id, + TEMP_MIN_MEASURED_VALUE_ID); + } + + if (update_max) { + max_measured_value[i].val1 = + sensor_value[i].val1; + max_measured_value[i].val2 = + sensor_value[i].val2; + NOTIFY_OBSERVER(IPSO_OBJECT_TEMP_SENSOR_ID, + obj_inst_id, + TEMP_MAX_MEASURED_VALUE_ID); + } + } + } + + return 1; +} + +static struct lwm2m_engine_obj_inst *temp_sensor_create(u16_t obj_inst_id) +{ + int index, i = 0; + + /* Check that there is no other instance with this ID */ + for (index = 0; index < MAX_INSTANCE_COUNT; index++) { + if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) { + SYS_LOG_ERR("Can not create instance - " + "already existing: %u", obj_inst_id); + return NULL; + } + } + + for (index = 0; index < MAX_INSTANCE_COUNT; index++) { + if (!inst[index].obj) { + break; + } + } + + if (index >= MAX_INSTANCE_COUNT) { + SYS_LOG_ERR("Can not create instance - " + "no more room: %u", obj_inst_id); + return NULL; + } + + /* Set default values */ + sensor_value[index].val1 = 0; + sensor_value[index].val2 = 0; + units[index][0] = '\0'; + min_measured_value[index].val1 = 0; + min_measured_value[index].val2 = 0; + max_measured_value[index].val1 = 0; + max_measured_value[index].val2 = 0; + min_range_value[index].val1 = 0; + min_range_value[index].val2 = 0; + max_range_value[index].val1 = 0; + max_range_value[index].val2 = 0; + + /* initialize instance resource data */ + INIT_OBJ_RES(res[index], i, TEMP_SENSOR_VALUE_ID, 0, + &sensor_value[index], sizeof(*sensor_value), + NULL, NULL, sensor_value_write_cb, NULL); + INIT_OBJ_RES_DATA(res[index], i, TEMP_UNITS_ID, + units[index], TEMP_STRING_SHORT); + INIT_OBJ_RES_DATA(res[index], i, TEMP_MIN_MEASURED_VALUE_ID, + &min_measured_value[index], + sizeof(*min_measured_value)); + INIT_OBJ_RES_DATA(res[index], i, TEMP_MAX_MEASURED_VALUE_ID, + &max_measured_value[index], + sizeof(*max_measured_value)); + INIT_OBJ_RES_DATA(res[index], i, TEMP_MIN_RANGE_VALUE_ID, + &min_range_value[index], + sizeof(*min_range_value)); + INIT_OBJ_RES_DATA(res[index], i, TEMP_MAX_RANGE_VALUE_ID, + &max_range_value[index], + sizeof(*max_range_value)); + INIT_OBJ_RES_EXECUTE(res[index], i, + TEMP_RESET_MIN_MAX_MEASURED_VALUES_ID, + reset_min_max_measured_values_cb); + + inst[index].resources = res[index]; + inst[index].resource_count = i; + SYS_LOG_DBG("Create IPSO Temperature Sensor instance: %d", + obj_inst_id); + return &inst[index]; +} + +static int ipso_temp_sensor_init(struct device *dev) +{ + int ret = 0; + + /* Set default values */ + memset(inst, 0, sizeof(*inst) * MAX_INSTANCE_COUNT); + memset(res, 0, sizeof(struct lwm2m_engine_res_inst) * + MAX_INSTANCE_COUNT * TEMP_MAX_ID); + + temp_sensor.obj_id = IPSO_OBJECT_TEMP_SENSOR_ID; + temp_sensor.fields = fields; + temp_sensor.field_count = sizeof(fields) / sizeof(*fields); + temp_sensor.max_instance_count = MAX_INSTANCE_COUNT; + temp_sensor.create_cb = temp_sensor_create; + lwm2m_register_obj(&temp_sensor); + + return ret; +} + +SYS_INIT(ipso_temp_sensor_init, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); From 65035d1459073379e32105a48cc76f498900ec6f Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Fri, 7 Jul 2017 11:04:06 -0700 Subject: [PATCH 6/9] samples: lwm2m: add IPSO temperature object to LWM2M client This commit adds IPSO temperature support to the LwM2M client sample. NOTE: A dummy value of 25C is set during initialization and does not change. Signed-off-by: Michael Scott --- samples/net/lwm2m_client/prj_frdm_k64f.conf | 2 ++ samples/net/lwm2m_client/prj_qemu_x86.conf | 2 ++ samples/net/lwm2m_client/src/lwm2m-client.c | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/samples/net/lwm2m_client/prj_frdm_k64f.conf b/samples/net/lwm2m_client/prj_frdm_k64f.conf index 0216c76d4eb2f..5f63ae04604f4 100644 --- a/samples/net/lwm2m_client/prj_frdm_k64f.conf +++ b/samples/net/lwm2m_client/prj_frdm_k64f.conf @@ -34,6 +34,8 @@ CONFIG_NET_APP_SETTINGS=y CONFIG_LWM2M=y CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT=2 +CONFIG_LWM2M_IPSO_SUPPORT=y +CONFIG_LWM2M_IPSO_TEMP_SENSOR=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 181edb63996c6..baa90c3a5401b 100644 --- a/samples/net/lwm2m_client/prj_qemu_x86.conf +++ b/samples/net/lwm2m_client/prj_qemu_x86.conf @@ -34,6 +34,8 @@ CONFIG_NET_APP_SETTINGS=y CONFIG_LWM2M=y CONFIG_LWM2M_RD_CLIENT_INSTANCE_COUNT=2 +CONFIG_LWM2M_IPSO_SUPPORT=y +CONFIG_LWM2M_IPSO_TEMP_SENSOR=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 30240339f953a..1fc2f8e89e118 100644 --- a/samples/net/lwm2m_client/src/lwm2m-client.c +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -129,6 +129,8 @@ static int set_endpoint_name(char *ep_name, sa_family_t family) static int lwm2m_setup(void) { + struct float32_value float_value; + /* setup SECURITY object */ /* setup SERVER object */ @@ -172,6 +174,14 @@ static int lwm2m_setup(void) lwm2m_firmware_set_write_cb(firmware_block_received_cb); lwm2m_engine_register_exec_callback("5/0/2", firmware_update_cb); + /* setup TEMP SENSOR object */ + + lwm2m_engine_create_obj_inst("3303/0"); + /* dummy temp data in C*/ + float_value.val1 = 25; + float_value.val2 = 0; + lwm2m_engine_set_float32("3303/0/5700", &float_value); + return 0; } From 7970296e02ed9696bf4738f28e88c217e224c6e6 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Tue, 25 Jul 2017 11:04:16 -0700 Subject: [PATCH 7/9] net: lwm2m: add SPX Apache-2.0 license tag w/ Linaro copyright Signed-off-by: Michael Scott --- subsys/net/lib/lwm2m/lwm2m_object.h | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rd_client.c | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rd_client.h | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rw_json.c | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rw_json.h | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c | 6 ++++++ subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h | 6 ++++++ 9 files changed, 54 insertions(+) diff --git a/subsys/net/lib/lwm2m/lwm2m_object.h b/subsys/net/lib/lwm2m/lwm2m_object.h index 68ffbba0e15f0..baee4959f74fc 100644 --- a/subsys/net/lib/lwm2m/lwm2m_object.h +++ b/subsys/net/lib/lwm2m/lwm2m_object.h @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2015, Yanzi Networks AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.c b/subsys/net/lib/lwm2m/lwm2m_rd_client.c index 700747a035aa5..35762ce054275 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rd_client.c +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.c @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2015, Yanzi Networks AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rd_client.h b/subsys/net/lib/lwm2m/lwm2m_rd_client.h index 6d839720e8d92..39453ab2aa7c7 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rd_client.h +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.h @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2016, SICS Swedish ICT AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_json.c b/subsys/net/lib/lwm2m/lwm2m_rw_json.c index c38eda40bf42f..82a68814fb965 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rw_json.c +++ b/subsys/net/lib/lwm2m/lwm2m_rw_json.c @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2016, Eistec AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_json.h b/subsys/net/lib/lwm2m/lwm2m_rw_json.h index 6a9c654df2517..65fdce1a526d3 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rw_json.h +++ b/subsys/net/lib/lwm2m/lwm2m_rw_json.h @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2016, Eistec AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c index 870ba4033a487..0ad7cb26cce3e 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c +++ b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2015, Yanzi Networks AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h index 77c88fd72ef48..27f993cf9c096 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h +++ b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2015, Yanzi Networks AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c index 658dee4528772..9a66bfb63fcbd 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c +++ b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2015, Yanzi Networks AB. * All rights reserved. diff --git a/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h index 3c659537d3064..c50fe871ee37b 100644 --- a/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h +++ b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + /* * Copyright (c) 2015, Yanzi Networks AB. * All rights reserved. From e7ed1a02bf78eea18f93808c5de38f45fb02bfbb Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Tue, 25 Jul 2017 11:13:49 -0700 Subject: [PATCH 8/9] MAINTAINERS: add maintainer for LwM2M library / samples Signed-off-by: Michael Scott --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 03cfd6b807723..08f9d43d48b14 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -280,6 +280,11 @@ M: Anas Nashif M: Inaky Perez-Gonzalez F: .known-issues/ +LWM2M +M: Michael Scott +S: Supported +F: subsys/net/lib/lwm2m/ +F: samples/net/lwm2m_client/ MAINTAINERS M: Anas Nashif From 4f27e8991fdb785bb5d22f5a2c1ad5fffeebdc78 Mon Sep 17 00:00:00 2001 From: Michael Scott Date: Tue, 25 Jul 2017 11:19:30 -0700 Subject: [PATCH 9/9] CODEOWNERS: add entries for LwM2M library / samples Signed-off-by: Michael Scott --- CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 2c7dd3883b52b..5fb7925a42af2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -142,6 +142,7 @@ samples/boards/quark_se_c1000/power*/* @youvedeep @nashif samples/net/* @jukkar @tbursztyka samples/net/dns_resolve/* @jukkar @tbursztyka samples/net/http_server/* @jukkar @tbursztyka +samples/net/lwm2m_client/* @mike-scott samples/net/mbedtls_sslclient/* @nashif samples/net/mqtt_publisher/* @jukkar @tbursztyka samples/net/zoap_client/* @vcgomes @@ -159,6 +160,7 @@ subsys/net/ip/* @jukkar @tbursztyka subsys/net/lib/* @jukkar @tbursztyka subsys/net/lib/dns/* @jukkar @tbursztyka subsys/net/lib/http/* @jukkar @tbursztyka +subsys/net/lib/lwm2m/* @mike-scott subsys/net/lib/mqtt/* @jukkar @tbursztyka subsys/net/lib/zoap/* @vcgomes subsys/usb @youvedeep @andyross