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 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 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/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 diff --git a/include/net/lwm2m.h b/include/net/lwm2m.h new file mode 100644 index 0000000000000..8dace2724987b --- /dev/null +++ b/include/net/lwm2m.h @@ -0,0 +1,159 @@ +/* + * 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 + +/* 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); +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/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/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..5f63ae04604f4 --- /dev/null +++ b/samples/net/lwm2m_client/prj_frdm_k64f.conf @@ -0,0 +1,45 @@ +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_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" +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..baa90c3a5401b --- /dev/null +++ b/samples/net/lwm2m_client/prj_qemu_x86.conf @@ -0,0 +1,45 @@ +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_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" +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..1fc2f8e89e118 --- /dev/null +++ b/samples/net/lwm2m_client/src/lwm2m-client.c @@ -0,0 +1,302 @@ +/* + * 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) +{ + struct float32_value float_value; + + /* 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); + + /* 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; +} + +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 +} 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..9ce8589568424 --- /dev/null +++ b/subsys/net/lib/lwm2m/Kconfig @@ -0,0 +1,150 @@ +# +# 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. + +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 new file mode 100644 index 0000000000000..12f4ef28cd247 --- /dev/null +++ b/subsys/net/lib/lwm2m/Makefile @@ -0,0 +1,30 @@ +# 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 + +# 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); 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..baee4959f74fc --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_object.h @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..35762ce054275 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.c @@ -0,0 +1,883 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..39453ab2aa7c7 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rd_client.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..82a68814fb965 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_json.c @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..65fdce1a526d3 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_json.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..0ad7cb26cce3e --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.c @@ -0,0 +1,713 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..27f993cf9c096 --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_oma_tlv.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..9a66bfb63fcbd --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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..c50fe871ee37b --- /dev/null +++ b/subsys/net/lib/lwm2m/lwm2m_rw_plain_text.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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_ */ 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; }