From 5f8c187880f4c38daa970d92d2907e54fca4d7ef Mon Sep 17 00:00:00 2001 From: Meng Date: Thu, 10 Jul 2025 16:46:46 +0200 Subject: [PATCH] dm-framework base --- bbfdm/files/etc/init.d/bbfdm.services | 21 +- bridgemngr/Config.in | 17 - bridgemngr/Makefile | 76 - .../files/VLAN_Filtering_Extension.json | 31 - dm-framework/datamodels/._Makefile | Bin 0 -> 4096 bytes dm-framework/datamodels/Makefile | 73 + dm-framework/datamodels/src/Makefile | 41 + .../src/dm-files/Bridge/._VLANBridge.json | Bin 0 -> 4096 bytes .../src/dm-files/Bridge/VLANBridge.json | 336 +++ .../src/dm-files/Bridge/bridge-apply.js | 177 ++ .../src/dm-files/Bridge/bridge-import.js | 142 ++ .../datamodels/src/dm-files/Bridge/bridge.js | 202 ++ .../datamodels/src/dm-files/makeDM.js | 508 ++++ dm-framework/datamodels/src/dm-files/uci.js | 139 ++ dm-framework/datamodels/src/dm-files/utils.js | 242 ++ dm-framework/datamodels/src/dm_node.c | 1480 ++++++++++++ dm-framework/datamodels/src/dm_node.h | 289 +++ dm-framework/datamodels/src/dm_types.h | 97 + dm-framework/datamodels/src/json2code.js | 1165 ++++++++++ dm-framework/dm-agent/._Makefile | Bin 0 -> 4096 bytes dm-framework/dm-agent/._bbfdm_service.json | Bin 0 -> 4096 bytes dm-framework/dm-agent/Makefile | 65 + .../dm-agent}/bbfdm_service.json | 1 + .../dm-agent}/files/etc/config/bridging | 0 .../dm-agent/files/etc/config/dm_agent | 2 + .../dm-agent}/files/etc/init.d/bridging | 0 .../dm-agent/files/etc/init.d/dm-agent | 37 + .../files/lib/upgrade/keep.d/dm-agent | 1 + dm-framework/dm-agent/src/._dm_agent.c | Bin 0 -> 4096 bytes dm-framework/dm-agent/src/Makefile | 37 + dm-framework/dm-agent/src/dm_agent.c | 1752 ++++++++++++++ dm-framework/dm-agent/src/main.c | 29 + dm-framework/dm-api/Makefile | 74 + dm-framework/dm-api/src/Makefile | 63 + dm-framework/dm-api/src/core/._dm_api.c | Bin 0 -> 4096 bytes dm-framework/dm-api/src/core/._dm_api.h | Bin 0 -> 4096 bytes dm-framework/dm-api/src/core/._dm_import.c | Bin 0 -> 4096 bytes dm-framework/dm-api/src/core/._dm_linker.c | Bin 0 -> 4096 bytes dm-framework/dm-api/src/core/._dm_linker.h | Bin 0 -> 4096 bytes dm-framework/dm-api/src/core/db_upgrade.c | 2053 +++++++++++++++++ dm-framework/dm-api/src/core/db_upgrade.h | 17 + dm-framework/dm-api/src/core/dbmgr.c | 681 ++++++ dm-framework/dm-api/src/core/dbmgr.h | 39 + dm-framework/dm-api/src/core/dm_api.c | 1494 ++++++++++++ dm-framework/dm-api/src/core/dm_api.h | 252 ++ dm-framework/dm-api/src/core/dm_apply.c | 724 ++++++ dm-framework/dm-api/src/core/dm_apply.h | 37 + dm-framework/dm-api/src/core/dm_import.c | 373 +++ dm-framework/dm-api/src/core/dm_linker.c | 427 ++++ dm-framework/dm-api/src/core/dm_linker.h | 45 + dm-framework/dm-api/src/core/inode_buf.c | 471 ++++ dm-framework/dm-api/src/core/inode_buf.h | 99 + dm-framework/dm-api/src/include/dm_log.h | 20 + dm-framework/dm-api/src/quickjs/._qjs.c | Bin 0 -> 4096 bytes .../dm-api/src/quickjs/._qjs_dm_api.c | Bin 0 -> 4096 bytes dm-framework/dm-api/src/quickjs/qjs.c | 1127 +++++++++ dm-framework/dm-api/src/quickjs/qjs.h | 55 + dm-framework/dm-api/src/quickjs/qjs_api.h | 18 + dm-framework/dm-api/src/quickjs/qjs_dm_api.c | 520 +++++ dm-framework/dm-api/src/quickjs/qjs_log.c | 70 + .../dm-api/src/quickjs/qjs_ubus_api.c | 267 +++ dm-framework/dm-api/src/utils/dm_list.c | 157 ++ dm-framework/dm-api/src/utils/dm_list.h | 77 + dm-framework/dm-api/src/utils/dm_log.c | 54 + dm-framework/dm-api/src/utils/dm_uci.c | 319 +++ dm-framework/dm-api/src/utils/dm_uci.h | 30 + dm-framework/dm-api/src/utils/ubus_client.c | 45 + dm-framework/dm-api/src/utils/ubus_client.h | 21 + dm-framework/dm-api/src/utils/utils.c | 84 + dm-framework/dm-api/src/utils/utils.h | 17 + quickjs/Makefile | 2 + 71 files changed, 16564 insertions(+), 128 deletions(-) delete mode 100644 bridgemngr/Config.in delete mode 100644 bridgemngr/Makefile delete mode 100644 bridgemngr/files/VLAN_Filtering_Extension.json create mode 100755 dm-framework/datamodels/._Makefile create mode 100644 dm-framework/datamodels/Makefile create mode 100644 dm-framework/datamodels/src/Makefile create mode 100755 dm-framework/datamodels/src/dm-files/Bridge/._VLANBridge.json create mode 100644 dm-framework/datamodels/src/dm-files/Bridge/VLANBridge.json create mode 100644 dm-framework/datamodels/src/dm-files/Bridge/bridge-apply.js create mode 100644 dm-framework/datamodels/src/dm-files/Bridge/bridge-import.js create mode 100644 dm-framework/datamodels/src/dm-files/Bridge/bridge.js create mode 100644 dm-framework/datamodels/src/dm-files/makeDM.js create mode 100644 dm-framework/datamodels/src/dm-files/uci.js create mode 100644 dm-framework/datamodels/src/dm-files/utils.js create mode 100644 dm-framework/datamodels/src/dm_node.c create mode 100644 dm-framework/datamodels/src/dm_node.h create mode 100644 dm-framework/datamodels/src/dm_types.h create mode 100755 dm-framework/datamodels/src/json2code.js create mode 100755 dm-framework/dm-agent/._Makefile create mode 100755 dm-framework/dm-agent/._bbfdm_service.json create mode 100644 dm-framework/dm-agent/Makefile rename {bridgemngr => dm-framework/dm-agent}/bbfdm_service.json (90%) rename {bridgemngr => dm-framework/dm-agent}/files/etc/config/bridging (100%) create mode 100644 dm-framework/dm-agent/files/etc/config/dm_agent rename {bridgemngr => dm-framework/dm-agent}/files/etc/init.d/bridging (100%) create mode 100644 dm-framework/dm-agent/files/etc/init.d/dm-agent create mode 100644 dm-framework/dm-agent/files/lib/upgrade/keep.d/dm-agent create mode 100755 dm-framework/dm-agent/src/._dm_agent.c create mode 100644 dm-framework/dm-agent/src/Makefile create mode 100644 dm-framework/dm-agent/src/dm_agent.c create mode 100644 dm-framework/dm-agent/src/main.c create mode 100644 dm-framework/dm-api/Makefile create mode 100644 dm-framework/dm-api/src/Makefile create mode 100755 dm-framework/dm-api/src/core/._dm_api.c create mode 100755 dm-framework/dm-api/src/core/._dm_api.h create mode 100755 dm-framework/dm-api/src/core/._dm_import.c create mode 100755 dm-framework/dm-api/src/core/._dm_linker.c create mode 100755 dm-framework/dm-api/src/core/._dm_linker.h create mode 100644 dm-framework/dm-api/src/core/db_upgrade.c create mode 100644 dm-framework/dm-api/src/core/db_upgrade.h create mode 100644 dm-framework/dm-api/src/core/dbmgr.c create mode 100644 dm-framework/dm-api/src/core/dbmgr.h create mode 100644 dm-framework/dm-api/src/core/dm_api.c create mode 100644 dm-framework/dm-api/src/core/dm_api.h create mode 100644 dm-framework/dm-api/src/core/dm_apply.c create mode 100644 dm-framework/dm-api/src/core/dm_apply.h create mode 100644 dm-framework/dm-api/src/core/dm_import.c create mode 100755 dm-framework/dm-api/src/core/dm_linker.c create mode 100755 dm-framework/dm-api/src/core/dm_linker.h create mode 100644 dm-framework/dm-api/src/core/inode_buf.c create mode 100644 dm-framework/dm-api/src/core/inode_buf.h create mode 100644 dm-framework/dm-api/src/include/dm_log.h create mode 100755 dm-framework/dm-api/src/quickjs/._qjs.c create mode 100755 dm-framework/dm-api/src/quickjs/._qjs_dm_api.c create mode 100644 dm-framework/dm-api/src/quickjs/qjs.c create mode 100644 dm-framework/dm-api/src/quickjs/qjs.h create mode 100644 dm-framework/dm-api/src/quickjs/qjs_api.h create mode 100644 dm-framework/dm-api/src/quickjs/qjs_dm_api.c create mode 100644 dm-framework/dm-api/src/quickjs/qjs_log.c create mode 100644 dm-framework/dm-api/src/quickjs/qjs_ubus_api.c create mode 100644 dm-framework/dm-api/src/utils/dm_list.c create mode 100644 dm-framework/dm-api/src/utils/dm_list.h create mode 100644 dm-framework/dm-api/src/utils/dm_log.c create mode 100644 dm-framework/dm-api/src/utils/dm_uci.c create mode 100644 dm-framework/dm-api/src/utils/dm_uci.h create mode 100644 dm-framework/dm-api/src/utils/ubus_client.c create mode 100644 dm-framework/dm-api/src/utils/ubus_client.h create mode 100644 dm-framework/dm-api/src/utils/utils.c create mode 100644 dm-framework/dm-api/src/utils/utils.h diff --git a/bbfdm/files/etc/init.d/bbfdm.services b/bbfdm/files/etc/init.d/bbfdm.services index c376e7a76..0b60de9dd 100644 --- a/bbfdm/files/etc/init.d/bbfdm.services +++ b/bbfdm/files/etc/init.d/bbfdm.services @@ -5,6 +5,7 @@ STOP=05 USE_PROCD=1 PROG=/usr/sbin/dm-service +DM_AGENT_PROG=/usr/sbin/dm-agent BBFDM_MICROSERVICE_DIR="/etc/bbfdm/services" @@ -24,7 +25,8 @@ validate_bbfdm_micro_service_section() _add_microservice() { local name path loglevel - local enable enable_core unified_daemon + local enable enable_core unified_daemon dm_framework + local daemon_prog # Check enable from micro-service path="${1}" @@ -47,14 +49,25 @@ _add_microservice() return 0 fi + json_get_var dm_framework dm-framework 0 + if [ "${dm_framework}" -eq "1" ] || [ "${dm_framework}" = "true" ]; then + daemon_prog="${DM_AGENT_PROG}" + else + daemon_prog="${PROG}" + fi + json_select config json_get_var loglevel loglevel 4 procd_open_instance "${name}" - procd_set_param command ${PROG} - procd_append_param command -m "${name}" - procd_append_param command -l "${loglevel}" + procd_set_param command ${daemon_prog} + + # Only add parameters for dm-service, not for dm-agent + if [ "${daemon_prog}" = "${PROG}" ]; then + procd_append_param command -m "${name}" + procd_append_param command -l "${loglevel}" + fi if [ "${enable_core}" -eq "1" ]; then procd_set_param limits core="unlimited" diff --git a/bridgemngr/Config.in b/bridgemngr/Config.in deleted file mode 100644 index 0858e1295..000000000 --- a/bridgemngr/Config.in +++ /dev/null @@ -1,17 +0,0 @@ -if PACKAGE_bridgemngr - -config BRIDGEMNGR_BRIDGE_VLAN - bool "Use bridge-vlan backend" - help - Set this option to use bridge-vlan as backend for VLAN objects. - -config BRIDGEMNGR_BRIDGE_VENDOR_EXT - bool "Use bridge BBF vendor extensions" - default y - help - Set this option to use bridge BBF vendor extensions. - -config BRIDGEMNGR_BRIDGE_VENDOR_PREFIX - string "Package specific datamodel Vendor Prefix for TR181 extensions" - default "" -endif diff --git a/bridgemngr/Makefile b/bridgemngr/Makefile deleted file mode 100644 index b0d5a0e85..000000000 --- a/bridgemngr/Makefile +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright (C) 2020-2024 iopsys -# - -include $(TOPDIR)/rules.mk - -PKG_NAME:=bridgemngr -PKG_VERSION:=1.0.17 - -PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) -LOCAL_DEV:=0 -ifneq ($(LOCAL_DEV),1) -PKG_SOURCE_PROTO:=git -PKG_SOURCE_URL:=https://dev.iopsys.eu/network/bridgemngr -PKG_SOURCE_VERSION:=36e6e8319a95dad3bccfe9f2d8a298b39c6ce86b -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz -PKG_MIRROR_HASH:=skip -endif - -PKG_LICENSE:=GPL-2.0-only -PKG_LICENSE_FILES:=LICENSE - -include $(INCLUDE_DIR)/package.mk -include ../bbfdm/bbfdm.mk - -define Package/bridgemngr - CATEGORY:=Utilities - TITLE:=Bridge Manager - DEPENDS:=+libuci +libubox +libubus +libblobmsg-json - DEPENDS+=+libbbfdm-api +libbbfdm-ubus +dm-service -endef - -define Package/bridgemngr/description - Package to add Device.Bridging. data model support. -endef - -define Package/$(PKG_NAME)/config - source "$(SOURCE)/Config.in" -endef - -MAKE_PATH:=src - -ifeq ($(CONFIG_BRIDGEMNGR_BRIDGE_VENDOR_PREFIX),"") -VENDOR_PREFIX = $(CONFIG_BBF_VENDOR_PREFIX) -else -VENDOR_PREFIX = $(CONFIG_BRIDGEMNGR_BRIDGE_VENDOR_PREFIX) -endif - -TARGET_CFLAGS += -DBBF_VENDOR_PREFIX=\\\"$(VENDOR_PREFIX)\\\" - -ifeq ($(CONFIG_BRIDGEMNGR_BRIDGE_VLAN),y) - TARGET_CFLAGS += -DBRIDGE_VLAN_BACKEND -endif - -define Package/bridgemngr/install - $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_DIR) $(1)/etc/config - - $(BBFDM_REGISTER_SERVICES) ./bbfdm_service.json $(1) $(PKG_NAME) - $(BBFDM_INSTALL_MS_DM) $(PKG_BUILD_DIR)/src/libbridgemngr.so $(1) $(PKG_NAME) -ifeq ($(CONFIG_BRIDGEMNGR_BRIDGE_VENDOR_EXT), y) - $(BBFDM_INSTALL_MS_PLUGIN) $(PKG_BUILD_DIR)/src/libbridgeext.so $(1) $(PKG_NAME) 10 - $(BBFDM_INSTALL_MS_PLUGIN) -v ${VENDOR_PREFIX} ./files/VLAN_Filtering_Extension.json $(1) $(PKG_NAME) 11 -endif - - $(INSTALL_BIN) ./files/etc/init.d/bridging $(1)/etc/init.d/ - $(INSTALL_DATA) ./files/etc/config/bridging $(1)/etc/config/ -endef - -ifeq ($(LOCAL_DEV),1) -define Build/Prepare - $(CP) ~/git/bridgemngr/* $(PKG_BUILD_DIR)/ -endef -endif - -$(eval $(call BuildPackage,bridgemngr)) diff --git a/bridgemngr/files/VLAN_Filtering_Extension.json b/bridgemngr/files/VLAN_Filtering_Extension.json deleted file mode 100644 index 250b0be88..000000000 --- a/bridgemngr/files/VLAN_Filtering_Extension.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "json_plugin_version": 2, - "Device.Bridging.Bridge.{i}.": { - "type": "object", - "protocols": [ - "cwmp", - "usp" - ], - "access": true, - "array": true, - "{BBF_VENDOR_PREFIX}VLANFiltering": { - "type": "boolean", - "read": true, - "write": true, - "protocols": [ - "cwmp", - "usp" - ], - "datatype": "boolean", - "description": "Enable or disable VLAN Filtering on this bridge.", - "mapping": [ - { - "data": "@Parent", - "type": "uci_sec", - "key": "vlan_filtering" - } - ] - } - } -} - diff --git a/dm-framework/datamodels/._Makefile b/dm-framework/datamodels/._Makefile new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/datamodels/Makefile b/dm-framework/datamodels/Makefile new file mode 100644 index 000000000..5e0c58213 --- /dev/null +++ b/dm-framework/datamodels/Makefile @@ -0,0 +1,73 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=datamodels +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=utils + CATEGORY:=Genexis + TITLE:=GeneOS Datamodel + URL:=http://www.genexis.eu + PKG_LICENSE:=GENEXIS + PKG_LICENSE_URL:= +endef + +define Package/$(PKG_NAME)/description + This package contains GeneOS datamodel. +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) -rf ./src/* $(PKG_BUILD_DIR)/ + cd $(PKG_BUILD_DIR); \ + npm install better-sqlite3 && \ + node json2code.js +endef + +TARGET_CFLAGS += $(FPIC) -I$(PKG_BUILD_DIR) + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR)\ + PROJECT_ROOT="$(PKG_BUILD_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + EXTRA_CFLAGS="$(TARGET_CFLAGS)" \ + all +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(INSTALL_DIR) $(1)/usr/lib + + $(CP) $(PKG_BUILD_DIR)/dm_types.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/dm_node.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/dm.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/libdm.so $(1)/usr/lib/ +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/lib + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DIR) $(1)/usr/lib/quickjs + $(INSTALL_DIR) $(1)/usr/lib/quickjs/dm_handlers + cd $(PKG_BUILD_DIR)/dm-files && find . -type d -exec mkdir -p $(1)/usr/lib/quickjs/dm_handlers/{} \; + cd $(PKG_BUILD_DIR)/dm-files && find . -name "*.js" -not -name "makeDM.js" -type f -exec cp {} $(1)/usr/lib/quickjs/dm_handlers/{} \; + $(INSTALL_BIN) $(PKG_BUILD_DIR)/default.db $(1)/etc/default_dm.db + $(INSTALL_BIN) $(PKG_BUILD_DIR)/libdm.so $(1)/usr/lib/ +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/dm-framework/datamodels/src/Makefile b/dm-framework/datamodels/src/Makefile new file mode 100644 index 000000000..4a6cf9509 --- /dev/null +++ b/dm-framework/datamodels/src/Makefile @@ -0,0 +1,41 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + + +PROG = libdm.so + +SRCS = dm_node.c + +# the next files are generated +SRCS += dm.c + +OBJS = $(SRCS:.c=.o) +DEPS = $(SRCS:.c=.d) + +CC = $(CROSS_COMPILE)gcc +STRIP = $(CROSS_COMPILE)strip +CFLAGS = -I$(STAGING_DIR)/usr/include $(EXTRA_CFLAGS) +CFLAGS += -MMD -MP + +LDFLAGS = -shared +CFLAGS += -Wall -Werror -fpic + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $^ $(LDFLAGS) -o $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $^ -o $@ + +clean: + rm -f $(PROG) *.o core $(DEPS) dm.c dm.h + +-include $(DEPS) diff --git a/dm-framework/datamodels/src/dm-files/Bridge/._VLANBridge.json b/dm-framework/datamodels/src/dm-files/Bridge/._VLANBridge.json new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/datamodels/src/dm-files/Bridge/VLANBridge.json b/dm-framework/datamodels/src/dm-files/Bridge/VLANBridge.json new file mode 100644 index 000000000..9938f52c6 --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/Bridge/VLANBridge.json @@ -0,0 +1,336 @@ +[ + { + "object": "Device.Bridging.", + "access": "readOnly", + "parameters": [ + { + "name": "MaxBridgeEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "MaxDBridgeEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "MaxQBridgeEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "MaxVLANEntries", + "access": "readOnly", + "dataType": "unsignedInt", + "const" : "4094" + }, + { + "name": "BridgeNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.", + "uniqueKeys": "Name,Alias", + "access": "readWrite", + "uci": "network.device", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean", + "uci": "enabled", + "uci-default": "true" + }, + { + "name": "Name", + "access": "readOnly", + "dataType": "string(:64)", + "set_on_create": "bridge_", + "db": true + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "Status", + "access": "readOnly", + "dataType": "enum", + "enum": [ + "Disabled", + "Enabled", + "Error" + ], + "default": "Disabled" + }, + { + "name": "Standard", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "802.1D-2004", + "802.1Q-2005", + "802.1Q-2011" + ], + "default": "802.1Q-2005" + }, + { + "name": "PortNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "VLANNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "VLANPortNumberOfEntries", + "access": "readOnly", + "dataType": "unsignedInt" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.Port.{i}.", + "uniqueKeys": "Alias,Name", + "access": "readWrite", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Status", + "access": "readOnly", + "dataType": "enum", + "enum": [ + "Up", + "Down", + "Unknown", + "Dormant", + "NotPresent", + "LowerLayerDown", + "Error" + ], + "default": "Down" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "Name", + "access": "readOnly", + "dataType": "string(:64)", + "set_on_create": "port_", + "db": "true", + "flags": [ + "linker" + ] + }, + { + "name": "LastChange", + "access": "readOnly", + "dataType": "unsignedInt", + "const": "0" + }, + { + "name": "LowerLayers", + "access": "readWrite", + "dataType": "pathRef[]", + "pathRef": [ + "Device.Bridging.Bridge.{i}.Port." + ] + }, + { + "name": "ManagementPort", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "DefaultUserPriority", + "access": "readWrite", + "dataType": "unsignedInt(0:7)" + }, + { + "name": "PriorityRegeneration", + "access": "readWrite", + "dataType": "unsignedInt(0:7)[]", + "default": "0,1,2,3,4,5,6,7" + }, + { + "name": "X_IOPSYS_EU_EgressPriorityRegeneration", + "access": "readWrite", + "dataType": "unsignedInt(0:7)[]", + "default": "0,1,2,3,4,5,6,7" + }, + { + "name": "Type", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "ProviderNetworkPort", + "CustomerNetworkPort", + "CustomerEdgePort", + "CustomerVLANPort", + "VLANUnawarePort" + ] + }, + { + "name": "PVID", + "access": "readWrite", + "dataType": "int(1:4094)", + "default": "1" + }, + { + "name": "TPID", + "access": "readWrite", + "dataType": "unsignedInt", + "default": "33024" + }, + { + "name": "AcceptableFrameTypes", + "access": "readWrite", + "dataType": "enum", + "enum": [ + "AdmitAll", + "AdmitOnlyVLANTagged", + "AdmitOnlyPrioUntagged" + ], + "default": "AdmitAll" + }, + { + "name": "IngressFiltering", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "PriorityTagging", + "access": "readWrite", + "dataType": "boolean" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.Port.{i}.Stats.", + "access": "readOnly", + "parameters": [ + { + "name": "BytesSent", + "access": "readOnly", + "dataType": "unsignedLong" + }, + { + "name": "BytesReceived", + "access": "readOnly", + "dataType": "unsignedLong" + }, + { + "name": "PacketsSent", + "access": "readOnly", + "dataType": "unsignedLong" + }, + { + "name": "PacketsReceived", + "access": "readOnly", + "dataType": "unsignedLong" + }, + { + "name": "ErrorsSent", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "ErrorsReceived", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "DiscardPacketsSent", + "access": "readOnly", + "dataType": "unsignedInt" + }, + { + "name": "DiscardPacketsReceived", + "access": "readOnly", + "dataType": "unsignedInt" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.VLAN.{i}.", + "uniqueKeys": "Alias,VLANID", + "access": "readWrite", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Name", + "access": "readWrite", + "dataType": "string(:64)", + "set_on_create": "vlan_" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "VLANID", + "access": "readWrite", + "dataType": "int(0:4094)" + } + ] + }, + { + "object": "Device.Bridging.Bridge.{i}.VLANPort.{i}.", + "uniqueKeys": "Alias,VLAN", + "access": "readWrite", + "parameters": [ + { + "name": "Enable", + "access": "readWrite", + "dataType": "boolean" + }, + { + "name": "Alias", + "access": "readWrite", + "dataType": "string(:64)" + }, + { + "name": "VLAN", + "access": "readWrite", + "dataType": "pathRef", + "pathRef": [ + "Device.Bridging.Bridge.{i}.VLAN." + ] + }, + { + "name": "Port", + "access": "readWrite", + "dataType": "pathRef", + "pathRef": [ + "Device.Bridging.Bridge.{i}.Port." + ] + }, + { + "name": "Untagged", + "access": "readWrite", + "dataType": "boolean" + } + ] + } +] \ No newline at end of file diff --git a/dm-framework/datamodels/src/dm-files/Bridge/bridge-apply.js b/dm-framework/datamodels/src/dm-files/Bridge/bridge-apply.js new file mode 100644 index 000000000..c18f74a64 --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/Bridge/bridge-apply.js @@ -0,0 +1,177 @@ +/* eslint-disable no-underscore-dangle */ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +/* global _dm_get, _dm_node, _log_error */ +/* eslint-disable no-continue */ +/* eslint-disable no-param-reassign */ + +import { + getUciOption, getUciByType, setUci, addUci, delUci, delUciOption, +} from '../uci.js'; +import * as dm from '../dm_consts.js'; +import { getBridgeInterfaceName } from './bridge.js'; + +function clearUnusedDevice(oldPorts, newPorts, devices) { + oldPorts?.forEach((port) => { + if (port.includes('.') && !newPorts?.includes(port)) { + const dev = devices?.find((x) => x.name === port); + if (dev) { + delUci('network', dev['.name']); + } + } + }); +} + +function applyBridge(bri, ports, VLANs, VLANPorts) { + const ifnames = []; + const devices = getUciByType('network', 'device'); + const portsVal = getUciOption('network', bri._key, 'ports'); + if (portsVal) { + delUci('network', bri._key, null, 'ports'); + } + + // get ports ethernet ifnames + for (const port of ports || []) { + if (port.ManagementPort || !port.LowerLayers.includes('Ethernet.Interface') || !port.Enable) { + continue; + } + + let ifname = _dm_get(`${port.LowerLayers}.Name`); + // check vlan + const portPath = `Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}`; + const vp = VLANPorts?.find((x) => x.Port === portPath); + if (!vp?.VLAN) { + ifnames.push(ifname); + continue; + } + // get index of the vlan + const [, indices] = _dm_node(vp.VLAN); + const vlanIdx = indices[indices.length - 1]; + const vlan = VLANs?.find((x) => x['.index'] === vlanIdx); + if (!vlan || vlan.VLANID <= 0) { + ifnames.push(ifname); + continue; + } + const eth = ifname; + ifname = `${ifname}.${vlan.VLANID}`; + + const dev = devices?.find((x) => x.name === ifname); + let devName; + if (dev) { + devName = dev['.name']; + } else { + devName = `br_${bri['.index']}_port_${vp['.index']}`; + const options = { + ifname: eth, + name: ifname, + vid: vlan.VLANID, + }; + addUci( + 'network', + 'device', + devName, + options, + ); + } + const uciConfigs = {}; + if (!vp.Untagged) { + uciConfigs.type = '8021q'; + } + uciConfigs.disabled = vlan.Enable && vp.Enable ? '0' : '1'; + if (port.PriorityRegeneration !== '0,1,2,3,4,5,6,7') { + uciConfigs.ingress_qos_mapping = port.PriorityRegeneration.split(',').map((p, i) => `${i}:${p}`); + } else { + uciConfigs.ingress_qos_mapping = ''; + } + + if (port.X_IOPSYS_EU_EgressPriorityRegeneration !== '0,1,2,3,4,5,6,7') { + uciConfigs.egress_qos_mapping = port.X_IOPSYS_EU_EgressPriorityRegeneration.split(',').map((p, i) => `${i}:${p}`); + } else { + uciConfigs.egress_qos_mapping = ''; + } + setUci('network', devName, uciConfigs); + ifnames.push(ifname); + } + + clearUnusedDevice(portsVal, ifnames, devices); + + if (ifnames.length > 0) { + setUci('network', bri._key, { ports: ifnames }); + } + + // apply for the WiFi.SSID port + const ssids = _dm_get('Device.WiFi.SSID.'); + if (ssids && ssids.length > 0) { + const networkName = getBridgeInterfaceName(bri['.index']); + if (!networkName) { + _log_error('network name is not found for bridge'); + return; + } + const ssidPorts = ports?.filter((x) => x.LowerLayers.includes('WiFi.SSID.')); + ssids?.forEach((ssid) => { + const path = `Device.WiFi.SSID.${ssid['.index']}`; + if (ssidPorts?.find((p) => p.Enable && p.LowerLayers === path)) { + setUci('wireless', ssid._key, { network: networkName }); + } else if (ssid.network === networkName) { + delUciOption('wireless', ssid._key, 'network'); + } + }); + } +} + +export function applyDeviceBridgingBridgePort(ports, bri) { + const vlans = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLAN, bri['.index']); + const vlanPorts = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLANPORT, bri['.index']); + applyBridge(bri, ports, vlans, vlanPorts); +} + +export function applyDeviceBridgingBridgeVLAN(vlans, bri) { + const ports = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_PORT, bri['.index']); + const vlanPorts = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLANPORT, bri['.index']); + applyBridge(bri, ports, vlans, vlanPorts); +} + +export function applyDeviceBridgingBridgeVLANPort(vlanPorts, bri) { + const ports = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_PORT, bri['.index']); + const vlans = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLAN, bri['.index']); + applyBridge(bri, ports, vlans, vlanPorts); +} + +export function initDeviceBridgingBridge(bri) { + setUci('network', bri._key, { + type: 'bridge', + name: bri.Name, + enabled: '0', + }); + // create empty interface for the bridge, otherwise the bridge will not be up. + // the empty interface will be deleted if there is a real interface is created later. + addUci('network', 'interface', `itf_${bri._key}`, { + device: bri.Name, + layer2_bridge: '1', + }); +} + +export function filterDeviceBridgingBridge(uci) { + return (uci.type === 'bridge'); +} + +export function deinitDeviceBridgingBridge(uci) { + const ports = getUciOption('network', uci, 'ports'); + ports?.forEach((port) => { + if (port.includes('.')) { + const dev = getUciByType('network', 'device', { match: { name: port } }); + if (dev) { + delUci('network', dev[0]['.name']); + } + } + }); +} diff --git a/dm-framework/datamodels/src/dm-files/Bridge/bridge-import.js b/dm-framework/datamodels/src/dm-files/Bridge/bridge-import.js new file mode 100644 index 000000000..eb77c7478 --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/Bridge/bridge-import.js @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +/* global _log_error + */ +/* eslint-disable no-continue */ +/* eslint-disable no-param-reassign */ + +import { getUciByType } from '../uci.js'; + +function importBridge(dev, devices, bridges) { + const briPorts = []; + const briVLAN = []; + const briVLANPort = []; + // create the management port first + briPorts.push({ + Alias: `cpe-${dev.name}`, + Enable: 1, + Name: 'ManagementPort', + ManagementPort: 1, + PVID: 1, + TPID: 37120, + Type: 'CustomerVLANPort', + }); + + bridges.push({ + Name: dev.name, + Alias: `cpe-${dev.name}`, + Enable: 1, + 'Port.': briPorts, + 'VLAN.': briVLAN, + 'VLANPort.': briVLANPort, + _key: dev['.name'], + }); + + const ethPorts = devices.filter((x) => x.ifname?.startsWith('eth')); + + for (const portName of (dev.ports || [])) { + let portIndex = ethPorts.findIndex((x) => x.ifname === portName); + if (portIndex >= 0) { + briPorts.push({ + Enable: 1, + Alias: `cpe-${ethPorts[portIndex]['.name']}`, + TPID: 37120, + PVID: 1, + Type: 'CustomerVLANPort', + LowerLayers: `Device.Ethernet.Interface.${portIndex + 1}`, + _key: ethPorts[portIndex]['.name'], + }); + } else { + // vlan device + const device = devices.find((x) => x.name === portName); + if (!device) { + _log_error('device not found', portName); + // eslint-disable-next-line no-continue + continue; + } + + if (device.type === '8021q' || device.type === 'untagged') { + let vlanIndex = briVLAN.findIndex((x) => x.VLANID === device.vid); + if (vlanIndex < 0) { + briVLAN.push({ Enable: 1, VLANID: device.vid }); + vlanIndex = briVLAN.length; + } else { + vlanIndex += 1; + } + + briPorts.push({ + Enable: 1, + Alias: `cpe-${port['.name']}`, + TPID: 33024, + PVID: device.vid, + Type: 'CustomerVLANPort', + LowerLayers: `Device.Ethernet.Interface.${port['.index'] + 1}`, + _key: device['.name'], + }); + + briVLANPort.push({ + Enable: 1, + VLAN: `Device.Bridging.Bridge.${bridges.length}.VLAN.${vlanIndex}`, + Port: `Device.Bridging.Bridge.${bridges.length}.Port.${briPorts.length}`, + Untagged: device.type === 'untagged' ? 1 : 0, + _key: device['.name'], + }); + } else { + _log_error('unknown device type:', device.type); + } + } + } + + // add Port for WiFi SSIDs + let wifiIfaces = getUciByType('wireless', 'wifi-iface'); + wifiIfaces = wifiIfaces?.filter((x) => !(x.multi_ap === '1' || x.type === 'backhaul')); + if (wifiIfaces && wifiIfaces.length > 0) { + wifiIfaces?.forEach((x, i) => { + x.index = i + 1; + }); + const itfSect = getUciByType('network', 'interface', { match: { device: dev.name } }); + if (itfSect && itfSect.length > 0) { + const itfName = itfSect[0]['.name']; + wifiIfaces = wifiIfaces.filter((x) => x.network === itfName); + wifiIfaces.forEach((x) => { + briPorts.push({ + Enable: 1, + Alias: `cpe-${x['.name']}`, + TPID: 37120, + PVID: 1, + Type: 'CustomerVLANPort', + LowerLayers: `Device.WiFi.SSID.${x.index}`, + _key: x['.name'], + }); + }); + } + } + + if (briPorts.length > 1) { + const indexes = Array.from({ length: briPorts.length - 1 }, (v, i) => i + 2); + briPorts[0].LowerLayers = indexes.map((i) => `Device.Bridging.Bridge.${bridges.length}.Port.${i}`).join(','); + } +} + +export function importDeviceBridgingBridge() { + const bridges = []; + const devices = getUciByType('network', 'device'); + devices?.forEach((dev) => { + if (dev.type === 'bridge') { + importBridge(dev, devices, bridges); + } + }); + + return bridges; +} + +export default importDeviceBridgingBridge; diff --git a/dm-framework/datamodels/src/dm-files/Bridge/bridge.js b/dm-framework/datamodels/src/dm-files/Bridge/bridge.js new file mode 100644 index 000000000..ad8b2e704 --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/Bridge/bridge.js @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +/* global _dm_get, _dm_update, _dm_instances, _ubus_call */ + +// eslint-disable-next-line import/no-unresolved +import * as std from 'std'; +import { getUciByType } from '../uci.js'; +import { findPathInLowerlayer } from '../utils.js'; + +function setMgmtPortLowerLayers(bri) { + if (!bri) { + return 0; + } + const portPath = `Device.Bridging.Bridge.${bri['.index']}.Port.`; + const mgmtPort = _dm_instances(portPath, '(ManagementPort="true" OR ManagementPort=1)'); + if (mgmtPort.length !== 1) { + return 0; + } + const nonMgmtPort = _dm_instances(portPath, '(ManagementPort="false" OR ManagementPort=0)'); + _dm_update(`${mgmtPort[0]}.LowerLayers`, nonMgmtPort.join(',')); + return 0; +} + +export function changedDeviceBridgingBridgePort(bri) { + return setMgmtPortLowerLayers(bri); +} + +export function changedDeviceBridgingBridgePortManagementPort(bri) { + return setMgmtPortLowerLayers(bri); +} + +export function getDeviceBridgingBridgeStatus(bri) { + const enable = _dm_get(`Device.Bridging.Bridge.${bri['.index']}.Enable`); + if (enable) { + return 'Enabled'; + } + + return 'Disabled'; +} + +export function getDeviceBridgingBridgePortStatus(bri, port) { + if (!port['.db']) { + return 'Up'; + } + const enable = _dm_get(`Device.Bridging.Bridge.${bri['.index']}.Port.${port['.index']}.Enable`); + if (enable) { + return 'Up'; + } + + return 'Down'; +} + +export function infoDeviceBridgingBridgePort(path, port) { + const mgmtPort = _dm_get(`${path}.ManagementPort`); + if (typeof mgmtPort === 'undefined' || mgmtPort) { + return; + } + + const briPath = path.split('.').slice(0, -2).join('.'); + const briName = _dm_get(`${briPath}.Name`); + let [, devices] = _ubus_call('uci', 'get', { config: 'network', type: 'device' }); + if (!devices && !devices.values) { + return; + } + + devices = Object.values(devices.values); + + // find the bridge device. + const brDev = devices.find((x) => x.name === briName); + if (!brDev || !brDev.ports) { + return; + } + + const lower = _dm_get(`${path}.LowerLayers`); + if (lower === '') { + return; + } + + const name = _dm_get(`${lower}.Name`); + const portName = brDev.ports.find((x) => x.startsWith(name)); + if (!portName) { + return; + } + + // eslint-disable-next-line no-param-reassign + port.device = portName; +} + +export function getDeviceBridgingBridgePortStatsBytesSent(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/tx_bytes`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsBytesReceived(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/rx_bytes`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsPacketsSent(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/tx_packets`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsPacketsReceived(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/rx_packets`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsErrorsSent(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/tx_errors`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsErrorsReceived(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/rx_errors`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsDiscardPacketsSent(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/tx_dropped`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsDiscardPacketsReceived(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/rx_dropped`)?.trim(); +} + +export function getDeviceBridgingBridgePortStatsMulticastPacketsReceived(bri, port) { + if (!port.device) { + return 0; + } + + return std.loadFile(`/sys/class/net/${port.device}/statistics/multicast`)?.trim(); +} + +export function getBridgeInterfaceName(briIndex) { + const insts = _dm_instances('Device.IP.Interface.'); + const inst = insts.find((x) => findPathInLowerlayer(x, `Device.Bridging.Bridge.${briIndex}.Port.1`, 'Bridging.Bridge.')); + if (inst) { + return _dm_get(`${inst}._key`); + } + return undefined; +} + +export function getDeviceBridgingBridgePort(bri) { + const networkName = getBridgeInterfaceName(bri['.index']); + if (!networkName) { + return undefined; + } + // add Port for WiFi SSIDs + const wifiIfaces = getUciByType('wireless', 'wifi-iface'); + wifiIfaces?.forEach((x, i) => { + x.index = i + 1; + }); + return wifiIfaces?.filter((x) => x.network === networkName); +} + +export function getDeviceBridgingBridgePortLowerLayers(bri, port) { + return `Device.WiFi.SSID.${port.index}`; +} + +export function getDeviceBridgingBridgePortManagementPort() { + return '0'; +} + +export function getDeviceBridgingBridgePortName(bri, port) { + return port['.name']; +} diff --git a/dm-framework/datamodels/src/dm-files/makeDM.js b/dm-framework/datamodels/src/dm-files/makeDM.js new file mode 100644 index 000000000..c5df27131 --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/makeDM.js @@ -0,0 +1,508 @@ +/* eslint-disable no-await-in-loop */ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +const assert = require('assert'); +const fs = require('fs'); +const xml2js = require('xml2js'); +const util = require('util'); + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); + +const parser = new xml2js.Parser(); +const parseXML = util.promisify(parser.parseString); + +const cwmpTr181XmlFile = 'tr-181-2-15-1-cwmp-full.xml'; +const uspTr181XmlFile = 'tr-181-2-15-1-usp-full.xml'; +const uspTr181VendorExtXmlFile = 'tr-181-vendor-extensions-usp.xml'; +const Tr104USPXmlFile = 'tr-104-2-0-2-usp-full.xml'; +const Tr104CWMPXmlFile = 'tr-104-2-0-2-cwmp-full.xml'; + +let cwmpModel; +let uspModel; +let uspVendorExtModel; + +let tr181 = true; + +async function saveFile(file, obj) { + await writeFile(file, JSON.stringify(obj, null, 4)); + console.log('saved file:', file); +} + +function getRange(attr) { + if (typeof attr[0] !== 'object') { + return ''; + } + + if (attr[0].range) { + const range = attr[0].range[0].$; + return `(${range.minInclusive ?? ''}:${range.maxInclusive ?? ''})`; + } + + return ''; +} + +function objParent(obj) { + let parent; + if (obj.endsWith('}.')) { + parent = obj.slice(0, -5); + } else { + parent = obj.slice(0, -1); + } + return parent.substring(0, parent.lastIndexOf('.')); +} + +function parsePathRef(obj, ref) { + let path = obj.$.name; + + ref = ref.trim().replace(/\n$/, ''); + if (ref.startsWith('.')) { + if (path.startsWith('Device.Services.VoiceService.') && !path.endsWith('()')) { + return `Device.Services.VoiceService.{i}${ref}`; + } + return `Device${ref}`; + } + if (ref.startsWith('#')) { + while (ref.startsWith('#')) { + path = objParent(path); + ref = ref.slice(1); + } + return path + ref; + } + if (ref.startsWith('Device.')) { + return ref; + } + return path + ref; +} + +function getParamType(obj, res, syntaxType) { + const intTypes = [ + 'int', + 'long', + 'unsignedInt', + 'unsignedLong', + ]; + + const intType = intTypes.find((x) => Object.keys(syntaxType).includes(x)); + if (intType) { + res.dataType = `${intType}${getRange(syntaxType[intType])}`; + } else if (syntaxType.boolean) { + res.dataType = 'boolean'; + } else if (syntaxType.dateTime) { + res.dataType = 'dateTime'; + } else if (syntaxType.string) { + const attr = syntaxType.string[0]; + if (typeof attr === 'object') { + if (attr.enumeration) { + res.dataType = 'enum'; + res.enum = attr.enumeration.map((x) => x.$.value); + } else if (attr.pathRef) { + res.dataType = 'pathRef'; + if (attr.pathRef[0].$.targetParent) { + res.pathRef = attr.pathRef[0].$.targetParent.split(' ').filter((x) => x).map((x) => parsePathRef(obj, x)); + } + } else if (attr.enumerationRef) { + res.dataType = 'enum'; + res.enumerationRef = parsePathRef(obj, attr.enumerationRef[0].$.targetParam); + } else if (attr.size) { + res.dataType = `string(${attr.size[0].$?.minLength ?? ''}:${attr.size[0].$?.maxLength ?? ''})`; + } else if (attr.pattern) { + // handle it as enum + res.dataType = 'enum'; + res.enum = attr.pattern.map((x) => x.$.value); + } else { + assert(false, `unknown string type: ${JSON.stringify(syntaxType, null, 2)}`); + } + } else { + res.dataType = 'string'; + } + } else if (syntaxType.dataType) { + res.dataType = syntaxType.dataType[0].$.ref; + if (res.dataType === 'Alias') { + res.dataType = 'string(:64)'; + } else if (res.dataType === 'DiagnosticsState') { + res.dataType = 'enum'; + res.enum = ['None', 'Requested', 'Canceled', 'Complete', 'Error']; + } else if (res.dataType === 'StatsCounter64') { + res.dataType = 'unsignedLong'; + } + } else if (syntaxType.hexBinary) { + res.dataType = 'hexBinary'; + const { size } = syntaxType.hexBinary[0]; + if (size) { + res.dataType += `(${size[0].$?.minLength ?? ''}:${size[0].$?.maxLength ?? ''})`; + } + } else if (syntaxType.base64) { + res.dataType = 'base64'; + const { size } = syntaxType.base64[0]; + if (size) { + res.dataType += `(${size[0].$?.minLength ?? ''}:${size[0].$?.maxLength ?? ''})`; + } + } else if (syntaxType.decimal) { + res.dataType = 'decimal'; + const { size } = syntaxType.decimal[0]; + if (size) { + res.dataType += `(${size[0].$?.minLength ?? ''}:${size[0].$?.maxLength ?? ''})`; + } + } else { + console.log(`unknown datatype:\n ${JSON.stringify(syntaxType, null, 4)}`); + res.dataType = 'unknown'; + } + + if (syntaxType.list) { + res.dataType += '[]'; + } +} + +function getParamObj(obj, param, proto) { + const res = { + name: param.$.name, + }; + + if (proto) { + res.proto = proto; + } + + res.access = param.$.access; + const syntaxType = param.syntax[0]; + + getParamType(obj, res, syntaxType); + + if (syntaxType.$?.hidden) { + res.hidden = true; + } + + if (syntaxType.default && syntaxType.default[0].$?.value) { + const def = syntaxType.default[0].$?.value; + if (def !== 'false' && def !== '') { + res.default = syntaxType.default[0].$?.value; + } + } + + return res; +} + +let allObjects = []; + +function generateCWMPObjects(objs) { + objs.forEach((obj) => { + const o = allObjects.find((x) => x.object === obj.$.name); + if (o) { + const params = obj.parameter.map((param) => getParamObj(obj, param, 'cwmp')); + o.parameters = o.parameters.concat(params); + } else { + allObjects.push({ + object: obj.$.name, + proto: 'cwmp', + fixedObject: obj.$['dmr:fixedObject'], + uniqueKeys: obj.uniqueKey?.map((x) => x.parameter[0].$.ref).join(','), + // numEntriesParameter: obj.$.numEntriesParameter, + access: obj.$.access, + parameters: obj.parameter?.map((param) => ( + getParamObj(obj, param, 'cwmp'))) ?? [], + }); + } + }); +} + +function getCommandInput(cmdInfo) { + if (!cmdInfo.input) { + return []; + } + + const objParams = []; + if (cmdInfo.input[0].object) { + cmdInfo.input[0].object.forEach((obj) => { + obj.parameter.forEach((p) => { + const inputParams = { + parameter: obj.$.name + p.$.name, + mandatory: p.$.mandatory === 'true', + }; + getParamType(cmdInfo, inputParams, p.syntax[0]); + objParams.push(inputParams); + }); + }); + } + + const params = cmdInfo.input[0].parameter?.map((p) => { + const inputParams = { + parameter: p.$.name, + mandatory: p.$.mandatory === 'true', + }; + getParamType(cmdInfo, inputParams, p.syntax[0]); + return inputParams; + }); + + return objParams.concat(params ?? []); +} + +function getCommandOutput(cmdInfo) { + if (!cmdInfo.output) { + return []; + } + + const outParams = cmdInfo.output[0].parameter?.map((p) => { + const outputs = { + parameter: p.$.name, + }; + getParamType(cmdInfo, outputs, p.syntax[0]); + return outputs; + }); + + const outObjs = cmdInfo.output[0].object?.map((obj) => { + const outputs = { + object: obj.$.name, + }; + outputs.parameters = obj.parameter?.map((p) => { + const outs = { + parameter: p.$.name, + }; + getParamType(cmdInfo, outs, p.syntax[0]); + return outs; + }); + return outputs; + }); + + if (outObjs) { + return outParams.concat(outObjs); + } + + return outParams; +} + +function generateUSPObjects(objs) { + objs.forEach((obj) => { + const o = allObjects.find((x) => x.object === obj.$.name); + if (o) { + delete o.proto; + obj.parameter?.forEach((p) => { + const param = o.parameters.find((x) => x.name === p.$.name); + if (param) { + delete param.proto; + } else { + o.parameters.push(getParamObj(obj, p, 'usp')); + } + }); + + if (obj.command) { + const cmds = obj.command.map((cmd) => ({ + name: cmd.$.name, + async: !!cmd.$.async, + input: getCommandInput(cmd), + output: getCommandOutput(cmd), + })); + + if (o.commands) { + o.commands = o.commands.concat(cmds); + } else { + o.commands = cmds; + } + } + + if (obj.event) { + const events = obj.event.map((ev) => ({ + name: ev.$.name, + parameter: ev.parameter?.map((p) => p.$.name), + })); + + if (o.events) { + o.events = o.events.concat(events); + } else { + o.events = events; + } + } + } else { + const cwmpObj = cwmpModel.object.find((x) => x.$.name === obj.$.name); + const newObj = { + object: obj.$.name, + proto: cwmpObj ? undefined : 'usp', + uniqueKeys: obj.uniqueKey?.map((x) => x.parameter[0].$.ref).join(','), + // numEntriesParameter: obj.$.numEntriesParameter, + access: obj.$.access, + fixedObject: obj.$['dmr:fixedObject'], + parameters: obj.parameter?.map((param) => ( + getParamObj(obj, param, (cwmpObj && cwmpObj.parameter?.find((x) => x.$.name === param.$.name)) ? undefined : 'usp'))) ?? [], + }; + + if (obj.command) { + newObj.commands = obj.command.map((cmd) => ({ + name: cmd.$.name, + async: !!cmd.$.async, + input: getCommandInput(cmd), + output: getCommandOutput(cmd), + })); + } + + if (obj.event) { + newObj.events = obj.event.map((ev) => ({ + name: ev.$.name, + parameter: ev.parameter?.map((p) => p.$.name), + })); + } + allObjects.push(newObj); + } + }); +} + +function mergeProfileObjs(obj1, obj2) { + if (!obj1) { + return obj2; + } + + if (!obj2) { + return obj1; + } + + obj2.forEach((obj) => { + const o = obj1.find((x) => x.object === obj.object); + if (o) { + if (o.parameter) { + o.parameter = o.parameter.concat(obj.parameter ?? []); + } else { + o.parameter = obj.parameter; + } + + if (o.command) { + o.command = o.command.concat(obj.command ?? []); + } else { + o.command = obj.command; + } + + if (o.event) { + o.event = o.event.concat(obj.event ?? []); + } else { + o.event = obj.event; + } + } else { + obj1.push(obj); + } + }); + + return obj1; +} + +function parseProfileObjects(model, profileName) { + const profile = model.profile.find((x) => x.$.name === profileName); + if (!profile) { + return []; + } + // assert(profile, `profile not found ${profileName}`); + + const objs = profile.object?.map((o) => ({ + object: o.$.ref, + parameter: o.parameter?.map((p) => p.$.ref), + command: o.command?.map((c) => c.$.ref), + event: o.event?.map((e) => e.$.ref), + })); + + const exts = profile.$.extends ?? profile.$.base; + if (exts) { + let res = objs; + exts.split(' ').forEach((ext) => { + const extObjs = parseProfileObjects(model, ext); + res = mergeProfileObjs(res, extObjs); + }); + + return res; + } + return objs; +} + +async function getProfileObjects(model, profileName) { + const profileObjs = parseProfileObjects(model, profileName); + const objs = []; + + profileObjs.forEach((obj) => { + const targetObj = model.object.find((o) => o.$.name === obj.object); + assert(targetObj, `object not found ${obj.object}`); + const keys = targetObj.uniqueKey?.map((x) => x.parameter[0].$.ref); + targetObj.parameter = targetObj.parameter?.filter((x) => obj.parameter?.includes(x.$.name) || (keys?.includes(x.$.name))); + targetObj.command = targetObj.command?.filter((x) => obj.command?.includes(x.$.name)); + objs.push(targetObj); + }); + + return objs; +} + +async function loadXMLModel(file) { + const xmlData = await readFile(file, 'utf8'); + const jsonData = await parseXML(xmlData); + const [model] = jsonData['dm:document'].model; + return model; +} + +function printUsage() { + console.log('Usage:\nnode makeDM.js [profile]'); +} + +(async () => { + try { + if (process.argv.length < 3 || (process.argv[2] !== 'tr181' && process.argv[2] !== 'tr104')) { + printUsage(); + process.exit(-1); + } + + if (process.argv[2] === 'tr104') { + tr181 = false; + } + + uspVendorExtModel = await loadXMLModel(uspTr181VendorExtXmlFile); + if (tr181) { + cwmpModel = await loadXMLModel(cwmpTr181XmlFile); + uspModel = await loadXMLModel(uspTr181XmlFile); + } else { + cwmpModel = await loadXMLModel(Tr104CWMPXmlFile); + uspModel = await loadXMLModel(Tr104USPXmlFile); + cwmpModel.object.forEach((obj) => { + obj.$.name = `Device.Services.${obj.$.name}`; + }); + cwmpModel.profile.forEach((prof) => { + prof.object?.forEach((obj) => { + obj.$.ref = `Device.Services.${obj.$.ref}`; + }); + }); + uspModel.object.forEach((obj) => { + obj.$.name = `Device.Services.${obj.$.name}`; + }); + uspModel.profile.forEach((prof) => { + prof.object?.forEach((obj) => { + obj.$.ref = `Device.Services.${obj.$.ref}`; + }); + }); + } + + if (process.argv.length === 3) { + generateCWMPObjects(cwmpModel.object); + generateUSPObjects(uspModel.object); + if (tr181) { + generateUSPObjects(uspVendorExtModel.object); + } + const fileName = `${tr181 ? 'tr181' : 'tr104'}-full-objects.json`; + await saveFile(fileName, allObjects); + process.exit(0); + } + + for (const arg of process.argv.slice(3)) { + // profile + const profile = arg; + const cwmpObjects = await getProfileObjects(cwmpModel, profile); + const uspObjects = await getProfileObjects(uspModel, profile); + generateCWMPObjects(cwmpObjects); + generateUSPObjects(uspObjects); + await saveFile(`${profile}.json`, allObjects); + allObjects = []; + } + } catch (error) { + console.error(`Error while reading file: ${error}`); + console.log(error.stack); + } +})(); diff --git a/dm-framework/datamodels/src/dm-files/uci.js b/dm-framework/datamodels/src/dm-files/uci.js new file mode 100644 index 000000000..7c350b1fb --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/uci.js @@ -0,0 +1,139 @@ +/* eslint-disable no-undef */ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +export function uciBool(val) { + // by default enable is true if it is not defined + return (val === undefined || val === '1' || val === 'true' || val === 'enable' || val === 'yes'); +} + +export function getUci(args) { + const [, res] = _ubus_call('uci', 'get', args); + if (res) { + if (res.values) { + if (!args.section) { + return Object.values(res.values); + } + return res.values; + } + if (res.value) { + return res.value; + } + } + return undefined; +} + +export function getUciOption(config, section, option, extraArgs) { + let args = { config, section, option }; + + if (extraArgs) { + args = { ...args, ...extraArgs }; + } + + return getUci(args); +} + +export function getUciByType(config, type, extraArgs) { + let args = { config, type }; + + if (extraArgs) { + args = { ...args, ...extraArgs }; + } + + return getUci(args); +} + +export function getUciSection(config, section, extraArgs) { + let args = { config, section }; + + if (extraArgs) { + args = { ...args, ...extraArgs }; + } + + return getUci(args); +} + +export function setUci(cfg, section, values, type, match) { + const args = { config: cfg }; + if (type) { + args.type = type; + } + + if (values) { + args.values = values; + } + + if (section) { + args.section = section; + } + + if (match) { + args.match = match; + } + + const [ret] = _ubus_call('uci', 'set', args); + return ret; +} + +export function addUci(cfg, type, name, values) { + const args = { + config: cfg, type, values, + }; + // name is optional + if (name) { + args.name = name; + } + const [, res] = _ubus_call('uci', 'add', args); + return res || undefined; +} + +export function delUci(cfg, section, type, option, options, match) { + const args = { + config: cfg, option, options, match, + }; + if (type) { + args.type = type; + } + if (section) { + args.section = section; + } + const [, res] = _ubus_call('uci', 'delete', args); + return res || undefined; +} + +export function delUciOption(config, section, option, match) { + const args = { + config, section, option, + }; + if (match) { + args.match = match; + } + const [, res] = _ubus_call('uci', 'delete', args); + return res || undefined; +} + +export function uciChanges(cfg) { + const args = { config: cfg }; + const [, res] = _ubus_call('uci', 'changes', args); + return res.changes || undefined; +} + +export function commitUci(cfg) { + const args = { config: cfg }; + const [res] = _ubus_call('uci', 'commit', args); + return res; +} + +export function revertUci(cfg) { + const args = { config: cfg }; + const [res] = _ubus_call('uci', 'revert', args); + return res; +} diff --git a/dm-framework/datamodels/src/dm-files/utils.js b/dm-framework/datamodels/src/dm-files/utils.js new file mode 100644 index 000000000..36954a9c2 --- /dev/null +++ b/dm-framework/datamodels/src/dm-files/utils.js @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +/* eslint-disable no-undef */ +/* eslint-disable no-bitwise */ +import * as os from 'os'; +import * as std from 'std'; + +export function macAddressToBase64(macAddress) { + // Split the MAC address into an array of bytes using the colon separator + const bytes = macAddress.split(':'); + + // Convert the bytes from hexadecimal to decimal + const decimalBytes = bytes.map((byte) => parseInt(byte, 16)); + + // Convert the decimal bytes into an array of 8-bit binary strings + const binaryBytes = decimalBytes.map((byte) => byte.toString(2).padStart(8, '0')); + + // Concatenate the binary strings into a single string + const binaryString = binaryBytes.join(''); + + // Split the binary string into groups of 6 bits + const base64Chars = []; + for (let i = 0; i < binaryString.length; i += 6) { + base64Chars.push(binaryString.slice(i, i + 6)); + } + + // Convert each group of 6 bits to a decimal number + const decimalBase64 = base64Chars.map((char) => parseInt(char, 2)); + + // Create the base64 character set + const base64CharacterSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + // Map the decimal numbers to their corresponding base64 characters + const base64String = decimalBase64.map((num) => base64CharacterSet.charAt(num)).join(''); + + return base64String; +} + +export function tr181Path(path, uciPath, keyName, keyVal) { + if (!keyVal) { + return ''; + } + + const [uciConfig, uciType] = uciPath.split('.'); + const args = { config: uciConfig, type: uciType }; + + const [, res] = _ubus_call('uci', 'get', args); + if (!res || !res.values) { + _log_error('tr181Path: invalid result'); + return ''; + } + + let insts = Object.values(res.values); + if (uciConfig === 'network' && uciType === 'interface') { + insts = insts.filter((x) => x.device !== 'lo' && !x.device?.startsWith('@') && x.proto !== 'dhcpv6'); + } + + const index = insts.findIndex((x) => x[keyName] === keyVal); + if (index < 0) { + return ''; + } + + if (path.startsWith('Device.')) { + return `${path}${index + 1}`; + } + return `Device.${path}${index + 1}`; +} + +export function tr181IPInterface(uci) { + return tr181Path('IP.Interface.', 'network.interface', '.name', uci); +} + +export function prefixLengthToSubnetMask(prefixLength) { + if (!prefixLength) { + return ''; + } + const mask = 0xFFFFFFFF << (32 - prefixLength); + const subnetMask = [ + (mask >>> 24) & 0xFF, + (mask >>> 16) & 0xFF, + (mask >>> 8) & 0xFF, + mask & 0xFF, + ].join('.'); + + return subnetMask; +} + +export function fileExists(path) { + let exists = false; + if (path !== '') { + // eslint-disable-next-line no-unused-vars + const [obj, err] = os.stat(path); + exists = (err === 0); + } + return exists; +} + +export function fileExistsWithRegex(directory, regex) { + const [files, err] = os.readdir(directory); + + if (err) { + _log_warn(`fileExistsWithRegex(): Could not read directory: ${directory}`); + } + + for (let i = 0; i < files.length; i += 1) { + if (regex.test(files[i])) { + return true; + } + } + + return false; +} + +export function isIPv4Address(addr) { + return addr?.includes('.'); +} + +export function isIPv6Address(addr) { + return addr?.includes(':'); +} + +// find the pathname in LowerLayers +export function findPathInLowerlayer(path, inst, instKey) { + const lowerlayer = _dm_get(`${path}.LowerLayers`); + if (lowerlayer === '') { + return false; + } + + if (lowerlayer.includes(instKey)) { + if (lowerlayer.includes(inst)) { + return true; + } + } else { + const layers = lowerlayer.split(','); + if (layers.find((x) => findPathInLowerlayer(x, inst, instKey))) { + return true; + } + } + + return false; +} + +export function hex2a(hex) { + let i = 0; + let str = ''; + for (i = 0; i < hex.length; i += 2) { + str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + return str; +} + +export function capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +export function lowercaseFirstLetter(string) { + return string.charAt(0).toLowerCase() + string.slice(1); +} + +export function getIfnameOperState(ifname) { + if (!ifname) { + return 'Down'; + } + + const res = std.loadFile(`/sys/class/net/${ifname}/operstate`); + if (res) { + return capitalizeFirstLetter(res.trim()); + } + + return 'Down'; +} + +export function getIfnameState(ifname, name) { + if (!ifname) { + return ''; + } + + const res = std.loadFile(`/sys/class/net/${ifname}/${name}`); + return res?.trim(); +} + +export function strToHex(str) { + if (!str) { + return ''; + } + let hex = ''; + for (let i = 0; i < str.length; i += 1) { + hex += str.charCodeAt(i).toString(16); + } + return hex; +} + +// transform the object of following object: +// { +// 'SSIDtoVIDMapping.1.SSID': 'abc', +// 'SSIDtoVIDMapping.1.VID': 100, +// 'SSIDtoVIDMapping.2.SSID': 'xyz', +// 'SSIDtoVIDMapping.2.VID': 200, +// Enable: 'true' +// } +// into: +// { +// SSIDtoVIDMapping: [ { SSID: 'abc', VID: 100 }, { SSID: 'xyz', VID: 200 } ], +// Enable: 'true' +// } +export function transformInputObject(obj) { + const result = {}; + + Object.entries(obj).forEach(([key, value]) => { + const splitKey = key.split('.'); + + if (splitKey.length < 3) { + result[key] = value; // add invalid keys directly to the result + return; + } + + const mainKey = splitKey[0]; + const index = parseInt(splitKey[1], 10) - 1; + const prop = splitKey[2]; + + if (!result[mainKey]) { + result[mainKey] = []; + } + + if (!result[mainKey][index]) { + result[mainKey][index] = {}; + } + + result[mainKey][index][prop] = value; + }); + + return result; +} \ No newline at end of file diff --git a/dm-framework/datamodels/src/dm_node.c b/dm-framework/datamodels/src/dm_node.c new file mode 100644 index 000000000..61868edd2 --- /dev/null +++ b/dm-framework/datamodels/src/dm_node.c @@ -0,0 +1,1480 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "dm_node.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +const struct dm_parameter *dm_node_get_parameter(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (info) { + if (info->type == DM_NODE_PARAMETER) + return (const struct dm_parameter *)info; + } + return NULL; +} + +const struct dm_command *dm_node_get_command(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (info) { + if (info->type == DM_NODE_COMMAND) + return (const struct dm_command *)info; + } + return NULL; +} + +const struct dm_event *dm_node_get_event(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (info) { + if (info->type == DM_NODE_EVENT) + return (const struct dm_event *)info; + } + return NULL; +} + +const struct dm_object *dm_node_get_object(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + /* Note: In the future we may want to split this in two functions, one for objects, one for the first object from an object list + The current code exploits that a node list is implemnted by just concatenating the nodes in memory. + */ + if (info) { + if ((info->type == DM_NODE_OBJECT) || (info->type == DM_NODE_OBJECT_LIST)) + return (struct dm_object *)info; + } + return NULL; +} + +int dm_node_is_valid(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (info) { + return 1; + } + + return 0; +} + +int dm_node_is_parameter(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || info->type != DM_NODE_PARAMETER) { + return 0; + } + + return 1; +} + +int dm_node_is_command(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || info->type != DM_NODE_COMMAND) { + return 0; + } + + return 1; +} + +int dm_node_is_event(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || info->type != DM_NODE_EVENT) { + return 0; + } + + return 1; +} + +int dm_node_is_writable(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + if (info) + return (info->flag & FLAG_WRITABLE); + + return 0; +} + +int dm_node_is_object(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || info->type != DM_NODE_OBJECT) { + return 0; + } + + return 1; +} + +int dm_node_is_objectlist(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || info->type != DM_NODE_OBJECT_LIST) { + return 0; + } + + return 1; +} + +int dm_node_is_fixed_objects(dm_node_id_t id) +{ + const struct dm_object *obj = dm_node_get_object(id); + return obj->fixed_objects; +} + +const char *dm_node_object_keys(dm_node_id_t id) +{ + const struct dm_object *obj = dm_node_get_object(id); + return obj->key_param_names; +} + +int dm_node_is_counter(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || !(info->flag & FLAG_COUNTER)) { + return 0; + } + + return 1; +} + +dm_node_id_t dm_node_counter_id(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + + if (!info) + return INVALID_DM_NODE_ID; + return info->data.counter_object; +} + +int dm_node_is_confidential(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + if (!info || !(info->flag & FLAG_CONFIDENTIAL)) { + return 0; + } + + return 1; +} + +int dm_node_is_cwmp_only(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || !(info->flag & FLAG_CWMP_ONLY)) { + return 0; + } + + return 1; +} + +int dm_node_is_usp_only(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || !(info->flag & FLAG_USP_ONLY)) { + return 0; + } + + return 1; +} + +int dm_node_is_internal(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + + if (!info || !(info->flag & FLAG_INTERNAL)) { + return 0; + } + + return 1; +} + +int dm_node_is_text_type(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + + if (!info || info->data_type == DM_DATA_INT || info->data_type == DM_DATA_LONG || + info->data_type == DM_DATA_UINT || info->data_type == DM_DATA_ULONG || info->data_type == DM_DATA_BOOLEAN) { + return 0; + } else { + return 1; + } +} + +int dm_node_is_bool_type(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + if (info->data_type == DM_DATA_BOOLEAN) { + return 1; + } else { + return 0; + } +} + +int dm_node_is_ul_type(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + if (!info) { + return 0; + } + if (info->data_type == DM_DATA_ULONG) { + return 1; + } + return 0; +} + +int dm_node_is_unsigned_type(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + if (!info) { + return 0; + } + if (info->data_type == DM_DATA_ULONG || info->data_type == DM_DATA_UINT || info->data_type == DM_DATA_BOOLEAN) { + return 1; + } + return 0; +} + +int dm_node_max_data_size(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + + if (!info) + return 0; + return info->max; +} + +int dm_node_has_db(dm_node_id_t id) +{ + const struct dm_node_info *node = dm_node_get_info(id); + + if (node) + return node->table_name != NULL; + return 0; +} + +const char *dm_node_name(dm_node_id_t id) +{ + const struct dm_node_info *node = dm_node_get_info(id); + + if (node) + return node->name; + return NULL; +} + +int dm_node_index_cnt(dm_node_id_t id) +{ + const struct dm_node_info *node = dm_node_get_info(id); + + int cnt = 0; + + while (node != NULL) { + if (node->type == DM_NODE_OBJECT_LIST) { + cnt++; + } + node = node->parent; + } + + return cnt; +} + +int dm_node_is_index_complete(const dm_node_t *node) +{ + int cnt = dm_node_index_cnt(node->id); + if (cnt == node->cnt) { + return 1; + } else { + return 0; + } +} + +dm_node_id_t dm_node_id_parent(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + if (info != NULL && info->parent != NULL) { + return dm_node_get_info(id)->parent->node_id; + } else { + return INVALID_DM_NODE_ID; + } +} + +int dm_node_parent(const dm_node_t *node, dm_node_t *parent) +{ + if (NULL == parent) { + return -1; + } + + *parent = *node; + + parent->id = dm_node_id_parent(node->id); + + if (dm_node_is_objectlist(node->id)) { + parent->cnt--; + } + + return 0; +} + +int dm_node_is_param_list(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + + if (!info) { + return 0; + } + + return info->list; +} + +enum DM_DATA_TYPE dm_node_data_type(dm_node_id_t id) +{ + const struct dm_parameter *info = dm_node_get_parameter(id); + + if (!info) { + return DM_DATA_UNKNOWN; + } + + return info->data_type; +} + +/* +Helper function to retrieve the name of a specific node. +@param info[in] Pointer to the node we want to get the name of. + This e.g. points to the node for "Device" +@param name[out] Location where the node name is written +@param len[in/out] Pointer to amount of space still available in name +@param node[in] Node ID of the node for which we are getting the name. +#param index[in/out] Pointer to the next index item. +@return 0 in case of success, -1 in case of failure +*/ +static char *_dm_node2name(const struct dm_node_info *info, char *name, int *len, const dm_node_t *node, int *index) +{ + /* addlen will contain the return value of snprintf. + This are the number of characters that would be written if snprintf did not truncate + and if snprintf does not truncate it is the actual characters written. + */ + int addlen; + /* end_of_name_ptr will always point to the end of the name string as assembled until now */ + char *end_of_name_ptr; + + if (info->parent != NULL) { + /* recursive call to get the name of the parent; + the function returns the position of the end of the string + so after the function this is the place to write the name of this node + */ + end_of_name_ptr = _dm_node2name(info->parent, name, len, node, index); + /* add . for next level; there must be room for this and a terminating 0 byte */ + if (*len > 1) { + end_of_name_ptr[0] = '.'; + end_of_name_ptr++; + (*len)--; + /* if len now becomes 0 we terminate; make sure the trailing 0 byte is written */ + end_of_name_ptr[0] = 0; + } + if (*len == 0) { + return end_of_name_ptr; + } + } else { + /* We're at the top level node, let's get the ball rolling */ + end_of_name_ptr = name; + } + + /* add the name of the node to the string. What needs to be added depends on the type of the node */ + if (info->type == DM_NODE_OBJECT_LIST) { + if (*index < node->cnt && node->index[*index] > 0) { + addlen = snprintf(end_of_name_ptr, *len, "%s.%u", info->name, node->index[*index]); + } else { + addlen = snprintf(end_of_name_ptr, *len, "%s.{i}", info->name); + } + (*index)++; + } else /* OBJECT OR PARAMETER */ + { + addlen = snprintf(end_of_name_ptr, *len, "%s", info->name); + } + + /* finally advance the pointer and reduce len with the number of bytes written */ + if (addlen >= *len) /* snprintf truncated, so end of buffer, remaining length = 0 */ + { + end_of_name_ptr = end_of_name_ptr + *len - 1; + *len = 0; + } else { + end_of_name_ptr = end_of_name_ptr + addlen; + /* reduce length with # of bytes written */ + *len = *len - addlen; + } + return end_of_name_ptr; +} + +/* +Get the full name of a node. +@param node[in] Node ID whose name we want to retrieve +@param name[out] Location where the node name is written +@param name_len[in] Amount of space available in name (including 0 terminator) +@return 0 in case of success, -1 in case of failure (including buffer too small) +*/ +int dm_node2name(const dm_node_t *node, char *name, int name_len) +{ + int index = 0; + const struct dm_node_info *info; + + if ((node == NULL) || (name == NULL) || (name_len <= 0)) + return -1; + + info = dm_node_get_info(node->id); + + if (info != NULL) { + /* return result of next call is not relevant/useful here, it is only used in the recursion */ + (void)_dm_node2name(info, name, &name_len, node, &index); + if (name_len > 0) { + if (dm_node_is_objectlist(node->id) && !dm_node_is_index_complete(node)) { + strcat(name, "."); + } + return 0; /* success! */ + } + } + return -1; +} + +const struct dm_node_info *find_child_object(const struct dm_node_info *parent, const char *name) +{ + const struct dm_object *obj = (const struct dm_object *)parent; + int i; + + for (i = 0; i < obj->object_num; i++) { + const struct dm_node_info *node = obj->object_list[i]; + + if (strcmp(name, node->name) == 0) { + return node; + } + } + return NULL; +} + +static const struct dm_node_info *dm_node_find_child(const struct dm_node_info *parent, const char *name) +{ + const struct dm_object *obj = dm_node_get_object(parent->node_id); + int i; + + if (obj == NULL) + return NULL; + + for (i = 0; i < obj->command_num; i++) { + const struct dm_node_info *node = obj->command_list[i]; + if (strcmp(name, node->name) == 0) { + return node; + } + } + + for (i = 0; i < obj->event_num; i++) { + const struct dm_node_info *node = obj->event_list[i]; + if (strcmp(name, node->name) == 0) { + return node; + } + } + + for (i = 0; i < obj->param_num; i++) { + const struct dm_node_info *node = obj->param_list[i]; + + if (strcmp(name, node->name) == 0) { + return node; + } + } + return NULL; +} + +dm_node_id_t dm_node_get_child_id(dm_node_id_t id, const char *name) +{ + const struct dm_node_info *node = dm_node_get_info(id); + + if (node == NULL) + return INVALID_DM_NODE_ID; + + node = dm_node_find_child(node, name); + if (node == NULL) + return INVALID_DM_NODE_ID; + + return node->node_id; +} + +int dm_node_get_child(const dm_node_t *node, const char *name, dm_node_t *child) +{ + const struct dm_node_info *node_info = dm_node_get_info(node->id); + + if (node_info == NULL) + return -1; + + node_info = dm_node_find_child(node_info, name); + if (node_info == NULL) + return -1; + + *child = *node; + child->id = node_info->node_id; + + return 0; +} + +static int _dm_name2node(const struct dm_node_info *parent, const char *name, dm_node_t *node) +{ + char node_name[256]; // should be long enough for a node name + int i; + + if (*name == '\0') { + return 0; + } + + for (i = 0; name[i] != '\0' && name[i] != '.' && i < sizeof(node_name) - 1; i++) { + node_name[i] = name[i]; + } + + node_name[i] = '\0'; + + const struct dm_node_info *child; + + if (parent == NULL) { + child = dm_node_get_root(); + if (strcmp(child->name, node_name) != 0) { + return -1; + } + } else { + if (name[i] == '.') { + child = find_child_object(parent, node_name); + if (child == NULL) { + return -1; + } + } else { + child = dm_node_find_child(parent, node_name); + if (child) { + node->id = child->node_id; + return 0; + } else { + child = find_child_object(parent, node_name); + if (child) { + node->id = child->node_id; + return 0; + } + return -1; + } + + return 0; + } + } + + node->id = child->node_id; + + if (name[i] == '.') { + i++; + } else if (name[i] != '\0') { + return -1; + } + + if (name[i] >= '0' && name[i] <= '9') { + if (child->type != DM_NODE_OBJECT_LIST) { + return -1; + } + + char digit[16] = {0}; + int cnt; + + for (cnt = 0; name[i] != '.' && name[i] != '\0'; i++, cnt++) { + digit[cnt] = name[i]; + } + + node->index[node->cnt] = strtoul(digit, NULL, 10); + node->cnt++; + + if (name[i] == '.') + i++; + } else if (name[i] == '{') { + if (child->type != DM_NODE_OBJECT_LIST) { + return -1; + } + + while (name[i] != '}' && name[i] != '\0') + i++; + + if (name[i] == '\0') { + return -1; + } + + i++; + if (name[i] == '.') + i++; + + // should not increase 'cnt' if no index + } + + return _dm_name2node(child, &name[i], node); +} + +int dm_name2node(const struct dm_node_info *parent, const char *name, dm_node_t *node) +{ + memset(node, 0, sizeof(dm_node_t)); + if (name == NULL || name[0] == '\0') { + return -1; + } + + return _dm_name2node(parent, name, node); +} + +int dm_path2node(const char *path, dm_node_t *node) +{ + return dm_name2node(NULL, path, node); +} + +const struct dm_node_info *dm_node_get_root(void) +{ + return dm_node_get_info(0); +} + +int dm_node_equal(const dm_node_t *node1, const dm_node_t *node2) +{ + int i; // Initialised in for statement + + if (node1 == NULL || node2 == NULL || node1->id != node2->id || node1->cnt != node2->cnt) + return 0; + + for (i = 0; i < node1->cnt; i++) { + if (node1->index[i] != node2->index[i]) + return 0; + } + + return 1; +} + +int dm_node_i_parent(const dm_node_t *node, dm_node_t *parent) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + if (info == NULL) { + return -1; + } + + *parent = *node; + + const struct dm_node_info *p = info->parent; + + while (p != NULL) { + if (p->type == DM_NODE_OBJECT_LIST) { + break; + } + p = p->parent; + } + + if (p == NULL) { + return -1; + } + + parent->id = p->node_id; + if (info->type == DM_NODE_OBJECT_LIST && parent->cnt > dm_node_index_cnt(parent->id)) { + parent->cnt--; + } + + return 0; +} + +dm_node_id_t dm_node_i_parent_id(const dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + if (info == NULL) { + return -1; + } + + const struct dm_node_info *p = info->parent; + + while (p != NULL) { + if (p->type == DM_NODE_OBJECT_LIST) { + break; + } + p = p->parent; + } + + if (p == NULL) { + return INVALID_DM_NODE_ID; + } + + return p->node_id; +} + +int is_valid_integer(const char *str) +{ + int length = strlen(str); + int index = 0; + + // Check for optional sign at the beginning + if (str[index] == '+' || str[index] == '-') { + index++; + } + + // Check if there are no digits remaining after the optional sign + if (index >= length) { + return 0; + } + + for (; index < length; index++) { + if (!isdigit(str[index])) { + return 0; // Invalid integer: non-digit character + } + } + + return 1; +} + +static int verify_integer(const char *value, long min, long max) +{ + if (!is_valid_integer(value)) { + return 0; + } + + long num = atol(value); + if (num < min || num > max) { + return 0; + } + + // Valid + return 1; +} + +static int verify_boolean(const char *data) +{ + if (strcmp(data, "0") != 0 && strcmp(data, "1") != 0 && strcmp(data, "true") != 0 && strcmp(data, "false") != 0) { + return 0; + } + + // Valid + return 1; +} + +static int verify_string(const char *data, int min_len, int max_len) +{ + int len = strlen(data); + if (len > max_len || len < min_len) { + return 0; + } + + // Valid + return 1; +} + + +// TR181: IPv4 or IPv6 routing prefix in Classless Inter-Domain Routing (CIDR) notation [RFC4632]. +// This is specified as an IP address followed by an appended "/n" suffix, where n (the prefix size) is an integer +// in the range 0-32 (for IPv4) or 0-128 (for IPv6) that indicates the number of (leftmost) '1' bits of the routing +// prefix. +// IPv4 example: 192.168.1.0/24 +// IPv6 example: 2001:edff:fe6a:f76::/64 +// If the IP address part is unspecified or inapplicable, it MUST be an empty string unless otherwise specified by +// the parameter definition. In this case the IP prefix will be of the form "/n". +// If the entire IP prefix is unspecified or inapplicable, it MUST be an empty string unless otherwise specified by +// the parameter definition. +// @return 1 for valid (including empty string), and 0 for invalid +static int verify_ip_prefix(const char *data) +{ + if (data[0] == '\0') + return 1; + + dm_ip_prefix_t prefix; + + strlcpy(prefix, data, sizeof(prefix)); + + char *saved; + char *p = strtok_r(prefix, "/", &saved); + + if (p == NULL) { + return 0; + } + + // To handle prefix of form /n and not x.x.x.x/n + if (prefix[0] != '/') { + p = strtok_r(NULL, "/", &saved); + if (p == NULL) { + return 0; + } + } + + errno = 0; + int prefix_len = strtol(p, NULL, 10); + + if (errno == EINVAL) { + return 0; + } else if (errno == ERANGE) { + return 0; + } + + if (prefix_len < 0 || prefix_len > 32) { + return 0; + } + + // empty ip is valid + if (prefix[0] == '\0') + return 1; + + struct sockaddr_in sa; + int result = inet_pton(AF_INET, prefix, &(sa.sin_addr)); + + if (result != 0) { + return 0; + } + + return 1; +} + +static int verify_mac(const char *data) +{ + if (strlen(data) > 0) { + char buf[2]; + + if (sscanf(data, + "%*1[0-9a-fA-F]%*1[0-9a-fA-F]:" + "%*1[0-9a-fA-F]%*1[0-9a-fA-F]:" + "%*1[0-9a-fA-F]%*1[0-9a-fA-F]:" + "%*1[0-9a-fA-F]%*1[0-9a-fA-F]:" + "%*1[0-9a-fA-F]%*1[0-9a-fA-F]:" + "%*1[0-9a-fA-F]%1[0-9a-fA-F]%c", + buf, buf + 1) != 1) { + return 0; + } + } + + return 1; +} + +static int verify_datetime(const char *data) +{ + if (strlen(data) > 0) { + char suffix[32] = ""; + struct tm T; + + memset(&T, 0, sizeof(T)); + if (sscanf(data, "%d-%d-%dT%d:%d:%d%31s", + &T.tm_year, + &T.tm_mon, + &T.tm_mday, + &T.tm_hour, + &T.tm_min, + &T.tm_sec, + suffix) < 6) { + return 0; + } + } + + return 1; +} + +static int verify_enum(const char *data, const char **enum_values) +{ + int i = 0; + + while (enum_values[i]) { + if (strcmp(enum_values[i], data) == 0) { + return 1; + } + i++; + } + + // not found + return 0; +} + +static int verify_url(const char *data) +{ + (void)data; + // TODO, feel it needs quite lots of code. + return 1; +} + +static int verify_hexbinary(const char *data, int min_len, int max_len) +{ + int len = strlen(data); + + if (len % 2 != 0) + return 0; + + if (len/2 < min_len || len/2 > max_len) { + return 0; + } + + while (*data != '\0') { + if (!isxdigit((unsigned char)*data)) { + return 0; + } + data++; + } + + return 1; +} + +static int verify_ipv4(const char *str) +{ + int num, count = 0; + char *token; + + if (str == NULL || str[0] == '\0') { + return 1; + } + + char *temp_str = strdup(str); + token = strtok(temp_str, "."); + + while (token != NULL) { + count++; + + // Check if the token contains only digits + for (int i = 0; token[i]; i++) { + if (token[i] < '0' || token[i] > '9') { + free(temp_str); + return 0; + } + } + + // Convert the token to an integer and check if it's a valid octet + num = atoi(token); + if (num < 0 || num > 255) { + free(temp_str); + return 0; + } + + token = strtok(NULL, "."); + } + + free(temp_str); + + // Check if there are exactly 4 octets + return count == 4; +} + +static int is_hex_char(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static int verify_valid_ipv6(const char *str) +{ + if (str == NULL || str[0] == '\0') { + return 1; + } + + int num_colons = 0, num_doubles = 0; + int block_len = 0, block_count = 0; + + for (int i = 0; str[i]; i++) { + if (str[i] == ':') { + num_colons++; + + if (block_len > 0) { + block_count++; + } + + if (i > 0 && str[i - 1] == ':') { + num_doubles++; + if (num_doubles > 1) { + return 0; + } + } + + block_len = 0; + } else if (is_hex_char(str[i])) { + block_len++; + + if (block_len > 4) { + return 0; + } + } else { + return 0; + } + } + + if (block_len > 0) { + block_count++; + } + + if (num_doubles) { + return num_colons <= 7 && block_count <= 8; + } + + return num_colons == 7 && block_count == 8; +} + +static int verify_ip(const char *data) +{ + if (verify_ipv4(data) || verify_valid_ipv6(data)) { + return 1; + } + + return 0; +} + +static int verify_ipv6_prefix(const char *str) { + if (str == NULL || str[0] == '\0') { + return 1; + } + + char *slash = strchr(str, '/'); + if (slash == NULL) { + return 0; + } + + *slash = '\0'; // Temporarily replace the slash with a null terminator to separate the address and prefix length + + int valid_address = verify_valid_ipv6(str); + if (!valid_address) { + *slash = '/'; // Restore the slash character + return 0; + } + + int prefix_length = atoi(slash + 1); + *slash = '/'; // Restore the slash character + + return prefix_length >= 0 && prefix_length <= 128; +} + +static int is_base64_char(char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +static int is_base64(const char *str) { + size_t len = strlen(str); + size_t i; + + // Check for valid length + if (len % 4 != 0) { + return 0; + } + + // Check for valid characters and padding + size_t padding_count = 0; + for (i = 0; i < len; ++i) { + if (str[i] == '=') { + padding_count++; + // Ensure padding is at the end of the string + if (padding_count > 2 || (padding_count == 2 && i < len - 1) || (padding_count == 1 && i < len - 2)) { + return 0; + } + } else if (!is_base64_char(str[i])) { + return 0; + } + } + + return 1; +} + +static int verify_param_value(enum DM_DATA_TYPE data_type, const char *value, + long min, long max, const char **enum_values) +{ + switch (data_type) { + case DM_DATA_INT: + case DM_DATA_LONG: + case DM_DATA_UINT: + case DM_DATA_ULONG: + return verify_integer(value, min, max); + case DM_DATA_BOOLEAN: + return verify_boolean(value); + case DM_DATA_STRING: + return verify_string(value, min, max); + case DM_DATA_HEXBINARY: + return verify_hexbinary(value, min, max); + case DM_DATA_BASE64: + return is_base64(value); + case DM_DATA_IP: + return verify_ip(value); + case DM_DATA_IP_PREFIX: + return verify_ip_prefix(value); + case DM_DATA_MAC: + return verify_mac(value); + case DM_DATA_DATETIME: + return verify_datetime(value); + case DM_DATA_ENUM: + return verify_enum(value, enum_values); + case DM_DATA_URL: + return verify_url(value); + case DM_PATH_NAME: + // will validate in framework + return 1; + case DM_DATA_IPV4: + return verify_ipv4(value); + case DM_DATA_IPV6: + return verify_valid_ipv6(value); + case DM_DATA_IPV6_PREFIX: + return verify_ipv6_prefix(value); + default: + break; + } + + return 0; +} + +// return 1 if valid, otherwise 0 +static int verify_param_data(enum DM_DATA_TYPE data_type, const char *data, int list, + long min, long max, const char **enumValues) +{ + if (data == NULL) { + return 0; + } + + if (list) { + // Create a temporary buffer to store a copy of the string + char temp_str[strlen(data) + 1]; + strcpy(temp_str, data); + char *token = strtok(temp_str, ","); + while (token != NULL) { + int res = verify_param_value(data_type, token, min, max, enumValues); + if (!res) { + // failed to verify + return 0; + } + token = strtok(NULL, ","); // Get the next token + } + + return 1; + } else { + return verify_param_value(data_type, data, min, max, enumValues); + } +} + +int dm_node_verify_param_data(dm_node_id_t id, const char *data) +{ + const struct dm_parameter *param_node = dm_node_get_parameter(id); + if (param_node == NULL) + return 0; + + return verify_param_data(param_node->data_type, data, param_node->list, + param_node->min, param_node->max, param_node->data.enum_strings); +} + +int dm_node_is_ancestor(dm_node_id_t id, dm_node_id_t ancestor) +{ + const struct dm_node_info *node = dm_node_get_info(id); + if (node == NULL) + return 0; + + node = node->parent; + + while (node != NULL) { + if (node->node_id == ancestor) { + return 1; + } + node = node->parent; + } + + return 0; +} + +dm_index_t dm_node_last_index(const dm_node_t *node) +{ + if (node->cnt > 0) + return node->index[node->cnt - 1]; + return 0; +} + +int dm_node_has_path(dm_node_id_t id, dm_node_id_t path) +{ + const struct dm_parameter *param_node = dm_node_get_parameter(id); + + if (!param_node) { + return 0; + } + + if (param_node->data_type != DM_PATH_NAME) { + return 0; + } + + if (param_node->data.paths == NULL) { + // no limitation defined. + return 1; + } + + const dm_node_id_t *paths = param_node->data.paths; + + while (*paths != INVALID_DM_NODE_ID) { + if (*paths == path) + return 1; + + paths++; + } + + return 0; +} + +int dm_node_get_enum_index(dm_node_id_t id, const char *enum_str) +{ + const struct dm_parameter *param_node = dm_node_get_parameter(id); + + if (!param_node) + return -1; + + if (param_node->data_type != DM_DATA_ENUM) + return -1; + + int i = 0; + + while (param_node->data.enum_strings[i]) { + if (strcmp(param_node->data.enum_strings[i], enum_str) == 0) { + return i; + } + i++; + } + + return -1; +} + +const char *dm_node_get_enum_str(dm_node_id_t id, int index) +{ + const struct dm_parameter *param_node = dm_node_get_parameter(id); + + if (!param_node) + return NULL; + + if (param_node->data_type != DM_DATA_ENUM) + return NULL; + + return param_node->data.enum_strings[index]; +} + +int tr181_paths_remove(dm_path_t paths, const dm_node_t *node) +{ + dm_path_t tmp; + + strlcpy(tmp, paths, sizeof(dm_path_t)); + dm_path_t node_path; + + if (dm_node2name(node, node_path, sizeof(dm_path_t)) < 0) + return -1; + + paths[0] = '\0'; + + char *saveptr; + char *tok = strtok_r(tmp, ",", &saveptr); + + while (tok) { + if (strcmp(node_path, tok) != 0) { + if (paths[0] != '\0') // not first one + strlcat(paths, ",", sizeof(dm_path_t)); + + strlcat(paths, tok, sizeof(dm_path_t)); + } + + tok = strtok_r(NULL, ",", &saveptr); + } + + return 0; +} + +int tr181_paths_add(dm_path_t paths, const dm_node_t *node) +{ + dm_path_t node_path; + int ret = dm_node2name(node, node_path, sizeof(dm_path_t)); + + if (ret < 0 || strlen(paths) + strlen(node_path) + 1 >= sizeof(dm_path_t)) + return -1; + + if (paths[0] != '\0') + strlcat(paths, ",", sizeof(dm_path_t)); + + strlcat(paths, node_path, sizeof(dm_path_t)); + + return 0; +} + +int dm_node_find_order_param(const dm_node_t *obj_node, dm_node_t *order_node) +{ + int i; + const struct dm_object *obj = (const struct dm_object *)dm_node_get_info(obj_node->id); + if (obj == NULL) + return -1; + + for (i = 0; i < obj->param_num; i++) { + const struct dm_node_info *node = obj->param_list[i]; + + if (node->flag & FLAG_HAS_ORDER) { + *order_node = *obj_node; + order_node->id = node->node_id; + return 0; + } + } + + return -1; +} + +const char *dm_node_get_table_name(const struct dm_node_info *node) +{ + return node->table_name; +} + +// thread-unsafe +const char *dm_node_str(const dm_node_t *node) +{ + static dm_path_t name; + if (dm_node2name(node, name, sizeof(name)) != 0) { + return ""; + } + + return name; +} + +const char *dm_node_id_str(const dm_node_id_t id) +{ + dm_node_t node = {id}; + return dm_node_str(&node); +} + +int dm_node_compatible(const dm_node_t *node1, const dm_node_t *node2) +{ + if (node1 == NULL || node2 == NULL || node1->id != node2->id) { + return 0; + } + + // mininum one + int cnt = ((node1->cnt < node2->cnt) ? node1->cnt : node2->cnt); + int i; + + for (i = 0; i < cnt; i++) { + if (node1->index[i] != node2->index[i]) { + return 0; + } + } + + return 1; +} + +const char *get_param_xsd_type(enum DM_DATA_TYPE type) +{ + switch (type) { + case DM_DATA_INT: + return "xsd:int"; + case DM_DATA_LONG: + return "xsd:long"; + case DM_DATA_UINT: + return "xsd:unsignedInt"; + case DM_DATA_ULONG: + return "xsd:unsignedLong"; + case DM_DATA_BOOLEAN: + return "xsd:boolean"; + case DM_DATA_DATETIME: + return "xsd:dateTime"; + case DM_DATA_BASE64: + return "xsd:base64"; + case DM_DATA_HEXBINARY: + return "xsd:hexBinary"; + default: + break; + } + + return "xsd:string"; +} + +const char *dm_node_get_param_xsd_type(dm_node_id_t id) +{ + enum DM_DATA_TYPE t = dm_node_data_type(id); + return get_param_xsd_type(t); +} + +// compare the command argument pathname with index (index part will be skipped for comparision) +// ex, "result.{i}.abc" == "result.100.abc" +// return 0 if equal, otherwise 1 +int dm_node_compare_command_arg_name(const char* str1, const char* str2) { + while (*str1 && *str2) { + if (*str1 != *str2) + return 1; + + str1++; + str2++; + if (*str1 == '{' || *str2 == '{') { + // skip the "{}."" zone + str1++; + while (*str1 != '\0' && *str1 != '.') str1++; + if (*str1 == '.') str1++; + str2++; + while (*str2 != '\0' && *str2 != '.') str2++; + if (*str2 == '.') str2++; + } + } + + if (*str1 == '\0' && *str2 == '\0') + return 0; + return 1; +} + +const struct command_arg *dm_node_get_command_output_arg(const dm_node_id_t id, const char *arg_name) +{ + const struct dm_command *cmd = dm_node_get_command(id); + if (cmd == NULL) { + return NULL; + } + + for (int i = 0; i < cmd->outputs_cnt; i++) { + if (dm_node_compare_command_arg_name(cmd->outputs[i].name, arg_name) == 0) { + return &cmd->outputs[i]; + } + } + + return NULL; +} + +int dm_node_verify_command_input(dm_node_id_t id, const char *input_name, const char *input_value) +{ + const struct dm_command *cmd = dm_node_get_command(id); + if (cmd == NULL) { + return 0; + } + + for (int i = 0; i < cmd->inputs_cnt; i++) { + if (dm_node_compare_command_arg_name(cmd->inputs[i].name, input_name) == 0) { + return verify_param_data(cmd->inputs[i].type, input_value, cmd->inputs[i].list, + cmd->inputs[i].min, cmd->inputs[i].max, cmd->inputs[i].enum_values); + } + } + return 0; +} + +dm_node_id_t dm_node_get_apply_depends(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + if (info->type == DM_NODE_OBJECT_LIST) { + const struct dm_object *obj = (const struct dm_object*)info; + if (obj->map.map) { + // the depends id is only valid when there is no uci map is used, otherwise will be used as "extends" + return INVALID_DM_NODE_ID; + } + } + return info->depends_node_id; +} + +dm_node_id_t dm_node_get_extends(dm_node_id_t id) +{ + const struct dm_node_info *info = dm_node_get_info(id); + if (info->type == DM_NODE_OBJECT_LIST) { + const struct dm_object *obj = (const struct dm_object*)info; + if (obj->map.map) { + // depends_node_id will be the "extends" id when uci map is used. + return info->depends_node_id; + } + } + return INVALID_DM_NODE_ID; +} \ No newline at end of file diff --git a/dm-framework/datamodels/src/dm_node.h b/dm-framework/datamodels/src/dm_node.h new file mode 100644 index 000000000..77f723666 --- /dev/null +++ b/dm-framework/datamodels/src/dm_node.h @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DM_NODE_H +#define DM_NODE_H + +#include "dm_types.h" + +enum NODE_FLAG { + FLAG_NONE = 0x0, + FLAG_COUNTER = 0x40, + FLAG_HAS_MIN = 0x80, + FLAG_HAS_MAX = 0x100, + FLAG_HAS_ORDER = 0x400, + FLAG_WRITABLE = 0x800, + FLAG_CONFIDENTIAL = 0x1000, + FLAG_CWMP_ONLY = 0x4000, + FLAG_USP_ONLY = 0x8000, + FLAG_INTERNAL= 0x10000, +}; + +enum DM_UCI_MAP_TYPE { + DM_UCI_MAP_TYPE_NONE = 0, + DM_UCI_MAP_TYPE_SIMPLE = 0x01, // simple value 2 value + DM_UCI_MAP_TYPE_DISABLE = 0x02, // uci disable bool type + DM_UCI_MAP_TYPE_TABLE = 0x04, // a JSON object is used for mapping, {uci: dm} + DM_UCI_MAP_TYPE_INTERFACE = 0x08, // ip interface + DM_UCI_MAP_TYPE_JS = 0x10, // js code +}; + +struct dm_uci_map { + unsigned int type; + const char *map; + const char *key; +}; + +struct dm_node_info { + enum DM_NODE_TYPE type; + dm_node_id_t node_id; + const char *const name; + const struct dm_node_info *const parent; + const char *const table_name; // if NULL the value is not stored in the database + const char *pathname; + enum NODE_FLAG flag; + dm_node_id_t depends_node_id; +}; + +struct dm_parameter { + struct dm_node_info node; + enum DM_DATA_TYPE data_type; + + long min; + long max; + int list; + + union { + // for enum data type + const char **enum_strings; + + // for counter data type + dm_node_id_t counter_object; + + // for path data type + const dm_node_id_t *paths; + } data; + const char *set_on_create; + const char *js_val; + const char *const_val; + const char *default_val; + const char *default_uci_val; + struct dm_uci_map map; +}; + +struct command_arg { + const char *name; + enum DM_DATA_TYPE type; + int min; + int max; + int list; + const char **enum_values; + int mandatory; +}; + +struct dm_command { + struct dm_node_info node; + int async; + const struct command_arg *inputs; + int inputs_cnt; + const struct command_arg *outputs; + int outputs_cnt; +}; + +struct event_arg { + const char *name; + enum DM_DATA_TYPE type; +}; + +struct dm_event { + struct dm_node_info node; + const struct event_arg *args; + int args_cnt; + const char *ubus_event; +}; + +struct dm_object { + struct dm_node_info node; + int param_num; + const struct dm_node_info *const *const param_list; + int command_num; + const struct dm_node_info *const *const command_list; + int object_num; + const struct dm_node_info *const *const event_list; + int event_num; + const struct dm_node_info *const *const object_list; + int paths_refs_num; + const struct dm_node_info *const *const paths_refs_list; + const char *key_param_names; + struct dm_uci_map map; + const char *js_val; + int fixed_objects; +}; + +const struct dm_node_info *dm_node_get_root(void); +const struct dm_node_info *dm_node_get_info(dm_node_id_t id); + +/** + * This function will look up a parameter by ID + * @pre None + * @post valid dm_parameter pointer returned or NULL if error + * @param id The actual id for which we want to retrieve a + * pointer to the dm_parameter struct + * @return NULL in case id is out of range or if id does not point + * to a parameter node, a pointer to the dm_parameter struct otherwise + */ +const struct dm_parameter *dm_node_get_parameter(dm_node_id_t id); + +const struct dm_command *dm_node_get_command(dm_node_id_t id); +const struct dm_event *dm_node_get_event(dm_node_id_t id); +/** + * This function will look up a command by ID + * @pre None + * @post valid dm_command pointer returned or NULL if error + * @param id The actual id for which we want to retrieve a + * pointer to the dm_parameter struct + * @return NULL in case id is out of range or if id does not point + * to a command node, a pointer to the dm_command struct otherwise + */ +const struct dm_command *dm_node_get_command(dm_node_id_t id); +/** + * This function will look up an object by ID + * @pre None + * @post valid dm_object pointer returned or NULL if error + * @param id The actual id for which we want to retrieve a + * pointer to the dm_object struct + * @return NULL in case id is out of range or if id does not point + * to an object node, a pointer to the dm_object struct otherwise + * Note that if the id points to an object list a pointer to the + * first object is returned + */ +const struct dm_object *dm_node_get_object(dm_node_id_t id); + +int dm_node_is_valid(dm_node_id_t id); +int dm_node_is_parameter(dm_node_id_t id); +int dm_node_is_command(dm_node_id_t id); +int dm_node_is_event(dm_node_id_t id); +int dm_node_is_writable(dm_node_id_t id); +int dm_node_is_object(dm_node_id_t id); +int dm_node_is_objectlist(dm_node_id_t id); +int dm_node_is_counter(dm_node_id_t id); +int dm_node_is_text_type(dm_node_id_t id); +int dm_node_is_bool_type(dm_node_id_t id); +int dm_node_is_ul_type(dm_node_id_t id); +int dm_node_is_unsigned_type(dm_node_id_t id); +int dm_node_is_confidential(dm_node_id_t id); +int dm_node_is_cwmp_only(dm_node_id_t id); +int dm_node_is_usp_only(dm_node_id_t id); +int dm_node_is_internal(dm_node_id_t id); +dm_node_id_t dm_node_counter_id(dm_node_id_t id); +int dm_node_has_db(dm_node_id_t id); + +const char *dm_node_object_keys(dm_node_id_t id); +int dm_node_is_fixed_objects(dm_node_id_t id); +int dm_node_max_data_size(dm_node_id_t id); +int dm_node_param_mem_size(dm_node_id_t node_id); +const char *get_param_xsd_type(enum DM_DATA_TYPE type); +const char *dm_node_name(dm_node_id_t id); +dm_node_id_t dm_node_id_parent(dm_node_id_t id); +int dm_node_parent(const dm_node_t *node, dm_node_t *parent); +// get first multi-instance parent +int dm_node_i_parent(const dm_node_t *node, dm_node_t *parent); +dm_node_id_t dm_node_i_parent_id(const dm_node_id_t id); +int dm_node_index_cnt(dm_node_id_t id); +enum DM_DATA_TYPE dm_node_data_type(dm_node_id_t id); +dm_node_id_t dm_node_get_apply_depends(dm_node_id_t id); +dm_node_id_t dm_node_get_extends(dm_node_id_t id); +/* +Get the full name of a node. +@param node[in] Node ID whose name we want to retrieve +@param name[out] Location where the node name is written +@param name_len[in] Amount of space available in name +@return 0 in case of success, -1 in case of failure +*/ +int dm_node2name(const dm_node_t *node, char *name, int name_len); +int dm_name2node(const struct dm_node_info *parent, const char *name, dm_node_t *node); +int dm_path2node(const char *path, dm_node_t *node); + +int dm_node_verify_param_data(dm_node_id_t id, const char *data); + +// is parameter ancestor is the ancestor or parent of parameter id +int dm_node_is_ancestor(dm_node_id_t id, dm_node_id_t ancestor); +// is parameter data type a list (comman separated) +int dm_node_is_param_list(dm_node_id_t id); +dm_index_t dm_node_last_index(const dm_node_t *node); + +int dm_node_is_index_complete(const dm_node_t *node); + +/** Compare if two nodes are identical. + Two nodes are considered identical if their id, their index arrays and the cnt is the same + @param node1[in] first node for the comparison + @param node2[in] second node for the comparison + @return 1 if identical, 0 if not identical + */ +int dm_node_equal(const dm_node_t *node1, const dm_node_t *node2); +int dm_node_has_path(dm_node_id_t node_id, dm_node_id_t path); + +// return 0-(max-1) for valid string, -1 for unknown enum string +int dm_node_get_enum_index(dm_node_id_t id, const char *enum_str); +// return the enum string for valid index: 0-(max-1), otherwise return NULL +const char *dm_node_get_enum_str(dm_node_id_t id, int index); + +/** Remove path name from the path names separated with comma + @param paths path names separated with with comma + @param node node of path name that is to be removed + @return 0 if successful, -1 for failure +*/ +int tr181_paths_remove(dm_path_t paths, const dm_node_t *node); + +/** Append one path name to the path names separated with comma + @param paths path names separated with with comma + @param node node of path name that is to be appended + @return 0 if successful, -1 for failure +*/ +int tr181_paths_add(dm_path_t paths, const dm_node_t *node); + +// Find the "Order" parameter node from all its child nodes +int dm_node_find_order_param(const dm_node_t *obj_node, dm_node_t *order_node); + +/** Return the database table for a node + @param node The node for which the database table is returned + @return pointer to the name of the database table (NULL if the node is not in the database + */ +const char *dm_node_get_table_name(const struct dm_node_info *node); +// Get child node id by its name +dm_node_id_t dm_node_get_child_id(dm_node_id_t id, const char *name); +// get child node by name +int dm_node_get_child(const dm_node_t *node, const char *name, dm_node_t *child); +const char *dm_node_str(const dm_node_t *node); +const char *dm_node_id_str(const dm_node_id_t id); + +// compare if the nodes are compatible, this is if node1 and node2 have the same id and +// all indexes of node1 are a subset of node2 or vice versa +int dm_node_compatible(const dm_node_t *node1, const dm_node_t *node2); + +// get the string xsd type of the data type +const char *dm_node_get_param_xsd_type(dm_node_id_t id); + +/** Get the output argument type of the command node + @param [in] id node id + @param [in] arg_name argument name of the command + @return pointer of const string if successful, NULL for failure +*/ +const struct command_arg *dm_node_get_command_output_arg(dm_node_id_t id, const char *arg_name); + +// return 1 if verified, otherwise 0. +int dm_node_verify_command_input(dm_node_id_t id, const char *input_name, const char *input_value); + +// Compare the command argument pathname with index (index part will be skipped for comparision) +// ex, "result.{i}.abc" == "result.100.abc" +// return 0 if equal, otherwise 1 +int dm_node_compare_command_arg_name(const char* str1, const char* str2); +#endif diff --git a/dm-framework/datamodels/src/dm_types.h b/dm-framework/datamodels/src/dm_types.h new file mode 100644 index 000000000..cb1a2bb2e --- /dev/null +++ b/dm-framework/datamodels/src/dm_types.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DM_TYPES_H +#define DM_TYPES_H + +#define MAX_DM_NODE_DEPTH 8 +#define INVALID_DM_INDEX ((dm_index_t)0) +#define INVALID_DM_NODE_ID ((dm_node_id_t)-1) + +typedef unsigned int dm_node_id_t; +typedef unsigned int dm_index_t; + +typedef dm_index_t dm_index_path_t[MAX_DM_NODE_DEPTH]; +typedef struct +{ + dm_index_path_t index; +} node_index_path_t; + +typedef struct +{ + dm_node_id_t id; + dm_index_path_t index; + int cnt; +} dm_node_t; + +#define dm_init_node(id) \ + { \ + id, {0}, 0 \ + } + +typedef unsigned int dm_uint_t; +typedef int dm_int_t; +typedef int dm_bool_t; +typedef char dm_enum_t[128]; +typedef char dm_ip_t[64]; + +// IPv4 or IPv6 routing prefix in Classless Inter-Domain Routing (CIDR) notation [RFC4632]. +// This is specified as an IP address followed by an appended "/n" suffix, +// where n (the prefix size) is an integer in the range 0-32 (for IPv4) or 0-128 (for IPv6) +// that indicates the number of (leftmost) '1' bits of the routing prefix. +// If the IP address part is unspecified or inapplicable, it MUST be an empty string unless +// otherwise specified by the parameter definition. In this case the IP prefix will be of the form "/n". +// IPv4 example: 192.168.1.0/24 +// IPv6 example: 2001:edff:fe6a:f76::/64 +typedef char dm_ip_prefix_t[64]; + +typedef char dm_mac_t[20]; // 18 would suffice but there can be word access when getting a value; hence rounded up to 20 */ +typedef char dm_date_time_t[64]; +typedef char dm_url_t[260]; +typedef unsigned long dm_ulong_t; +typedef unsigned long long dm_ulonglong_t; +typedef char dm_path_t[1024]; +typedef char dm_domain_t[256]; + +#define dm_true 1 +#define dm_false 0 + +enum DM_DATA_TYPE { + DM_DATA_INT = 0, + DM_DATA_LONG, + DM_DATA_UINT, + DM_DATA_ULONG, + DM_DATA_BOOLEAN, + DM_DATA_STRING, + DM_DATA_HEXBINARY, + DM_DATA_BASE64, + DM_DATA_IP, + DM_DATA_IPV4, + DM_DATA_IPV6, + DM_DATA_IP_PREFIX, + DM_DATA_IPV6_PREFIX, + DM_DATA_MAC, + DM_DATA_DATETIME, + DM_DATA_ENUM, + DM_DATA_URL, + DM_PATH_NAME, + DM_DATA_UNKNOWN +}; + +enum DM_NODE_TYPE { + DM_NODE_PARAMETER = 0, + DM_NODE_OBJECT, + DM_NODE_OBJECT_LIST, + DM_NODE_COMMAND, + DM_NODE_EVENT +}; + +#endif diff --git a/dm-framework/datamodels/src/json2code.js b/dm-framework/datamodels/src/json2code.js new file mode 100755 index 000000000..21b297038 --- /dev/null +++ b/dm-framework/datamodels/src/json2code.js @@ -0,0 +1,1165 @@ +/* eslint-disable no-param-reassign */ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +/* eslint-disable no-await-in-loop */ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const fsSync = require('fs').promises; +const sqlite3 = require('better-sqlite3'); + +const folderPath = './dm-files'; + +function nodeName(pathname) { + const result = pathname.replace(/.{i}/g, '').replace(/\./g, '_'); + if (result.endsWith('_')) { + return result.slice(0, -1); + } + return result; +} + +const rootObj = { + name: 'Device.', + path: 'Device', + children: [], +}; + +const linkerNodeIds = []; +function createObject(parent, objName) { + const obj = parent.children.find((x) => x.name === objName); + if (obj) { + return obj; + } + + const newObj = { + name: objName, + children: [], + parent, + }; + + parent.children.push(newObj); + return newObj; +} + +function parseObject(objPath) { + let splitArray = []; + let buffer = ''; + for (let i = 0; i < objPath.length; i += 1) { + const char = objPath[i]; + buffer += char; + + if ((char === '.' && objPath[i + 1] !== '{') || (char === '}' && objPath[i - 1] === 'i' && objPath[i + 1] !== '.')) { + splitArray.push(buffer); + buffer = ''; + } + } + + if (buffer) { + splitArray.push(buffer); + } + + let parent = rootObj; + // remove the root "Device." + assert(splitArray[0] === 'Device.', `unsupported root object: ${splitArray[0]}`); + splitArray = splitArray.slice(1); + splitArray.forEach((x) => { + parent = createObject(parent, x); + }); + + return parent; +} + +async function dumpNodeDeclear(file, obj) { + await fsSync.appendFile(file, `static const struct dm_object _dm_obj_${obj.path};\n`); + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + await fsSync.appendFile(file, `static const struct dm_parameter _dm_param_${obj.path}_${p.name};\n`); + } + + const cmds = obj.children.filter((x) => x.name.endsWith('()')); + for (const c of cmds) { + await fsSync.appendFile(file, `static const struct dm_command _dm_cmd_${obj.path}_${c.name.slice(0, -2)};\n`); + } + + const events = obj.children.filter((x) => x.name.endsWith('!')); + for (const e of events) { + await fsSync.appendFile(file, `static const struct dm_event _dm_event_${obj.path}_${e.name.slice(0, -1)};\n`); + } + + const childObj = obj.children.filter((x) => x.name.endsWith('.')); + for (const o of childObj) { + await dumpNodeDeclear(file, o); + } +} + +function getDataType(type) { + const types = { + int: 'DM_DATA_INT', + long: 'DM_DATA_LONG', + unsignedInt: 'DM_DATA_UINT', + unsignedLong: 'DM_DATA_ULONG', + boolean: 'DM_DATA_BOOLEAN', + string: 'DM_DATA_STRING', + enum: 'DM_DATA_ENUM', + base64: 'DM_DATA_BASE64', + pathRef: 'DM_PATH_NAME', + URL: 'DM_DATA_URL', + dateTime: 'DM_DATA_DATETIME', + MACAddress: 'DM_DATA_MAC', + hexBinary: 'DM_DATA_HEXBINARY', + IPAddress: 'DM_DATA_IP', + IPv4Address: 'DM_DATA_IPV4', + IPv6Address: 'DM_DATA_IPV6', + IPPrefix: 'DM_DATA_IP_PREFIX', + IPv6Prefix: 'DM_DATA_IPV6_PREFIX', + StatsCounter32: 'DM_DATA_UINT', + }; + + assert(Object.keys(types).includes(type), `unsupported datatype: ${type}`); + return types[type]; +} + +function getDBTable(obj) { + if (obj.name.endsWith('}.')) { + return obj.path; + } + if (obj.parent === rootObj) { + return obj.path; + } + + return getDBTable(obj.parent); +} + +async function dumpParams(file, obj) { + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + if (p.flags && Array.isArray(p.flags) && p.flags.includes('linker')) { + linkerNodeIds.push(`DM_${obj.path.toUpperCase()}_${p.name.toUpperCase()}`); + } + if (p.enum) { + await fsSync.appendFile(file, `static const char * _dm_enum_${obj.path}_${p.name}[] = {\n`); + for (const val of p.enum) { + await fsSync.appendFile(file, `\t"${val.replace(/\\/g, '\\\\')}",\n`); + } + await fsSync.appendFile(file, '\tNULL\n};\n\n'); + } else if (p.pathRef) { + await fsSync.appendFile(file, `static const dm_node_id_t _dm_path_${obj.path}_${p.name}[] = {\n`); + for (const val of p.pathRef) { + await fsSync.appendFile(file, `\tDM_${nodeName(val).toUpperCase()},\n`); + } + await fsSync.appendFile(file, '\tINVALID_DM_NODE_ID\n};\n\n'); + } + + let min = 'INT_MIN'; let + max = 'INT_MAX'; + let type = p.dataType; + let isList = false; + + if (type.includes('long')) { + min = 'LONG_MIN'; + max = 'LONG_MAX'; + } + + if (!type.startsWith('int') && !type.startsWith('long')) { + // unsigned number or text + min = 0; + } + + if (type.endsWith('[]')) { + type = type.slice(0, -2); + isList = true; + } + + const idx = type.indexOf('('); + if (idx > 0) { + const range = type.slice(idx + 1, -1); + const minMax = range.split(':'); + if (minMax[0] !== '') { + min = minMax[0]; + } + if (minMax[1] !== '') { + max = minMax[1]; + } + type = type.slice(0, idx); + } + + let flag = ''; + if (p.access === 'readWrite') { + flag = 'FLAG_WRITABLE'; + } + + if (min !== 'INT_MIN') { + if (flag !== '') { + flag += ' | '; + } + flag += 'FLAG_HAS_MIN'; + } + + if (max !== 'INT_MAX') { + if (flag !== '') { + flag += ' | '; + } + flag += 'FLAG_HAS_MAX'; + } + + if (p.proto) { + if (flag !== '') { + flag += ' | '; + } + if (p.proto === 'cwmp') { + flag += 'FLAG_CWMP_ONLY'; + } else if (p.proto === 'none') { + flag += 'FLAG_INTERNAL'; + } else { + flag += 'FLAG_USP_ONLY'; + } + } + + if (p.name === 'Order' && min === '1') { + if (flag !== '') { + flag += ' | '; + } + flag += 'FLAG_HAS_ORDER'; + } + + if (p.name.endsWith('NumberOfEntries')) { + if (flag !== '') { + flag += ' | '; + } + flag += 'FLAG_COUNTER'; + } + + if (p.hidden) { + if (flag !== '') { + flag += ' | '; + } + flag += 'FLAG_CONFIDENTIAL'; + } + + if (flag === '') { + flag = 'FLAG_NONE'; + } + + let data = '{}'; + if (p.name.endsWith('NumberOfEntries')) { + const entryName = p.name.replace('NumberOfEntries', ''); + const id = `DM_${obj.path}_${entryName}`.toUpperCase(); + data = `{.counter_object = ${id}}`; + } else if (type === 'enum') { + if (p.enumerationRef) { + data = `{.enum_strings = _dm_enum_${nodeName(p.enumerationRef)}}`; + } else { + data = `{.enum_strings = _dm_enum_${obj.path}_${p.name}}`; + } + } else if (type === 'pathRef' && p.pathRef) { + data = `{.paths = _dm_path_${obj.path}_${p.name}}`; + } + + const dbTable = (p.db || p.access === 'readWrite' ? `"${getDBTable(obj)}"` : 'NULL'); + + const setOnCreate = p.set_on_create ? `"${p.set_on_create}"` : 'NULL'; + const jsVal = p['js-value'] !== undefined ? `"${p['js-value']}"` : 'NULL'; + const constVal = (p.const !== undefined ? `"${p.const}"` : 'NULL'); + const defaultVal = (p.default !== undefined ? `"${p.default}"` : 'NULL'); + const defaultUCIVal = (p['uci-default'] !== undefined ? `"${p['uci-default']}"` : 'NULL'); + let mapType = 'DM_UCI_MAP_TYPE_NONE'; + let map = 'NULL'; + if (p.uci) { + mapType = 'DM_UCI_MAP_TYPE_SIMPLE'; + map = `"${p.uci}"`; + } else if (p['uci-disable']) { + mapType = 'DM_UCI_MAP_TYPE_DISABLE'; + map = `"${p['uci-disable']}"`; + } else if (p['uci-map']) { + mapType = 'DM_UCI_MAP_TYPE_TABLE_MAP'; + map = `"${p['uci-map']}"`; + } else if (p['import-js']) { + mapType = 'DM_UCI_MAP_TYPE_JS'; + map = `"${p['import-js']}"`; + } else if (p['uci-interface']) { + mapType = 'DM_UCI_MAP_TYPE_INTERFACE'; + map = `"${p['uci-interface']}"`; + } + + let depends = 'INVALID_DM_NODE_ID'; + if (p.applyRedirect) { + depends = `DM_${nodeName(p.applyRedirect).toUpperCase()}` + } + + await fsSync.appendFile( + file, + `static const struct dm_parameter _dm_param_${obj.path}_${p.name} = {\n` + + ' .node = {\n' + + ' .type = DM_NODE_PARAMETER,\n' + + ` .node_id = DM_${obj.path.toUpperCase()}_${p.name.toUpperCase()},\n` + + ` .name = "${p.name}",\n` + + ` .parent = (const struct dm_node_info*)&_dm_obj_${obj.path},\n` + + ` .pathname = "${obj.path.replace(/_/g, '')}${p.name}",\n` + + ` .table_name = ${dbTable},\n` + + ` .flag = ${flag},\n` + + ` .depends_node_id = ${depends},\n` + + ' },\n' + + ` .data_type = ${getDataType(type)},\n` + + ` .list = ${isList ? '1' : '0'},\n` + + ` .min = ${min ?? 'INT_MIN'},\n` + + ` .max = ${max ?? 'INT_MAX'},\n` + + ` .data = ${data},\n` + + ` .set_on_create = ${setOnCreate},\n` + + ` .js_val = ${jsVal},\n` + + ` .const_val = ${constVal},\n` + + ` .default_val = ${defaultVal},\n` + + ` .default_uci_val = ${defaultUCIVal},\n` + + ' .map = {\n' + + ` .type = ${mapType},\n` + + ` .map = ${map}\n` + + ' },\n' + + '};\n\n' + ); + } +} + +async function dumpCmdArgs(file, obj, cmd, args, name) { + const varName = `_dm_${name}_${nodeName(obj.path)}_${cmd.name.slice(0, -2)}`; + for (const i of args) { + if (i.enum) { + await fsSync.appendFile(file, `static const char * ${varName}_${i.parameter.replace('.{i}.', '')}_enum[] = {\n`); + for (const val of i.enum) { + await fsSync.appendFile(file, `\t"${val}",\n`); + } + await fsSync.appendFile(file, '\tNULL\n};\n\n'); + } + } + + await fsSync.appendFile( + file, + `static const struct command_arg ${varName}[] = {\n`, + ); + for (const i of args) { + let min = 'INT_MIN'; let + max = 'INT_MAX'; + let type = i.dataType; + let isList = false; + + if (type.includes('long')) { + min = 'LONG_MIN'; + max = 'LONG_MAX'; + } + + if (!type.startsWith('int') && !type.startsWith('long')) { + // unsigned number or text + min = 0; + } + + if (type.endsWith('[]')) { + type = type.slice(0, -2); + isList = true; + } + + const idx = type.indexOf('('); + if (idx > 0) { + const range = type.slice(idx + 1, -1); + const minMax = range.split(':'); + if (minMax[0] !== '') { + min = minMax[0]; + } + if (minMax[1] !== '') { + max = minMax[1]; + } + type = type.slice(0, idx); + } + + const enumVals = i.enum ? `&${varName}_${i.parameter.replace('.{i}.', '')}_enum[0]` : 'NULL'; + + await fsSync.appendFile( + file, + ' {\n' + + ` .name = "${i.parameter}",\n` + + ` .mandatory = ${i.mandatory ? '1' : '0'},\n` + + ` .min = ${min},\n` + + ` .max = ${max},\n` + + ` .list = ${isList ? '1' : '0'},\n` + + ` .type = ${getDataType(type)},\n` + + ` .enum_values = ${enumVals},\n` + + ' },\n', + ); + } + + await fsSync.appendFile(file, '};\n\n'); + + return varName; +} + +async function dumpCmds(file, obj) { + const cmds = obj.children.filter((x) => x.name.endsWith('()')); + for (const cmd of cmds) { + let inputs = 'NULL'; + let output = 'NULL'; + + // if (p.enum) { + + // } + + if (cmd.input?.length) { + inputs = await dumpCmdArgs(file, obj, cmd, cmd.input, 'input'); + } + + let output_len = cmd.output ? cmd.output.length : 0; + if (cmd.output?.length) { + let params = cmd.output.filter((x) => !x.object); + cmd.output.filter((x) => x.object).forEach((obj) => { + obj.parameters?.forEach((p) => { + p.parameter = obj.object + p.parameter; + }); + if (obj.parameters) { + params = params.concat(obj.parameters); + } + }); + output_len = params.length; + output = await dumpCmdArgs(file, obj, cmd, params, 'output'); + } + + await fsSync.appendFile( + file, + `static const struct dm_command _dm_cmd_${obj.path}_${cmd.name.slice(0, -2)} = {\n` + + ' .node = {\n' + + ' .type = DM_NODE_COMMAND,\n' + + ` .node_id = DM_CMD_${obj.path.toUpperCase()}_${cmd.name.slice(0, -2).toUpperCase()},\n` + + ` .name = "${cmd.name}",\n` + + ` .parent = (const struct dm_node_info*)&_dm_obj_${obj.path},\n` + + ` .pathname = "${obj.path.replace(/_/g, '')}${cmd.name.slice(0, -2)}",\n` + + ' .table_name = NULL,\n' + + ' .flag = 0,\n' + + ' },\n' + + ` .inputs = ${inputs},\n` + + ` .inputs_cnt = ${cmd.input.length},\n` + + ` .outputs = ${output},\n` + + ` .outputs_cnt = ${output_len},\n` + + ` .async = ${cmd.async ? '1' : '0'},\n` + + '};\n\n', + ); + } +} + +async function dumpEvents(file, obj) { + const events = obj.children.filter((x) => x.name.endsWith('!')); + for (const ev of events) { + let args = 'NULL'; + if (ev.parameter?.length) { + args = `_dm_event_param_${nodeName(obj.path)}_${ev.name.slice(0, -1)}`; + await fsSync.appendFile( + file, + `static const struct event_arg ${args}[] = {\n`, + ); + for (const p of ev.parameter) { + await fsSync.appendFile( + file, + ' {\n' + + ` .name = "${p}",\n` + + ' .type = DM_DATA_STRING,\n' + + ' },\n', + ); + } + await fsSync.appendFile( + file, + '};\n\n', + ); + } + + const ubus_event = ev['ubus-event'] ? `"${ev['ubus-event']}"` : 'NULL'; + + await fsSync.appendFile( + file, + `static const struct dm_event _dm_event_${obj.path}_${ev.name.slice(0, -1)} = {\n` + + ' .node = {\n' + + ' .type = DM_NODE_EVENT,\n' + + ` .node_id = DM_EV_${obj.path.toUpperCase()}_${ev.name.slice(0, -1).toUpperCase()},\n` + + ` .name = "${ev.name}",\n` + + ` .parent = (const struct dm_node_info*)&_dm_obj_${obj.path},\n` + + ` .pathname = "${obj.path.replace(/_/g, '')}${ev.name.slice(0, -1)}",\n` + + ' .table_name = NULL,\n' + + ' .flag = 0,\n' + + ' },\n' + + ` .args = ${args},\n` + + ` .args_cnt = ${ev.parameter?.length ?? '0'},\n` + + ` .ubus_event = ${ubus_event},\n` + + '};\n\n', + ); + } +} + +async function dumpObj(file, obj) { + const params = obj.children.filter((x) => x.dataType); + const cmds = obj.children.filter((x) => x.name.endsWith('()')); + const objs = obj.children.filter((x) => x.name.endsWith('.')); + const events = obj.children.filter((x) => x.name.endsWith('!')); + + await fsSync.appendFile(file, '\n'); + + if (params.length > 0) { + await fsSync.appendFile( + file, + `static const struct dm_node_info* const _dm_param_array_${obj.path}[] = {\n`, + ); + for (const p of params) { + await fsSync.appendFile( + file, + ` (const struct dm_node_info*)&_dm_param_${obj.path}_${p.name},\n`, + ); + } + await fsSync.appendFile(file, '};\n\n'); + } + + if (cmds.length > 0) { + await fsSync.appendFile( + file, + `static const struct dm_node_info* const _dm_cmd_array_${obj.path}[] = {\n`, + ); + for (const cmd of cmds) { + await fsSync.appendFile( + file, + ` (const struct dm_node_info*)&_dm_cmd_${obj.path}_${cmd.name.slice(0, -2)},\n`, + ); + } + await fsSync.appendFile(file, '};\n\n'); + } + + if (events.length > 0) { + await fsSync.appendFile( + file, + `static const struct dm_node_info* const _dm_event_array_${obj.path}[] = {\n`, + ); + for (const e of events) { + await fsSync.appendFile( + file, + ` (const struct dm_node_info*)&_dm_event_${obj.path}_${e.name.slice(0, -1)},\n`, + ); + } + await fsSync.appendFile(file, '};\n\n'); + } + + if (objs.length > 0) { + await fsSync.appendFile( + file, + `static const struct dm_node_info* const _dm_obj_array_${obj.path}[] = {\n`, + ); + for (const o of objs) { + await fsSync.appendFile( + file, + ` (const struct dm_node_info*)&_dm_obj_${o.path},\n`, + ); + } + await fsSync.appendFile(file, '};\n\n'); + } + + if (obj.refParams) { + await fsSync.appendFile( + file, + `static const struct dm_node_info * const _dm_path_ref_array_${obj.path}[] = {\n`, + ); + for (const ref of obj.refParams) { + await fsSync.appendFile( + file, + ` (const struct dm_node_info*)&_dm_param_${nodeName(ref)},\n`, + ); + } + await fsSync.appendFile(file, '};\n\n'); + } + + const parent = obj.parent ? `&_dm_obj_${obj.parent.path}` : 'NULL'; + const name = obj.name.endsWith('}.') ? obj.name.slice(0, -5) : obj.name.slice(0, -1); + const paramList = params.length === 0 ? 'NULL' : `&_dm_param_array_${obj.path}[0]`; + const cmdList = cmds.length === 0 ? 'NULL' : `&_dm_cmd_array_${obj.path}[0]`; + const evList = events.length === 0 ? 'NULL' : `&_dm_event_array_${obj.path}[0]`; + const objList = objs.length === 0 ? 'NULL' : `&_dm_obj_array_${obj.path}[0]`; + + const multiInst = obj.name.endsWith('}.'); + const dbTable = (multiInst && (obj.db || obj.writable || obj.fixedObject)) ? `"${obj.path}"` : 'NULL'; + const pathRefList = obj.refParams ? `&_dm_path_ref_array_${obj.path}[0]` : 'NULL'; + const uniqueKeys = obj.uniqueKeys ? `"${obj.uniqueKeys}"` : 'NULL'; + const jsVal = obj['js-value'] !== undefined ? `"${obj['js-value']}"` : 'NULL'; + + let mapType = 'DM_UCI_MAP_TYPE_NONE'; + let map = 'NULL'; + let map_key = 'NULL'; + if (obj.uci) { + map = `"${obj.uci}"` + mapType = 'DM_UCI_MAP_TYPE_SIMPLE'; + } else if (obj['import-js']) { + map = `"${obj['import-js']}"` + mapType = 'DM_UCI_MAP_TYPE_JS'; + } + + if (obj['uci-key']) { + map_key = `"${obj['uci-key']}"` + } + + let flags = 'FLAG_NONE'; + if (obj.writable) { + const hasOrderParam = params.find((x) => x.name === 'Order'); + if (multiInst && hasOrderParam) { + flags = 'FLAG_WRITABLE | FLAG_HAS_ORDER'; + } else { + flags = 'FLAG_WRITABLE'; + } + } + + let depends = 'INVALID_DM_NODE_ID'; + if (obj.applyRedirect) { + depends = `DM_${nodeName(obj.applyRedirect).toUpperCase()}` + } + + if (obj.extends) { + depends = `DM_${nodeName(obj.extends).toUpperCase()}` + } + + await fsSync.appendFile( + file, + `static const struct dm_object _dm_obj_${obj.path} = {\n` + + ' .node = {\n' + + ` .type = ${multiInst ? 'DM_NODE_OBJECT_LIST' : 'DM_NODE_OBJECT'},\n` + + ` .node_id = DM_${obj.path.toUpperCase()},\n` + + ` .name = "${name}",\n` + + ` .parent = (const struct dm_node_info*)${parent},\n` + + ` .pathname = "${obj.path.replace(/_/g, '')}",\n` + + ` .table_name = ${dbTable},\n` + + ` .flag = ${flags},\n` + + ` .depends_node_id = ${depends},\n` + + ' },\n' + + ` .param_num = ${params.length},\n` + + ` .command_num = ${cmds.length},\n` + + ` .event_num = ${events.length},\n` + + ` .object_num = ${objs.length},\n` + + ` .param_list = ${paramList},\n` + + ` .command_list = ${cmdList},\n` + + ` .event_list = ${evList},\n` + + ` .object_list = ${objList},\n` + + ` .paths_refs_num = ${obj.refParams?.length ?? '0'},\n` + + ` .paths_refs_list = ${pathRefList},\n` + + ` .key_param_names = ${uniqueKeys},\n` + + ` .fixed_objects = ${obj.fixedObject ? '1' : '0'},\n` + + ' .map = {\n' + + ` .type = ${mapType},\n` + + ` .map = ${map},\n` + + ` .key = ${map_key}\n` + + ' },\n' + + ` .js_val = ${jsVal},\n` + + '};\n\n', + ); +} + +async function dumpNodeTree(file, obj) { + await dumpParams(file, obj); + await dumpCmds(file, obj); + await dumpEvents(file, obj); + await dumpObj(file, obj); + + const childObj = obj.children.filter((x) => x.name.endsWith('.')); + for (const o of childObj) { + await dumpNodeTree(file, o); + } +} + +async function dumpObjectNodes(file, obj) { + await fsSync.appendFile(file, `\t(const struct dm_node_info*)&_dm_obj_${obj.path},\n`); + + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + await fsSync.appendFile(file, `\t(const struct dm_node_info*)&_dm_param_${obj.path}_${p.name},\n`); + } + + const cmds = obj.children.filter((x) => x.name.endsWith('()')); + for (const c of cmds) { + await fsSync.appendFile(file, `\t(const struct dm_node_info*)&_dm_cmd_${obj.path}_${c.name.slice(0, -2)},\n`); + } + + const events = obj.children.filter((x) => x.name.endsWith('!')); + for (const e of events) { + await fsSync.appendFile(file, `\t(const struct dm_node_info*)&_dm_event_${obj.path}_${e.name.slice(0, -1)},\n`); + } + + const objs = obj.children.filter((x) => x.name.endsWith('.')); + for (const o of objs) { + await dumpObjectNodes(file, o); + } +} + +async function dumpNodeArray(file, obj) { + await fsSync.appendFile(file, 'static const struct dm_node_info * const _all_nodes_list[] = {\n'); + await dumpObjectNodes(file, obj); + await fsSync.appendFile(file, '};\n\n'); + await fsSync.appendFile( + file, + 'const struct dm_node_info * dm_node_get_info(dm_node_id_t node_id) {\n' + + ' if (node_id >= sizeof(_all_nodes_list)/sizeof(struct dm_node_info * )) {\n' + + ' return NULL;\n' + + ' }\n' + + ' return _all_nodes_list[node_id];\n' + + '}\n', + ); +} + +const head = ` +// WARNNING - Generated code by tool, DONT modify anything! +#include +#include +#include "dm_node.h" +#include "dm.h" + +`; + +const license = '/*\n' + + ' * Copyright (c) 2023 Genexis B.V. All rights reserved.\n' + + ' * This Software and its content are protected by the Dutch Copyright Act\n' + + ' * (\'Auteurswet\'). All and any copying and distribution of the software\n' + + ' * and its content without authorization by Genexis B.V. is\n' + + ' * prohibited. The prohibition includes every form of reproduction and\n' + + ' * distribution.\n' + + '*/\n\n'; + +let nodeID = 0; +async function exportNodeID(file, obj) { + await fsSync.appendFile(file, `#define DM_${obj.path.toUpperCase()} ${nodeID}\n`); + nodeID += 1; + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + await fsSync.appendFile(file, `#define DM_${obj.path.toUpperCase()}_${p.name.toUpperCase()} ${nodeID}\n`); + nodeID += 1; + } + + const cmds = obj.children.filter((x) => x.name.endsWith('()')); + for (const c of cmds) { + await fsSync.appendFile(file, `#define DM_CMD_${obj.path.toUpperCase()}_${c.name.slice(0, -2).toUpperCase()} ${nodeID}\n`); + nodeID += 1; + } + + const events = obj.children.filter((x) => x.name.endsWith('!')); + for (const e of events) { + await fsSync.appendFile(file, `#define DM_EV_${obj.path.toUpperCase()}_${e.name.slice(0, -1).toUpperCase()} ${nodeID}\n`); + nodeID += 1; + } + + const objs = obj.children.filter((x) => x.name.endsWith('.')); + for (const o of objs) { + await exportNodeID(file, o); + } +} + +async function exportJSNodeID(file, obj) { + await fsSync.appendFile(file, `export const DM_${obj.path.toUpperCase()} = ${nodeID};\n`); + nodeID += 1; + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + await fsSync.appendFile(file, `export const DM_${obj.path.toUpperCase()}_${p.name.toUpperCase()} = ${nodeID};\n`); + nodeID += 1; + } + + const cmds = obj.children.filter((x) => x.name.endsWith('()')); + for (const c of cmds) { + await fsSync.appendFile(file, `export const DM_CMD_${obj.path.toUpperCase()}_${c.name.slice(0, -2).toUpperCase()} = ${nodeID};\n`); + nodeID += 1; + } + + const events = obj.children.filter((x) => x.name.endsWith('!')); + for (const e of events) { + await fsSync.appendFile(file, `export const DM_EV_${obj.path.toUpperCase()}_${e.name.slice(0, -1).toUpperCase()} = ${nodeID};\n`); + nodeID += 1; + } + + const objs = obj.children.filter((x) => x.name.endsWith('.')); + for (const o of objs) { + await exportJSNodeID(file, o); + } +} + +async function exportDMHeaderFile() { + const headerFile = 'dm.h'; + nodeID = 0; + + await fsSync.writeFile(headerFile, license); + await fsSync.appendFile( + headerFile, + '#ifndef _DM_H\n' + + '#define _DM_H\n\n' + + '#include "dm_types.h"\n\n', + ); + + await exportNodeID(headerFile, rootObj); + + await fsSync.appendFile(headerFile, `\n#define NODE_ID_MAX ${nodeID}\n\n`); + if (linkerNodeIds.length > 0) { + await fsSync.appendFile(headerFile, 'extern const dm_node_id_t dm_linker_nodes[];\n\n'); + } + await fsSync.appendFile(headerFile, '#endif\n'); +} + +async function exportJSHeaderFile() { + const file = './dm-files/dm_consts.js'; + nodeID = 0; + + await fsSync.writeFile(file, license); + await fsSync.appendFile(file, '// WARNNING - Generated code by tool, DONT modify anything!\n\n'); + await fsSync.appendFile(file, 'export const DM_INVALID = -1;\n'); + + await exportJSNodeID(file, rootObj); +} + +async function exportDMCFile() { + const cfile = 'dm.c'; + await fsSync.writeFile(cfile, license); + await fsSync.appendFile(cfile, head); + await dumpNodeDeclear(cfile, rootObj); + await fsSync.appendFile(cfile, '\n'); + await dumpNodeTree(cfile, rootObj); + await dumpNodeArray(cfile, rootObj); + if (linkerNodeIds.length > 0) { + await fsSync.appendFile(cfile, '\nconst dm_node_id_t dm_linker_nodes[] = {\n'); + for (const id of linkerNodeIds) { + await fsSync.appendFile(cfile, ` ${id},\n`); + } + await fsSync.appendFile(cfile, ' INVALID_DM_NODE_ID\n};\n'); + } +} + +function isParamStringType(dt) { + if (dt.endsWith(']')) { + // list of value is stored in string. + return true; + } + + if (dt.startsWith('int') || dt.startsWith('long') || dt.startsWith('unsigned') + || dt.startsWith('boolean')) { + return false; + } + return true; +} + +function getParamDefaultVal(param) { + if (param.default) { + return `'${param.default}'`; + } + if (isParamStringType(param.dataType)) { + return "''"; + } + return '0'; +} + +function getParamDBSchema(table, obj, param) { + let schema = ''; + const dbName = `${obj.path}_${param.name}`.replace(`${table}_`, ''); + + if (isParamStringType(param.dataType)) { + schema = `"${dbName}" TEXT NOT NULL`; + } else { + schema = `"${dbName}" INTEGER NOT NULL`; + } + + schema += ` DEFAULT ${getParamDefaultVal(param)}`; + return schema; +} + +function getTableParameterCreateSQL(obj, table) { + let res = ''; + + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + if (p.db || p.access === 'readWrite') { + res += getParamDBSchema(table, obj, p); + res += ','; + } + } + + const childObj = obj.children.filter((x) => x.name.endsWith('.') && !x.name.endsWith('}.')); + childObj.forEach((o) => { + res += getTableParameterCreateSQL(o, table); + }); + + return res; +} + +function getTableNextIndexCreateSQL(obj, table) { + let res = ''; + const childObj = obj.children.filter((x) => x.name.endsWith('.')); + childObj.forEach((o) => { + if (o.name.endsWith('}.')) { + if (o.db || o.writable || o.fixedObject) { + const p = o.path.replace(`${table}_`, ''); + res += `"NextIndexOf${p}" INTEGER NOT NULL DEFAULT 1,`; + } + } else { + res += getTableNextIndexCreateSQL(o, table); + } + }); + + return res; +} + +function getTableInsertNameSQL(table, obj) { + let res = ''; + + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + if (p.db || p.access === 'readWrite') { + const dbName = `${obj.path}_${p.name}`.replace(`${table}_`, ''); + res += `"${dbName}",`; + } + } + + const childObj = obj.children.filter((x) => x.name.endsWith('.') && !x.name.endsWith('}.')); + childObj.forEach((o) => { + res += getTableInsertNameSQL(table, o); + }); + + return res; +} + +function getTableInsertValueSQL(table, obj) { + let res = ''; + + const params = obj.children.filter((x) => x.dataType); + for (const p of params) { + if (p.db || p.access === 'readWrite') { + res += `${getParamDefaultVal(p)},`; + } + } + + const childObj = obj.children.filter((x) => x.name.endsWith('.') && !x.name.endsWith('}.')); + childObj.forEach((o) => { + res += getTableInsertValueSQL(table, o); + }); + + return res; +} + +function createGlobalDBRecords(db, obj) { + let nameSql = getTableInsertNameSQL(obj.path, obj); + if (nameSql !== '') { + nameSql = nameSql.slice(0, -1); + let valueSql = getTableInsertValueSQL(obj.path, obj); + if (valueSql !== '') { + valueSql = valueSql.slice(0, -1); + } + db.exec(`INSERT INTO ${obj.path} (${nameSql}) VALUES (${valueSql});`); + } else { + db.exec(`INSERT INTO ${obj.path} DEFAULT VALUES;`); + } +} + +function createGlobalDBTable(db, obj) { + let sql = getTableParameterCreateSQL(obj, obj.path); + sql += getTableNextIndexCreateSQL(obj, obj.path); + if (sql === '') { + return; + } + + sql = sql.slice(0, -1); + // console.log('create table:', `CREATE TABLE ${obj.path} (${sql});`) + db.exec(`CREATE TABLE ${obj.path} (${sql});`); + createGlobalDBRecords(db, obj); +} + +function getPathObjectIndex(obj) { + let res = ''; + if (obj.parent) { + res += getPathObjectIndex(obj.parent); + } + + if (obj.name.endsWith('}.')) { + res += `"IndexOf${obj.name.slice(0, -5)}" INTEGER NOT NULL DEFAULT 0,`; + } + + return res; +} + +function getPathObjectKey(obj) { + let res = ''; + if (obj.parent) { + res += getPathObjectKey(obj.parent); + } + + if (obj.name.endsWith('}.')) { + if (res !== '') { + res += ','; + } + res += `"IndexOf${obj.name.slice(0, -5)}"`; + } + + return res; +} + +function createDynamicTables(obj, db) { + if (obj.name.endsWith('}.') && (obj.db || obj.writable || obj.fixedObject)) { + const sql = `CREATE TABLE ${obj.path} (${getPathObjectIndex(obj)}${getTableParameterCreateSQL(obj, obj.path)}` + + `${getTableNextIndexCreateSQL(obj, obj.path)}PRIMARY KEY(${getPathObjectKey(obj)}));`; + + db.exec(sql); + } + + const childObj = obj.children.filter((x) => x.name.endsWith('.')); + childObj.forEach((o) => { + createDynamicTables(o, db); + }); +} + +function createSqliteDB() { + const dbFile = 'default.db'; + if (fs.existsSync(dbFile)) { + fs.unlinkSync(dbFile); + } + + const db = new sqlite3(dbFile); + + const childObj = rootObj.children.filter((x) => x.name.endsWith('.')); + childObj.forEach((obj) => { + if (!obj.name.endsWith('}.')) { + // static object + createGlobalDBTable(db, obj); + } + + createDynamicTables(obj, db); + }); + + db.close(); +} + +function convertPathRef(objects) { + objects.forEach((obj) => { + obj.parameters?.forEach((param) => { + if (param.access === 'readWrite') { + param.pathRef?.forEach((ref) => { + const refObj = objects.find((x) => x.object === ref + '{i}.'); + if (!refObj) { + console.log(`Warning: missing object "${ref}" referenced by:"${obj.object}${param.name}"`); + } else if (refObj.refParams) { + refObj.refParams.push(obj.object + param.name); + } else { + refObj.refParams = [obj.object + param.name]; + } + }); + } + }); + }); +} + +const mergeObjects = (target, source) => { + Object.keys(source).forEach((key) => { + if (!['parameters', 'commands', 'events'].includes(key)) { + target[key] = source[key]; + } + }); + return target; +}; + +function readDirRecursively(directory, fileCallback, isRoot = true) { + const files = fs.readdirSync(directory); + + for (const file of files) { + const absolute = path.join(directory, file); + if (fs.statSync(absolute).isDirectory()) { + readDirRecursively(absolute, fileCallback, false); + } else if (!isRoot) { + fileCallback(absolute); + } + } +} + +function combineJSONFiles(directory) { + let result = []; + readDirRecursively(directory, (filePath) => { + if (path.extname(filePath) === '.json' && !path.basename(filePath).startsWith('.')) { + console.log('json file', filePath); + const data = JSON.parse(fs.readFileSync(filePath, 'utf8')); + if (Array.isArray(data)) { + result = result.concat(data); + } else { + result.push(data); + } + } + }); + + return result; +} + +function readDirRecursively(directory, fileCallback, isRoot = true) { + const files = fs.readdirSync(directory); + + for (const file of files) { + const absolute = path.join(directory, file); + if (fs.statSync(absolute).isDirectory()) { + if (file === 'node_modules') continue; + readDirRecursively(absolute, fileCallback, false); + } else if (!isRoot) { + fileCallback(absolute); + } + } +} + +function createExportFile(directory) { + let exportStatements = ''; + + readDirRecursively(directory, (filePath) => { + if (path.extname(filePath) === '.js') { + const relativePath = path.relative(directory, filePath).replace(/\\/g, '/'); + exportStatements += `export * from './${relativePath}';\n`; + } + }); + + const headerComment = `// This file is auto-generated. Do not modify it directly!\n\n`; + fs.writeFileSync(path.join(directory, 'exports.js'), headerComment + exportStatements); +} + +(async () => { + try { + const objects = combineJSONFiles('./dm-files'); + convertPathRef(objects); + objects.forEach((obj) => { + const o = parseObject(obj.object); + mergeObjects(o, obj); + o.path = nodeName(obj.object); + o.writable = (obj.access === 'readWrite'); + + o.db = obj.db; + if (obj.parameters) { + o.children = o.children.concat(obj.parameters); + } + + if (obj.access === 'readWrite' || obj.fixedObject || obj.db) { + // generate a key parameter for storing the uci key for the object instance + if (!o.children.find((x) => x.name === "_key")) { + o.children.push({ + name: "_key", + access: "readWrite", + proto: "none", + dataType: "string(:128)", + set_on_create: `${obj.object.slice(0, -5).split('.').slice(-1)[0].toLowerCase()}_`, + }); + } + } + + if (obj.commands) { + o.children = o.children.concat(obj.commands); + } + if (obj.events) { + o.children = o.children.concat(obj.events); + } + }); + + await exportDMCFile(); + await exportDMHeaderFile(); + await exportJSHeaderFile(); + createExportFile('./dm-files'); + createSqliteDB(); + console.log('done.'); + } catch (error) { + console.error(`Error while reading file: ${error}`); + console.log(error.stack); + } +})(); diff --git a/dm-framework/dm-agent/._Makefile b/dm-framework/dm-agent/._Makefile new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-agent/._bbfdm_service.json b/dm-framework/dm-agent/._bbfdm_service.json new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-agent/Makefile b/dm-framework/dm-agent/Makefile new file mode 100644 index 000000000..82a3c91c0 --- /dev/null +++ b/dm-framework/dm-agent/Makefile @@ -0,0 +1,65 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=bridgemngr +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) +PLATFORM_CONFIG:=$(TOPDIR)/.config + +include $(INCLUDE_DIR)/package.mk +include ../../bbfdm/bbfdm.mk + +define Package/$(PKG_NAME) + DEPENDS:=+dm-api +datamodels +libubox +libubus +ubus + CATEGORY:=Genexis + TITLE:=GeneOS agent + URL:=http://www.genexis.eu + PKG_LICENSE:=GENEXIS + PKG_LICENSE_URL:= +endef + +define Package/$(PKG_NAME)/description + This package contains GeneOS agent. +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) -rf ./src/* $(PKG_BUILD_DIR)/ +endef + +TARGET_CFLAGS += $(FPIC) -I$(PKG_BUILD_DIR) + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR)\ + PROJECT_ROOT="$(PKG_BUILD_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + EXTRA_CFLAGS="$(TARGET_CFLAGS)" \ + all +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_BIN) ./files/etc/init.d/bridging $(1)/etc/init.d/ + $(INSTALL_DATA) ./files/etc/config/bridging $(1)/etc/config/ + $(BBFDM_REGISTER_SERVICES) ./bbfdm_service.json $(1) $(PKG_NAME) + $(INSTALL_DIR) $(1)/lib/upgrade/keep.d + # $(INSTALL_BIN) ./files/etc/init.d/dm-agent $(1)/etc/init.d/dm-agent + $(INSTALL_BIN) ./files/lib/upgrade/keep.d/dm-agent $(1)/lib/upgrade/keep.d/dm-agent + $(INSTALL_BIN) $(PKG_BUILD_DIR)/dm-agent $(1)/usr/sbin +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/bridgemngr/bbfdm_service.json b/dm-framework/dm-agent/bbfdm_service.json similarity index 90% rename from bridgemngr/bbfdm_service.json rename to dm-framework/dm-agent/bbfdm_service.json index 54fe0825c..7b99db7d4 100644 --- a/bridgemngr/bbfdm_service.json +++ b/dm-framework/dm-agent/bbfdm_service.json @@ -2,6 +2,7 @@ "daemon": { "enable": "1", "service_name": "bridgemngr", + "dm-framework": true, "unified_daemon": false, "services": [ { diff --git a/bridgemngr/files/etc/config/bridging b/dm-framework/dm-agent/files/etc/config/bridging similarity index 100% rename from bridgemngr/files/etc/config/bridging rename to dm-framework/dm-agent/files/etc/config/bridging diff --git a/dm-framework/dm-agent/files/etc/config/dm_agent b/dm-framework/dm-agent/files/etc/config/dm_agent new file mode 100644 index 000000000..e83f0a56a --- /dev/null +++ b/dm-framework/dm-agent/files/etc/config/dm_agent @@ -0,0 +1,2 @@ +config dm_agent 'agent' + option loglevel '1' diff --git a/bridgemngr/files/etc/init.d/bridging b/dm-framework/dm-agent/files/etc/init.d/bridging similarity index 100% rename from bridgemngr/files/etc/init.d/bridging rename to dm-framework/dm-agent/files/etc/init.d/bridging diff --git a/dm-framework/dm-agent/files/etc/init.d/dm-agent b/dm-framework/dm-agent/files/etc/init.d/dm-agent new file mode 100644 index 000000000..60ada6ee6 --- /dev/null +++ b/dm-framework/dm-agent/files/etc/init.d/dm-agent @@ -0,0 +1,37 @@ +#!/bin/sh /etc/rc.common + +START=99 +STOP=10 + +USE_PROCD=1 +PROG=/usr/sbin/dm-agent + +start_service() { + logger -t dmapi "waiting network ubus" + ubus -t 30 wait_for network.device uci + [ "$?" -eq 0 ] && { + logger -t dmapi "waiting for network ubus done" + } || { + logger -t dmapi "failed to wait for network ubus" + } + + [ -f "/etc/config/wireless" ] && { + logger -t dmapi "waiting wifi ubus" + ubus -t 30 wait_for wifi + [ "$?" -eq 0 ] && { + logger -t dmapi "waiting for wifi ubus done" + } || { + logger -t dmapi "failed to wait for wifi ubus" + } + } + + procd_open_instance dm-agent + procd_set_param command ${PROG} + procd_set_param respawn + procd_close_instance +} + +reload_service() { + stop + start +} diff --git a/dm-framework/dm-agent/files/lib/upgrade/keep.d/dm-agent b/dm-framework/dm-agent/files/lib/upgrade/keep.d/dm-agent new file mode 100644 index 000000000..fcd62f062 --- /dev/null +++ b/dm-framework/dm-agent/files/lib/upgrade/keep.d/dm-agent @@ -0,0 +1 @@ +/etc/dm.db diff --git a/dm-framework/dm-agent/src/._dm_agent.c b/dm-framework/dm-agent/src/._dm_agent.c new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-agent/src/Makefile b/dm-framework/dm-agent/src/Makefile new file mode 100644 index 000000000..5cdd13233 --- /dev/null +++ b/dm-framework/dm-agent/src/Makefile @@ -0,0 +1,37 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + +PROG = dm-agent + +SRCS = main.c dm_agent.c + +OBJS = $(SRCS:.c=.o) +DEPS = $(SRCS:.c=.d) + +CC = $(CROSS_COMPILE)gcc +STRIP = $(CROSS_COMPILE)strip + +CFLAGS = -Wall -Werror $(EXTRA_CFLAGS) +CFLAGS += -MMD -MP -std=gnu99 + +LDFLAGS += -ldm -ldmapi -lubus -lubox -ljson-c -lblobmsg_json + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $^ $(LDFLAGS) -o $@ + +%.o: %.c + $(CC) -c $(CFLAGS) $^ -o $@ + +clean: + rm -f $(PROG) *.o core $(DEPS) + +-include $(DEPS) diff --git a/dm-framework/dm-agent/src/dm_agent.c b/dm-framework/dm-agent/src/dm_agent.c new file mode 100644 index 000000000..92d2cd88a --- /dev/null +++ b/dm-framework/dm-agent/src/dm_agent.c @@ -0,0 +1,1752 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dm_api.h" +#include "dm_log.h" + +#define METHOD_NAME "bbfdm.bridgemngr" +#define BBF_EVENT "bbfdm.event" +#define BBF_ASYNC_COMPLETE_EVENT "bbfdm.dmf.async_complete" +#define DEFAULT_TRANS_TIMEOUT 300*1000 + +struct agent_context { + struct ubus_context ubus_ctx; + struct list_head async_req_list; + unsigned int last_async_id; + unsigned int trans_id; + struct uloop_timeout trans_timer; +}; + +struct async_operate_request { + struct ubus_request_data req; + unsigned int async_id; + struct list_head list; +}; + +struct dm_ubus_event_handler { + struct ubus_event_handler uhandler; + dm_node_id_t node_id; +}; + +enum { + GET_VALUE = 0, + GET_NAME, + GET_OBJ_NAME, + GET_INSTANCE +}; + +enum instance_mode { + INSTANCE_MODE_NUMBER, + INSTANCE_MODE_ALIAS +}; + +enum dm_proto_type { + DM_PROTO_CWMP, + DM_PROTO_USP, + DM_PROTO_BOTH +}; + +typedef struct { + enum dm_proto_type dm_type; + enum instance_mode instance_mode; + bool is_raw; + int trans_id; + bool command; + bool event; + bool parameter; + int maxdepth; +} bbf_options_t; + +static struct agent_context agent_ctx; + +// Helper function to check if a parameter is a key parameter +static int is_key_parameter(const dm_node_t *node) +{ + if (!dm_node_is_parameter(node->id)) { + return 0; + } + + // Get the parent object node + dm_node_id_t parent_id = dm_node_i_parent_id(node->id); + if (parent_id == 0) { + return 0; + } + + // Check if parent is an object list (multi-instance object) + if (!dm_node_is_objectlist(parent_id)) { + return 0; + } + + // Get the keys for the parent object + const char *keys = dm_node_object_keys(parent_id); + if (!keys) { + return 0; + } + + // Get the parameter name from the node path + dm_path_t node_path; + dm_node2name(node, node_path, sizeof(dm_path_t)); + + // Extract just the parameter name (last part after the last dot) + char *param_name = strrchr(node_path, '.'); + if (!param_name) { + return 0; + } + param_name++; // Skip the dot + + // Check if the parameter name is in the keys list + char *keys_str = strdup(keys); + char *key = strtok(keys_str, ","); + while (key) { + if (strcmp(key, param_name) == 0) { + free(keys_str); + return 1; + } + key = strtok(NULL, ","); + } + free(keys_str); + + return 0; +} + +// return 1 if the proto is allowed for the node, otherwise return 0 +static int check_dm_proto(int proto, dm_node_id_t node_id) +{ + if (dm_node_is_internal(node_id)) { + return 0; + } + + if (proto == DM_PROTO_BOTH) { + return 1; + } + + if (proto == DM_PROTO_CWMP) { + if (dm_node_is_command(node_id)) { + return 0; + } + + if (dm_node_is_usp_only(node_id)) { + return 0; + } + } else { + // USP + if (dm_node_is_cwmp_only(node_id)) { + return 0; + } + } + + return 1; +} + +static int get_dm_nodes(const bbf_options_t *opts, const dm_node_t *node, + struct blob_buf *bb, int get_type, int depth) +{ + if (opts->maxdepth > 0 && depth > opts->maxdepth) { + return 0; + } + + if (!check_dm_proto(opts->dm_type, node->id)) { + return 0; + } + + if (dm_node_is_command(node->id) || dm_node_is_event(node->id)) { + return 0; + } + + if (dm_node_is_parameter(node->id)) { + if (get_type == GET_INSTANCE) + return 0; + + void *table = blobmsg_open_table(bb, NULL); + + dm_path_t node_path; + dm_node2name(node, node_path, sizeof(dm_path_t)); + blobmsg_add_string(bb, "path", (char *)node_path); + if (get_type != GET_OBJ_NAME) { + char *value = NULL; + if (dmapi_param_get(node, &value) < 0 || value == NULL) { + blobmsg_add_string(bb, "data", ""); + } else { + blobmsg_add_string(bb, "data", value); + free(value); + } + } else { + blobmsg_add_string(bb, "data", dm_node_is_writable(node->id) ? "1" : "0"); + } + + blobmsg_add_string(bb, "type", dm_node_get_param_xsd_type(node->id)); + + // Add flags array for key parameters + if (is_key_parameter(node)) { + void *flags_array = blobmsg_open_array(bb, "flags"); + blobmsg_add_string(bb, NULL, "Unique"); + blobmsg_close_array(bb, flags_array); + } + + blobmsg_close_table(bb, table); + } else { + const struct dm_object *obj = dm_node_get_object(node->id); + if (obj == NULL) + return 0; + + if (get_type == GET_NAME || get_type == GET_OBJ_NAME) { + void *table = blobmsg_open_table(bb, NULL); + dm_path_t node_path; + dm_node2name(node, node_path, sizeof(dm_path_t)); + char last = node_path[strlen(node_path) - 1]; + if (last == '.') { + // remove the {i} + node_path[strlen(node_path) - 4] = '\0'; + } else { + strcat(node_path, "."); + } + + blobmsg_add_string(bb, "path", (char *)node_path); + if (get_type == GET_OBJ_NAME) { + blobmsg_add_string(bb, "data", dm_node_is_writable(node->id) ? "1" : "0"); + } else { + blobmsg_add_string(bb, "data", "0"); + } + + blobmsg_add_string(bb, "type", "xsd:object"); + blobmsg_close_table(bb, table); + } + + if (dm_node_is_index_complete(node)) { + if (get_type == GET_INSTANCE && dm_node_is_objectlist(node->id)) { + void *table = blobmsg_open_table(bb, NULL); + dm_path_t node_path; + dm_node2name(node, node_path, sizeof(dm_path_t)); + blobmsg_add_string(bb, "path", (char *)node_path); + blobmsg_close_table(bb, table); + } + + if (get_type != GET_INSTANCE) { + for (int i = 0; i < obj->param_num; i++) { + dm_node_t child = *node; + const struct dm_node_info *info = obj->param_list[i]; + child.id = info->node_id; + get_dm_nodes(opts, &child, bb, get_type, depth+1); + } + } + + for (int i = 0; i < obj->object_num; i++) { + dm_node_t child = *node; + const struct dm_node_info *info = obj->object_list[i]; + child.id = info->node_id; + get_dm_nodes(opts, &child, bb, get_type, depth + 1); + } + } else { + if (!dm_node_is_objectlist(node->id)) { + dmlog_error("unexpected path"); + return -1; + } + dm_nodelist_h list = dm_nodelist_get(node); + const dm_node_t *n = NULL; + nodelist_for_each_node(n, list) + { + get_dm_nodes(opts, n, bb, get_type, depth); + } + dm_nodelist_free(list); + } + } + + return 0; +} + +int get_supported_dm(const bbf_options_t *opts, dm_node_id_t id, struct blob_buf *bb) +{ + if (!check_dm_proto(opts->dm_type, id)) { + return 0; + } + + if (!opts->command && dm_node_is_command(id)) { + return 0; + } + + if (!opts->parameter && dm_node_is_parameter(id)) { + return 0; + } + + if (!opts->event && dm_node_is_event(id)) { + return 0; + } + + if (dm_node_is_parameter(id) || dm_node_is_objectlist(id) + || dm_node_is_command(id) || dm_node_is_event(id)) { + void *table = blobmsg_open_table(bb, NULL); + dm_path_t node_path; + dm_node_t node = {id}; + dm_node2name(&node, node_path, sizeof(dm_path_t)); + blobmsg_add_string(bb, "path", (char *)node_path); + if (!dm_node_is_command(id) && !dm_node_is_event(id)) { + blobmsg_add_string(bb, "data", dm_node_is_writable(id) ? "1" : "0"); + } + const char *type; + if (dm_node_is_objectlist(id)) { + type = "xsd:object"; + } else if (dm_node_is_command(id)) { + type = "xsd:command"; + } else if (dm_node_is_event(id)) { + type = "xsd:event"; + } else { + type = dm_node_get_param_xsd_type(id); + } + blobmsg_add_string(bb, "type", type); + + // Add flags array for key parameters + if (dm_node_is_parameter(id) && is_key_parameter(&node)) { + void *flags_array = blobmsg_open_array(bb, "flags"); + blobmsg_add_string(bb, NULL, "Unique"); + blobmsg_close_array(bb, flags_array); + } + // if (dm_node_is_objectlist(id)) { + // const char *keys = dm_node_object_keys(id); + // if (keys) { + // void *array = blobmsg_open_array(bb, "input"); + // char *keys_str = strdup(keys); + // char *key = strtok(keys_str, ","); + // void *tbl = blobmsg_open_table(bb, NULL); + // blobmsg_add_string(bb, "path", key); + // blobmsg_close_table(bb, tbl); + // while ((key = strtok(NULL, ","))) { + // tbl = blobmsg_open_table(bb, NULL); + // blobmsg_add_string(bb, "path", key); + // blobmsg_close_table(bb, tbl); + // } + // blobmsg_close_table(bb, array); + // free(keys_str); + // } + // } + if (dm_node_is_command(id)) { + const struct dm_command *cmd = dm_node_get_command(id); + blobmsg_add_string(bb, "data", cmd->async ? "async" : "sync"); + if (cmd->inputs_cnt > 0) { + void *array = blobmsg_open_array(bb, "input"); + for (int i = 0; i < cmd->inputs_cnt; i++) { + void *tbl = blobmsg_open_table(bb, NULL); + blobmsg_add_string(bb, "path", cmd->inputs[i].name); + blobmsg_close_table(bb, tbl); + } + blobmsg_close_table(bb, array); + } + if (cmd->outputs_cnt > 0) { + void *array = blobmsg_open_array(bb, "output"); + for (int i = 0; i < cmd->outputs_cnt; i++) { + void *tbl = blobmsg_open_table(bb, NULL); + blobmsg_add_string(bb, "path", cmd->outputs[i].name); + blobmsg_close_table(bb, tbl); + } + blobmsg_close_table(bb, array); + } + } else if (dm_node_is_event(id)) { + const struct dm_event *evt = dm_node_get_event(id); + if (evt->args_cnt > 0) { + void *array = blobmsg_open_array(bb, "input"); + for (int i = 0; i < evt->args_cnt; i++) { + void *tbl = blobmsg_open_table(bb, NULL); + blobmsg_add_string(bb, "path", evt->args[i].name); + blobmsg_close_table(bb, tbl); + } + blobmsg_close_table(bb, array); + } + } + blobmsg_close_table(bb, table); + } + + if (dm_node_is_object(id) || dm_node_is_objectlist(id)) { + const struct dm_object *obj = dm_node_get_object(id); + int i; + if (obj == NULL) + return -1; + + for (i = 0; i < obj->param_num; i++) { + const struct dm_node_info *info = obj->param_list[i]; + get_supported_dm(opts, info->node_id, bb); + } + + for (i = 0; i < obj->object_num; i++) { + const struct dm_node_info *info = obj->object_list[i]; + get_supported_dm(opts, info->node_id, bb); + } + + for (i = 0; i < obj->command_num; i++) { + const struct dm_node_info *info = obj->command_list[i]; + get_supported_dm(opts, info->node_id, bb); + } + + for (i = 0; i < obj->event_num; i++) { + const struct dm_node_info *info = obj->event_list[i]; + get_supported_dm(opts, info->node_id, bb); + } + } + + return 0; +} + +enum { + DM_SCHEMA_PATH, + DM_SCHEMA_OPTIONAL, + __DM_SCHEMA_MAX +}; + +static const struct blobmsg_policy dm_schema_policy[] = { + [DM_SCHEMA_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [DM_SCHEMA_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE}, +}; + +static int get_proto_type(struct blob_attr *proto) +{ + int type = DM_PROTO_BOTH; + + if (proto) { + const char *val = blobmsg_get_string(proto); + if (strcmp("cwmp", val) == 0) + type = DM_PROTO_CWMP; + else if (strcmp("usp", val) == 0) + type = DM_PROTO_USP; + else + type = DM_PROTO_BOTH; + } + + return type; +} + +static int get_instance_mode(struct blob_attr *ins) +{ + int instance_mode = INSTANCE_MODE_NUMBER; + + if (ins) + instance_mode = blobmsg_get_u32(ins); + + if (instance_mode > INSTANCE_MODE_ALIAS) + instance_mode = INSTANCE_MODE_NUMBER; + + return instance_mode; +} + +static void get_bbf_options(bbf_options_t *options, struct blob_attr *msg) +{ + struct blob_attr *attr; + size_t rem; + + // set default + options->dm_type = DM_PROTO_BOTH; + options->instance_mode = INSTANCE_MODE_NUMBER; + options->is_raw = true; + options->command = true; + options->event = true; + options->parameter = true; + + blobmsg_for_each_attr(attr, msg, rem) { + if (!blobmsg_name(attr)[0]) + continue; + + if (strcmp(blobmsg_name(attr), "proto") == 0) + options->dm_type = get_proto_type(attr); + + if (strcmp(blobmsg_name(attr), "instance_mode") == 0) + options->instance_mode = get_instance_mode(attr); + + if (strcmp(blobmsg_name(attr), "transaction_id") == 0) + options->trans_id = blobmsg_get_u32(attr); + + if (strcmp(blobmsg_name(attr), "format") == 0) + options->is_raw = strcmp(blobmsg_get_string(attr), "raw") == 0 ? true : false; + + if (strcmp(blobmsg_name(attr), "maxdepth") == 0) + options->maxdepth = blobmsg_get_u32(attr); + + if (strcmp(blobmsg_name(attr), "commands") == 0) + options->command = blobmsg_get_bool(attr); + + if (strcmp(blobmsg_name(attr), "events") == 0) + options->event = blobmsg_get_bool(attr); + + if (strcmp(blobmsg_name(attr), "params") == 0) + options->parameter = blobmsg_get_bool(attr); + } +} + +static int usp_schema_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), + struct ubus_request_data *req, const char *method __attribute__((unused)), + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_SCHEMA_MAX]; + bbf_options_t options; + memset(&options, 0, sizeof(bbf_options_t)); + struct blob_buf bb; + dm_node_t node; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + if (blobmsg_parse(dm_schema_policy, __DM_SCHEMA_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_schema_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_SCHEMA_PATH]) { + dmlog_error("usp_schema_handler, missing path parameter"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&options, tb[DM_SCHEMA_OPTIONAL]); + unsigned int dm_type = options.dm_type; + + bool param_in_opts = false; + bool cmd_in_opts = false; + bool evt_in_opts = false; + + if (tb[DM_SCHEMA_OPTIONAL]) { + struct blob_attr *attr; + size_t rem; + blobmsg_for_each_attr(attr, tb[DM_SCHEMA_OPTIONAL], rem) + { + if (strcmp(blobmsg_name(attr), "params") == 0) + param_in_opts = true; + if (strcmp(blobmsg_name(attr), "commands") == 0) + cmd_in_opts = true; + if (strcmp(blobmsg_name(attr), "events") == 0) + evt_in_opts = true; + } + } + + if (!param_in_opts) + options.parameter = true; + + if (!cmd_in_opts) + options.command = (dm_type == DM_PROTO_CWMP) ? false : true; + + if (!evt_in_opts) + options.event = (dm_type == DM_PROTO_CWMP) ? false : true; + + void *array = blobmsg_open_array(&bb, "results"); + const char *path = blobmsg_get_string(tb[DM_SCHEMA_PATH]); + if (dm_path2node(path, &node) == 0) { + if (options.dm_type == DM_PROTO_CWMP) { + get_dm_nodes(&options, &node, &bb, GET_OBJ_NAME, 0); + } else { + get_supported_dm(&options, node.id, &bb); + } + } else { + dmlog_error("usp_schema_handler, invalid path: %s", path); + } + + blobmsg_close_array(&bb, array); + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + DM_GET_PATH, + DM_GET_OPTIONAL, + __DM_GET_MAX +}; + +static const struct blobmsg_policy dm_get_policy[] = { + [DM_GET_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [DM_GET_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE}, +}; + +static int usp_get_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), + struct ubus_request_data *req, const char *method __attribute__((unused)), + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_GET_MAX]; + bbf_options_t options; + struct blob_buf bb; + dm_node_t node; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + memset(&options, 0, sizeof(bbf_options_t)); + + if (blobmsg_parse(dm_get_policy, __DM_GET_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_get_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_GET_PATH]) { + dmlog_error("usp_get_handler, missing the path argument"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&options, tb[DM_GET_OPTIONAL]); + + void *array = blobmsg_open_array(&bb, "results"); + char *path_str = blobmsg_get_string(tb[DM_GET_PATH]); + if (dm_path2node(path_str, &node) == 0) { + get_dm_nodes(&options, &node, &bb, GET_VALUE, 0); + } else { + // dmlog_error("usp_get_handler, invalid path: %s", path_str); + } + + blobmsg_close_array(&bb, array); + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + DM_INSTANCES_PATH, + DM_INSTANCES_OPTIONAL, + __DM_INSTANCES_MAX +}; + +static const struct blobmsg_policy dm_instances_policy[] = { + [DM_INSTANCES_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [DM_INSTANCES_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE }, +}; + +static int usp_instances_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), + struct ubus_request_data *req, const char *method __attribute__((unused)), + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_INSTANCES_MAX]; + bbf_options_t options; + struct blob_buf bb; + dm_node_t node; + + memset(&options, 0, sizeof(bbf_options_t)); + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + if (blobmsg_parse(dm_instances_policy, __DM_INSTANCES_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_debug("usp_instances_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_INSTANCES_PATH]) { + dmlog_debug("usp_instances_handler, missing the path"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&options, tb[DM_INSTANCES_OPTIONAL]); + + void *array = blobmsg_open_array(&bb, "results"); + char *path_str = blobmsg_get_string(tb[DM_INSTANCES_PATH]); + if (dm_path2node(path_str, &node) == 0) { + get_dm_nodes(&options, &node, &bb, GET_INSTANCE, 0); + } else { + dmlog_error("usp_get_handler, invalid path: %s", path_str); + } + + blobmsg_close_array(&bb, array); + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +static int get_random_id(void) +{ + int ret; + srand(time(0)); + ret = rand(); + if (!ret) + ret = 1; + + return ret; +} + +static void trans_timeout_handler(struct uloop_timeout *t __attribute__((unused))) +{ + agent_ctx.trans_id = 0; + dmapi_session_end(TRANX_ROLLBACK); +} + +static int start_trans(int max_timeout) +{ + if (dmapi_in_session()) { + dmlog_error("failed to start transaction."); + return -1; + } + + dmapi_session_start(); + agent_ctx.trans_id = get_random_id(); + memset(&agent_ctx.trans_timer, 0, sizeof(agent_ctx.trans_timer)); + agent_ctx.trans_timer.cb = trans_timeout_handler; + int timeout = DEFAULT_TRANS_TIMEOUT; + if (max_timeout > 0) { + timeout = max_timeout*1000; + } + uloop_timeout_set(&agent_ctx.trans_timer, timeout); + return agent_ctx.trans_id; +} + +static int commit_trans(int restart_service) +{ + int ret; + if (restart_service) { + ret = dmapi_session_end(TRANX_COMMIT_AND_APPLY); + } else { + ret = dmapi_session_end(TRANX_COMMIT); + } + uloop_timeout_cancel(&agent_ctx.trans_timer); + agent_ctx.trans_id = 0; + return ret; +} + +static int abort_trans() +{ + uloop_timeout_cancel(&agent_ctx.trans_timer); + agent_ctx.trans_id = 0; + return dmapi_session_end(TRANX_ROLLBACK); +} + +enum { + TRANS_CMD, + TRANS_OPTIONAL, + __TRANS_MAX, +}; + +static const struct blobmsg_policy transaction_policy[] = { + [TRANS_CMD] = { .name = "cmd", .type = BLOBMSG_TYPE_STRING }, + [TRANS_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE }, +}; + +static int usp_transaction_handler(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int ret; + struct blob_attr *tb[__TRANS_MAX] = {NULL}; + bbf_options_t options; + bool is_service_restart = true; + uint32_t max_timeout = 0; + char *trans_cmd = "status"; + struct blob_buf bb; + + memset(&options, 0, sizeof(bbf_options_t)); + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + if (blobmsg_parse(transaction_policy, __TRANS_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_debug("usp_transaction_handlerm, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[TRANS_CMD]) { + dmlog_error("missing transaction cmd"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + if (tb[TRANS_CMD]) + trans_cmd = blobmsg_get_string(tb[TRANS_CMD]); + + get_bbf_options(&options, tb[TRANS_OPTIONAL]); + + struct blob_attr *attr; + size_t rem; + blobmsg_for_each_attr(attr, tb[TRANS_OPTIONAL], rem) { + if (strcmp(blobmsg_name(attr), "timeout") == 0) + max_timeout = blobmsg_get_u32(attr); + if (strcmp(blobmsg_name(attr), "restart_services") == 0) + is_service_restart = blobmsg_get_bool(attr); + } + + if (strcmp(trans_cmd, "start") != 0 && strcmp(trans_cmd, "status") != 0 && + (agent_ctx.trans_id == 0 || agent_ctx.trans_id != options.trans_id)) { + dmlog_error("invalid transaction id for cmd: %s. tid: %d", trans_cmd, options.trans_id); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + if (strcmp(trans_cmd, "start") == 0) { + ret = start_trans(max_timeout); + // ret = transaction_start(max_timeout); + if (ret > 0) { + blobmsg_add_u8(&bb, "status", true); + blobmsg_add_u32(&bb, "transaction_id", ret); + } else { + blobmsg_add_u8(&bb, "status", false); + } + } else if (strcmp(trans_cmd, "commit") == 0) { + ret = commit_trans(is_service_restart); + blobmsg_add_u8(&bb, "status", (ret == 0)); + } else if (strcmp(trans_cmd, "abort") == 0) { + ret = abort_trans(); + blobmsg_add_u8(&bb, "status", (ret == 0)); + } else if (strcmp(trans_cmd, "status") == 0) { + int64_t rem = uloop_timeout_remaining64(&agent_ctx.trans_timer); + blobmsg_add_string(&bb, "status", "on-going"); + blobmsg_add_u32(&bb, "remaining_time", rem / 1000); + } else { + dmlog_error("unknown trans method(%s)", method); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + DM_SET_PATH, + DM_SET_VALUE, + DM_SET_OBJ_PATH, + DM_SET_OPTIONAL, + __DM_SET_MAX, +}; + +static int set_blob_value(const dm_node_t *node, struct blob_attr *attr) +{ + int ret; + if (blob_id(attr) == BLOBMSG_TYPE_STRING) { + ret = dmapi_param_set(node, blobmsg_get_string(attr)); + } else { + char * value = NULL; + switch (blob_id(attr)) { + case BLOBMSG_TYPE_INT8: + asprintf(&value, "%d", blobmsg_get_u8(attr)); + break; + case BLOBMSG_TYPE_INT16: + asprintf(&value, "%d", blobmsg_get_u16(attr)); + break; + case BLOBMSG_TYPE_INT32: + asprintf(&value, "%u", blobmsg_get_u32(attr)); + break; + case BLOBMSG_TYPE_INT64: + asprintf(&value, "%"PRIu64"", blobmsg_get_u64(attr)); + break; + default: + dmlog_error("Unhandled set request type|%x|", blob_id(attr)); + return -1; + } + + ret = dmapi_param_set(node, value); + free(value); + } + + return ret; +} + +static void set_result(struct blob_buf *bb, const char *path, int fault, const char *info) +{ + void *table = blobmsg_open_table(bb, NULL); + blobmsg_add_string(bb, "path", path); + if (fault != 0) { + blobmsg_add_string(bb, "data", ""); + blobmsg_add_u8(bb, "status", false); + blobmsg_add_u32(bb, "fault", fault); + blobmsg_add_string(bb, "fault_msg", info); + } else { + blobmsg_add_string(bb, "data", info); + blobmsg_add_u8(bb, "status", true); + } + + blobmsg_close_table(bb, table); +} + +static const struct blobmsg_policy dm_set_policy[] = { + [DM_SET_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [DM_SET_VALUE] = { .name = "value", .type = BLOBMSG_TYPE_STRING }, + [DM_SET_OBJ_PATH] = { .name = "obj_path", .type = BLOBMSG_TYPE_TABLE }, + [DM_SET_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE }, +}; + +int usp_set_handler(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_SET_MAX] = {NULL}; + bbf_options_t opts; + struct blob_buf bb; + dm_node_t node; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + memset(&opts, 0, sizeof(opts)); + + if (blobmsg_parse(dm_set_policy, __DM_SET_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_set_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_SET_PATH]) { + dmlog_error("usp_set_handler, missing the path"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&opts, tb[DM_SET_OPTIONAL]); + + char *path_str = blobmsg_get_string(tb[DM_SET_PATH]); + if (dm_path2node(path_str, &node) != 0 || dm_node_is_command(node.id) || dm_node_is_event(node.id)) { + dmlog_error("usp_set_handler, invalid path %s", path_str); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + int is_param = dm_node_is_parameter(node.id); + if ((is_param && !tb[DM_SET_VALUE]) || (!is_param && !tb[DM_SET_OBJ_PATH])) { + dmlog_error("usp_set_handler, missing value or obj_path argument"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + int internal_trans = 0; + int res = 0; + if (agent_ctx.trans_id == 0) { + internal_trans = 1; + if (start_trans(0) < 0) { + return UBUS_STATUS_UNKNOWN_ERROR; + } + } else if (opts.trans_id != agent_ctx.trans_id) { + dmlog_error("in transaction or transaction id is invalid"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + void *array = blobmsg_open_array(&bb, "results"); + if (dm_node_is_parameter(node.id)) { + if (set_blob_value(&node, tb[DM_SET_VALUE]) != 0) { + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path_str, 9007, "Invalid parameter value"); + else + set_result(&bb, path_str, 7012, "Invalid value"); + res = -1; + } else { + set_result(&bb, path_str, 0, "1"); + } + } else { + struct blob_attr *attr; + struct blobmsg_hdr *hdr; + size_t tlen = (size_t)blobmsg_data_len(tb[DM_SET_OBJ_PATH]); + + __blob_for_each_attr(attr, blobmsg_data(tb[DM_SET_OBJ_PATH]), tlen) { + hdr = blob_data(attr); + char *path=NULL; + dm_node_t param_node; + asprintf(&path, "%s%s", path_str, (char *)hdr->name); + if (dm_path2node(path, ¶m_node) != 0 || !dm_node_is_parameter(param_node.id)) { + dmlog_error("usp_set_handler, invalid path %s", path); + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path_str, 9005, "Invalid parameter name"); + else + set_result(&bb, path_str, 7026, "Invalid path"); + res = -1; + } else { + if (set_blob_value(¶m_node, attr) != 0) { + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path_str, 9007, "Invalid parameter value"); + else + set_result(&bb, path_str, 7012, "Invalid value"); + res = -1; + } else { + set_result(&bb, path_str, 0, "1"); + } + } + free(path); + if (res < 0) { + break; + } + } + } + + if (internal_trans) { + // Internal transaction: need to commit or revert the changes + if (res == 0 && commit_trans(1) != 0) { + dmlog_error("usp_set_handler, failed to commit"); + } else if (res < 0 && abort_trans() < 0) { + dmlog_error("usp_set_handler, failed to abort"); + } + } + + blobmsg_close_array(&bb, array); + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + DM_ADD_PATH, + DM_ADD_OBJ_PATH, + DM_ADD_OPTIONAL, + __DM_ADD_MAX +}; + +static const struct blobmsg_policy dm_add_policy[] = { + [DM_ADD_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [DM_ADD_OBJ_PATH] = { .name = "obj_path", .type = BLOBMSG_TYPE_TABLE }, + [DM_ADD_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE }, +}; + +int usp_add_handler(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_ADD_MAX]; + bbf_options_t opts; + struct blob_buf bb; + dm_node_t node; + bool allow_partial = false; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + memset(&opts, 0, sizeof(opts)); + + if (blobmsg_parse(dm_add_policy, __DM_ADD_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_add_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_ADD_PATH]) { + dmlog_error("usp_add_handler, missing path"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&opts, tb[DM_ADD_OPTIONAL]); + if (tb[DM_ADD_OPTIONAL]) { + struct blob_attr *attr; + size_t rem; + blobmsg_for_each_attr(attr, tb[DM_ADD_OPTIONAL], rem) { + if (strcmp(blobmsg_name(attr), "allow_partial") == 0) + allow_partial = blobmsg_get_bool(attr); + } + } + + char *path_str = blobmsg_get_string(tb[DM_ADD_PATH]); + if (dm_path2node(path_str, &node) != 0 || !dm_node_is_objectlist(node.id)) { + dmlog_error("usp_add_handler, invalid path %s", path_str); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + int internal_trans = 0; + int res = 0; + if (agent_ctx.trans_id == 0) { + internal_trans = 1; + if (start_trans(0) < 0) { + return UBUS_STATUS_UNKNOWN_ERROR; + } + } else if (opts.trans_id != agent_ctx.trans_id) { + dmlog_error("in transaction or transaction id is invalid"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + void *array = blobmsg_open_array(&bb, "results"); + + if (dmapi_object_add(&node) == 0) { + dm_index_t idx = dm_node_last_index(&node); + char *data = NULL; + asprintf(&data, "%d", idx); + set_result(&bb, path_str, 0, data); + free(data); + } else { + res = -1; + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path_str, 9005, "Invalid parameter name"); + else + set_result(&bb, path_str, 7026, "Invalid path"); + blobmsg_close_array(&bb, array); + goto end; + } + + if (tb[DM_ADD_OBJ_PATH]) { + struct blob_attr *attr; + struct blobmsg_hdr *hdr; + size_t tlen = (size_t)blobmsg_data_len(tb[DM_ADD_OBJ_PATH]); + dm_path_t inst_path; + dm_node2name(&node, inst_path, sizeof(inst_path)); + __blob_for_each_attr(attr, blobmsg_data(tb[DM_ADD_OBJ_PATH]), tlen) { + hdr = blob_data(attr); + char *path=NULL; + dm_node_t param_node; + asprintf(&path, "%s.%s", inst_path, (char *)hdr->name); + if (dm_path2node(path, ¶m_node) != 0 || !dm_node_is_parameter(param_node.id)) { + dmlog_error("usp_add_handler, invalid path %s", path); + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path, 9005, "Invalid parameter name"); + else + set_result(&bb, path, 7026, "Invalid path"); + res = -1; + } else { + if (set_blob_value(¶m_node, attr) != 0) { + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path, 9007, "Invalid parameter value"); + else + set_result(&bb, path, 7012, "Invalid value"); + res = -1; + } else { + set_result(&bb, path, 0, "1"); + } + } + free(path); + if (res < 0) { + if (!allow_partial) { + blobmsg_close_array(&bb, array); + goto end; + } + res = 0; // clear error for partial + } + } + } + blobmsg_close_array(&bb, array); + +end: + if (internal_trans) { + // Internal transaction: need to commit or revert the changes + if (res == 0 && commit_trans(1) != 0) { + dmlog_error("usp_add_handler, failed to commit"); + } else if (res < 0 && abort_trans() < 0) { + dmlog_error("usp_add_handler, failed to abort"); + } + } + + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + DM_DEL_PATH, + DM_DEL_OPTIONAL, + __DM_DEL_MAX +}; + +static const struct blobmsg_policy dm_del_policy[] = { + [DM_DEL_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [DM_DEL_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE }, +}; + +int usp_del_handler(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_DEL_MAX]; + bbf_options_t opts; + struct blob_buf bb; + dm_node_t node; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + memset(&opts, 0, sizeof(opts)); + + if (blobmsg_parse(dm_del_policy, __DM_DEL_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_del_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_DEL_PATH]) { + dmlog_error("usp_del_handler, missing path"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&opts, tb[DM_DEL_OPTIONAL]); + + int internal_trans = 0; + int res = 0; + if (agent_ctx.trans_id == 0) { + internal_trans = 1; + if (start_trans(0) < 0) { + return UBUS_STATUS_UNKNOWN_ERROR; + } + } else if (opts.trans_id != agent_ctx.trans_id) { + dmlog_error("in transaction or transaction id is invalid"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + void *array = blobmsg_open_array(&bb, "results"); + char *path = blobmsg_get_string(tb[DM_DEL_PATH]); + if (dm_path2node(path, &node) != 0 || dmapi_object_del(&node) < 0) { + set_result(&bb, path, 9005, "Invalid path"); + res = -1; + goto end; + } + set_result(&bb, path, 0, "1"); + +end: + blobmsg_close_array(&bb, array); + if (internal_trans) { + // Internal transaction: need to commit or revert the changes + if (res == 0 && commit_trans(1) != 0) { + dmlog_error("usp_add_handler, failed to commit"); + } else if (res < 0 && abort_trans() < 0) { + dmlog_error("usp_add_handler, failed to abort"); + } + } + + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + DM_OPERATE_COMMAND, + DM_OPERATE_OPTIONAL, + __DM_OPERATE_MAX, +}; + +static const struct blobmsg_policy dm_operate_policy[__DM_OPERATE_MAX] = { + [DM_OPERATE_COMMAND] = { .name = "command", .type = BLOBMSG_TYPE_STRING }, + [DM_OPERATE_OPTIONAL] = { .name = "optional", .type = BLOBMSG_TYPE_TABLE }, +}; + +// check if mandatory name is present in the input +static int check_mandatory_input_name(struct blob_attr *input, const char *mandatory_name) +{ + if (input == NULL) { + return 0; + } + + struct blob_attr *head = blobmsg_data(input); + int len = blobmsg_data_len(input); + struct blob_attr *attr; + __blob_for_each_attr(attr, head, len) + { + if (dm_node_compare_command_arg_name(blobmsg_name(attr), mandatory_name) == 0) { + return 1; + } + } + + return 0; +} + +// check if the input names and values are expected as defined in the datamodel +// return 1 if successful, otherwise 0 +static int check_operate_inputs(const struct dm_command *cmd, struct blob_attr *input) +{ + // check mandatory input + for (int i = 0; i < cmd->inputs_cnt; i++) { + if (cmd->inputs[i].mandatory) { + if (check_mandatory_input_name(input, cmd->inputs[i].name) != 1) { + dmlog_error("mandatory input name not found: %s", cmd->inputs[i].name); + return 0; + } + } + } + + if (input == NULL) { + return 1; + } + + struct blob_attr *head = blobmsg_data(input); + int len = blobmsg_data_len(input); + struct blob_attr *attr; + __blob_for_each_attr(attr, head, len) + { + const char *name = blobmsg_name(attr); + if (strcmp(name, "Internal_TimeRef") == 0) { + continue; + } + if (dm_node_verify_command_input(cmd->node.node_id, name, blobmsg_get_string(attr)) != 1) { + dmlog_error("invalid argument value: %s: %s", name, blobmsg_get_string(attr)); + return 0; + } + } + + return 1; +} + +void async_operate(struct ubus_context *ctx, const dm_node_t *node, const char *path, const char *cmd_key, + char *input_json, struct ubus_request_data *req) +{ + agent_ctx.last_async_id++; + pid_t child = fork(); + if (child == -1) { + dmlog_error("fork failed"); + } else if (child == 0) { + struct ubus_context *ctx = ubus_connect(NULL); + struct json_object *json_output = NULL; + struct blob_buf bb = {}; + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + blobmsg_add_u32(&bb, "async_id", agent_ctx.last_async_id); + void *array = blobmsg_open_array(&bb, "results"); + void *table = blobmsg_open_table(&bb, NULL); + + if (dmapi_operate(node, input_json, &json_output) < 0) { + dmlog_error("async_operate failed"); + set_result(&bb, path, 7022, "Operation failed"); + } else { + blobmsg_add_string(&bb, "path", path); + blobmsg_add_string(&bb, "data", cmd_key); + if (json_output != NULL) { + blobmsg_add_json_element(&bb, "output", json_output); + json_object_put(json_output); + } + blobmsg_close_table(&bb, table); + } + + blobmsg_close_array(&bb, array); + ubus_send_event(ctx, BBF_ASYNC_COMPLETE_EVENT, bb.head); + blob_buf_free(&bb); + _exit(0); + } else { + struct async_operate_request *async_req = calloc(1, sizeof(struct async_operate_request)); + async_req->async_id = agent_ctx.last_async_id; + ubus_defer_request(ctx, req, &async_req->req); + list_add_tail(&async_req->list, &agent_ctx.async_req_list); + } +} + +static int contains_invalid_input_status(const struct dm_command *cmd) +{ + for (int i = 0; i < cmd->outputs_cnt; i++) { + if (strcmp(cmd->outputs[i].name, "Status") == 0) { + if (cmd->outputs[i].enum_values) { + for (int j = 0; cmd->outputs[i].enum_values[j]; j++) { + if (strcmp(cmd->outputs[i].enum_values[j], "Error_Invalid_Input") == 0) { + return 1; + } + } + } + } + } + + return 0; +} + +static void set_invalid_input_status(struct blob_buf *bb, const char *path) +{ + void *table = blobmsg_open_table(bb, NULL); + blobmsg_add_string(bb, "path", path); + blobmsg_add_string(bb, "data", ""); + void *output = blobmsg_open_array(bb, "output"); + void *table2 = blobmsg_open_table(bb, NULL); + blobmsg_add_string(bb, "path", "Status"); + blobmsg_add_string(bb, "data", "Error_Invalid_Input"); + blobmsg_add_string(bb, "type", "xsd:string"); + blobmsg_close_table(bb, table2); + blobmsg_close_array(bb, output); + blobmsg_close_table(bb, table); +} + +static int usp_operate_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), + struct ubus_request_data *req, const char *method __attribute__((unused)), + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_OPERATE_MAX] = {NULL}; + bbf_options_t opts; + struct blob_buf bb; + dm_node_t node; + struct blob_attr *op_input = NULL; + const char *cmd_key = ""; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + memset(&opts, 0, sizeof(opts)); + + if (blobmsg_parse(dm_operate_policy, __DM_OPERATE_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_operate_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_OPERATE_COMMAND]) { + dmlog_error("usp_operate_handler, missing the operate pathname"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + get_bbf_options(&opts, tb[DM_OPERATE_OPTIONAL]); + + if (tb[DM_OPERATE_OPTIONAL]) { + struct blob_attr *attr; + size_t rem; + blobmsg_for_each_attr(attr, tb[DM_OPERATE_OPTIONAL], rem) { + if (strcmp(blobmsg_name(attr), "command_key") == 0) + cmd_key = blobmsg_get_string(attr); + if (strcmp(blobmsg_name(attr), "input") == 0) + op_input = attr; + } + } + + void *array = blobmsg_open_array(&bb, "results"); + char *path = blobmsg_get_string(tb[DM_OPERATE_COMMAND]); + if (dm_path2node(path, &node) != 0 || !dm_node_is_command(node.id)) { + dmlog_error("usp_operate_handler, invalid operate pathname %s", path); + if (opts.dm_type == DM_PROTO_CWMP) + set_result(&bb, path, 9005, "Invalid parameter name"); + else + set_result(&bb, path, 7026, "Invalid path"); + goto end; + } + + const struct dm_command *cmd = dm_node_get_command(node.id); + if (check_operate_inputs(cmd, op_input) != 1) { + dmlog_error("failed to verify input arguments"); + if (contains_invalid_input_status(cmd)) { + set_invalid_input_status(&bb, path); + } else { + dmlog_error("send 7027"); + set_result(&bb, path, 7027, "Invalid command arguments"); + } + goto end; + } + + char *input_json = NULL; + if (op_input) { + input_json = blobmsg_format_json(op_input, true); + } + + if (cmd->async) { + async_operate(ctx, &node, path, cmd_key, input_json, req); + blob_buf_free(&bb); + if (input_json) { + free(input_json); + } + return 0; + } + + struct json_object *json_output = NULL; + if (dmapi_operate(&node, input_json, &json_output) < 0) { + dmlog_error("dmapi_operate failed"); + set_result(&bb, path, 7022, "Command failure"); + goto end; + } else { + void *table = blobmsg_open_table(&bb, NULL); + blobmsg_add_string(&bb, "path", path); + if (json_output != NULL) { + blobmsg_add_string(&bb, "data", cmd_key); + blobmsg_add_json_element(&bb, "output", json_output); + json_object_put(json_output); + } + blobmsg_add_u8(&bb, "status", true); + blobmsg_close_table(&bb, table); + } + + if (input_json) { + free(input_json); + } + +end: + blobmsg_close_array(&bb, array); + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + return 0; +} + +enum { + BBF_NOTIFY_NAME, + BBF_NOTIFY_PRAMS, + __BBF_NOTIFY_MAX, +}; + +static const struct blobmsg_policy dm_notify_event_policy[] = { + [BBF_NOTIFY_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, + [BBF_NOTIFY_PRAMS] = { .name = "parameters", .type = BLOBMSG_TYPE_TABLE }, +}; + +static int usp_notify_event(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req __attribute__((unused)), const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__BBF_NOTIFY_MAX] = {NULL}; + + if (blobmsg_parse(dm_notify_event_policy, __BBF_NOTIFY_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_notify_event,failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[BBF_NOTIFY_NAME]) { + dmlog_error("usp_notify_event, missing notify name"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + ubus_send_event(ctx, BBF_EVENT, msg); + + return 0; +} + +static void async_operate_complete_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, + const char *type, struct blob_attr *msg) +{ + (void)ev; + struct blob_attr *tb[1] = {NULL}; + const struct blobmsg_policy policy[] = { + [0] = {.name = "async_id", .type = BLOBMSG_TYPE_INT32} + }; + + if (blobmsg_parse(policy, 1, tb, blob_data(msg), blob_len(msg)) || !tb[0]) { + dmlog_error("Failed to parse msg in async_operate_complete_event_handler"); + return; + } + + unsigned int async_id = blobmsg_get_u32(tb[0]); + struct async_operate_request *async_req; + list_for_each_entry(async_req, &agent_ctx.async_req_list, list) + { + if (async_req->async_id == async_id) { + ubus_send_reply(ctx, &async_req->req, msg); + ubus_complete_deferred_request(ctx, &async_req->req, 0); + list_del(&async_req->list); + free(async_req); + return; + } + } + + dmlog_error("unknown aysnc id from the notify %d", async_id); +} + +static int register_async_event() +{ + printf("DEBUG: register_async_event() starting\n"); + struct ubus_event_handler *ev = (struct ubus_event_handler *)malloc(sizeof(struct ubus_event_handler)); + if (!ev) { + printf("DEBUG: malloc failed in register_async_event()\n"); + return -1; + } + memset(ev, 0, sizeof(struct ubus_event_handler)); + ev->cb = async_operate_complete_event_handler; + + if (0 != ubus_register_event_handler(&agent_ctx.ubus_ctx, ev, BBF_ASYNC_COMPLETE_EVENT)) { + printf("DEBUG: Failed to register ubus event usp_async_complete\n"); + dmlog_error("Failed to register ubus event usp_async_complete"); + return -1; + } + printf("DEBUG: register_async_event() completed successfully\n"); + + return 0; +} + +static void dm_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, + const char *type, struct blob_attr *msg) +{ + struct dm_ubus_event_handler *myhandler = container_of(ev, struct dm_ubus_event_handler, uhandler); + char *json_str = blobmsg_format_json(msg, true); + dmlog_debug("dm_event_handler ubus event: %s, for node: %s, msg: %s", type, dm_node_id_str(myhandler->node_id), json_str); + + if (json_str == NULL) { + return; + } + + int len = strlen(json_str); + if (len < 2) { + return; + } + + char * json_input = json_str; + if (json_str[0] == '{' && json_str[1] == '{') { + // object without name, which is invalid JSON format, remove the outer "{}". + json_str[0] = '\0'; + json_str[len - 1] = '\0'; + json_input = json_str + 1; + } + struct json_object *res = NULL; + dmapi_handle_ubus_event(myhandler->node_id, json_input, &res); + if (json_str) { + free(json_str); + } + if (!res) { + dmlog_error("dm_event_handler, event not handled: %s", type); + return; + } + const struct dm_event *evt_obj = dm_node_get_event(myhandler->node_id); + if (!evt_obj) { + dmlog_error("dm_event_handler, event not found: %d", myhandler->node_id); + return; + } + + struct blob_buf bb; + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + blobmsg_add_string(&bb, "name", dm_node_id_str(myhandler->node_id)); + blobmsg_add_json_element(&bb, "input", res); + ubus_send_event(ctx, "bbfdm.event", bb.head); + json_object_put(res); + blob_buf_free(&bb); +} + +static void register_dm_event(const struct dm_object * obj) +{ + printf("DEBUG: register_dm_event() starting, obj=%p\n", obj); + if (!obj) { + printf("DEBUG: register_dm_event() obj is NULL\n"); + return; + } + + printf("DEBUG: register_dm_event() event_num=%d\n", obj->event_num); + for (int i = 0; i < obj->event_num; i++) { + const struct dm_event * evt = (const struct dm_event*)obj->event_list[i]; + if (evt->ubus_event) { + printf("DEBUG: registering ubus event: %s\n", evt->ubus_event); + dmlog_debug("registerred ubus event: %s for node %s", evt->ubus_event, dm_node_id_str(evt->node.node_id)); + struct dm_ubus_event_handler *ev = (struct dm_ubus_event_handler *)malloc(sizeof(struct dm_ubus_event_handler)); + if (!ev) { + printf("DEBUG: malloc failed for dm_ubus_event_handler\n"); + continue; + } + memset(ev, 0, sizeof(struct dm_ubus_event_handler)); + ev->uhandler.cb = dm_event_handler; + ev->node_id = evt->node.node_id; + if (0 != ubus_register_event_handler(&agent_ctx.ubus_ctx, &ev->uhandler, evt->ubus_event)) { + printf("DEBUG: Failed to register ubus event %s\n", evt->ubus_event); + dmlog_error("Failed to register ubus event %s", evt->ubus_event); + } + } + } + + printf("DEBUG: register_dm_event() object_num=%d\n", obj->object_num); + for (int i = 0; i < obj->object_num; i++) { + const struct dm_object * child_obj = (const struct dm_object*)obj->object_list[i]; + register_dm_event(child_obj); + } + printf("DEBUG: register_dm_event() completed\n"); +} + + +enum { + DM_DUMP_BUF_PATH, + __DM_DUMP_BUF_MAX +}; + +static const struct blobmsg_policy dm_dump_buf_policy[] = { + [DM_DUMP_BUF_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, +}; +static int usp_dump_buf_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), + struct ubus_request_data *req, const char *method __attribute__((unused)), + struct blob_attr *msg) +{ + struct blob_attr *tb[__DM_DUMP_BUF_MAX]; + dm_node_t node; + + if (blobmsg_parse(dm_dump_buf_policy, __DM_DUMP_BUF_MAX, tb, blob_data(msg), blob_len(msg))) { + dmlog_error("usp_dump_buf_handler, failed to parse blob"); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + if (!tb[DM_DUMP_BUF_PATH]) { + dmlog_error("usp_dump_buf_handler, missing path parameter"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + if (dm_path2node(blobmsg_get_string(tb[DM_DUMP_BUF_PATH]), &node) != 0) { + dmlog_error("usp_dump_buf_handler, missing path parameter"); + return UBUS_STATUS_INVALID_ARGUMENT; + } + + dmapi_dump_node_buffer(&node); + + return 0; +} + +// just to be compatible with bbfdm +int bbfdm_refresh_references_db(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_buf bb = {0}; + + memset(&bb, 0, sizeof(struct blob_buf)); + blob_buf_init(&bb, 0); + + blobmsg_add_u8(&bb, "status", true); + + ubus_send_reply(ctx, req, bb.head); + blob_buf_free(&bb); + + return 0; +} + +static struct ubus_method bbf_methods[] = { + UBUS_METHOD("schema", usp_schema_handler, dm_schema_policy), + UBUS_METHOD("get", usp_get_handler, dm_get_policy), + UBUS_METHOD("instances", usp_instances_handler, dm_instances_policy), + UBUS_METHOD("transaction", usp_transaction_handler, transaction_policy), + UBUS_METHOD("set", usp_set_handler, dm_set_policy), + UBUS_METHOD("add", usp_add_handler, dm_add_policy), + UBUS_METHOD("del", usp_del_handler, dm_del_policy), + UBUS_METHOD("operate", usp_operate_handler, dm_operate_policy), + UBUS_METHOD("dump-buffer", usp_dump_buf_handler, dm_dump_buf_policy), + UBUS_METHOD("notify_event", usp_notify_event, dm_notify_event_policy), + UBUS_METHOD_NOARG("refresh_references_db", bbfdm_refresh_references_db) +}; + +static struct ubus_object_type bbf_type = UBUS_OBJECT_TYPE(METHOD_NAME, bbf_methods); + +static struct ubus_object bbf_object = { + .name = METHOD_NAME, + .type = &bbf_type, + .methods = bbf_methods, + .n_methods = ARRAY_SIZE(bbf_methods) +}; + +static int usp_init(struct agent_context *u) +{ + int ret; + + printf("DEBUG: usp_init() starting\n"); + dmlog_debug("Registering bbfdm ubus objects...."); + ret = ubus_add_object(&u->ubus_ctx, &bbf_object); + if (ret) { + printf("DEBUG: ubus_add_object failed, ret=%d\n", ret); + dmlog_error("ubus_add_object failed"); + return ret; + } + printf("DEBUG: usp_init() completed successfully\n"); + + return ret; +} + +int start_dm_agent() +{ + int ret = 0; + const char *ubus_socket = NULL; + + printf("DEBUG: start_dm_agent() called\n"); + + memset(&agent_ctx, 0, sizeof(struct agent_context)); + INIT_LIST_HEAD(&agent_ctx.async_req_list); + printf("DEBUG: agent_ctx initialized\n"); + + uloop_init(); + printf("DEBUG: uloop_init() completed\n"); + + ret = ubus_connect_ctx(&agent_ctx.ubus_ctx, ubus_socket); + if (ret != UBUS_STATUS_OK) { + printf("DEBUG: Failed to connect to ubus, ret=%d\n", ret); + dmlog_error("Failed to connect to ubus"); + return -1; + } + printf("DEBUG: ubus_connect_ctx() successful\n"); + + printf("DEBUG: calling register_async_event()\n"); + register_async_event(); + printf("DEBUG: calling register_dm_event()\n"); + register_dm_event(dm_node_get_object(0)); + printf("DEBUG: calling ubus_add_uloop()\n"); + ubus_add_uloop(&agent_ctx.ubus_ctx); + printf("DEBUG: calling usp_init()\n"); + ret = usp_init(&agent_ctx); + if (ret != UBUS_STATUS_OK) { + printf("DEBUG: usp_init failed, ret=%d\n", ret); + dmlog_error("usp_init failed"); + ret = UBUS_STATUS_UNKNOWN_ERROR; + goto exit; + } + printf("DEBUG: usp_init() successful\n"); + + dmlog_info("dm-agent started"); + printf("DEBUG: entering uloop_run()\n"); + uloop_run(); + printf("DEBUG: uloop_run() exited\n"); + +exit: + printf("DEBUG: cleaning up and exiting\n"); + ubus_shutdown(&agent_ctx.ubus_ctx); + uloop_done(); + + return ret; +} diff --git a/dm-framework/dm-agent/src/main.c b/dm-framework/dm-agent/src/main.c new file mode 100644 index 000000000..29c77ab20 --- /dev/null +++ b/dm-framework/dm-agent/src/main.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "dm_api.h" +#include "dm_log.h" + +extern int start_dm_agent(); + +int main(void) +{ + dmlog_info("dm-agent started"); + if (dmapi_init() != 0) { + dmlog_error("dmapi_init failed"); + return -1; + } + + start_dm_agent(); + + dmapi_quit(); + return 0; +} diff --git a/dm-framework/dm-api/Makefile b/dm-framework/dm-api/Makefile new file mode 100644 index 000000000..a1294fafd --- /dev/null +++ b/dm-framework/dm-api/Makefile @@ -0,0 +1,74 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=dm-api +PKG_VERSION:=1.0 +PKG_RELEASE:=1 + +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) +PLATFORM_CONFIG:=$(TOPDIR)/.config +AUTO_CONF_H:=$(PKG_BUILD_DIR)/autoconf.h + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + CATEGORY:=Genexis + TITLE:=dm-api + PKG_BUILD_DEPENDS:=datamodels + DEPENDS:=+libsqlite3 \ + +libjson-c +libstdcpp +quickjs \ + +libubus +libubox + + # Depedencies for RG products + URL:=http://www.genexis.eu + PKG_LICENSE:=GENEXIS + PKG_LICENSE_URL:= +endef + +define Package/$(PKG_NAME)/description + This package contains api for the dm-framework +endef + +define Build/Prepare + $(CP) -rf ./src/* $(PKG_BUILD_DIR)/ +endef + +TARGET_CFLAGS += $(FPIC) -I$(PKG_BUILD_DIR) + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR)\ + PROJECT_ROOT="$(PKG_BUILD_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + EXTRA_CFLAGS="$(TARGET_CFLAGS)" \ + all +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(INSTALL_DIR) $(1)/usr/lib + + $(CP) $(PKG_BUILD_DIR)/core/dm_api.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/core/dbmgr.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/include/dm_log.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/utils/dm_list.h $(1)/usr/include/ + $(CP) $(PKG_BUILD_DIR)/libdmapi.so $(1)/usr/lib/ +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/lib + $(INSTALL_DIR) $(1)/sbin/ + + $(INSTALL_BIN) $(PKG_BUILD_DIR)/libdmapi.so $(1)/usr/lib/ +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/dm-framework/dm-api/src/Makefile b/dm-framework/dm-api/src/Makefile new file mode 100644 index 000000000..8bd067785 --- /dev/null +++ b/dm-framework/dm-api/src/Makefile @@ -0,0 +1,63 @@ +# +# Copyright (c) 2023 Genexis B.V. All rights reserved. +# This Software and its content are protected by the Dutch Copyright Act +# ('Auteurswet'). All and any copying and distribution of the software +# and its content without authorization by Genexis B.V. is +# prohibited. The prohibition includes every form of reproduction and +# distribution. +# +# + +PROG = libdmapi.so + +SRCS = \ + core/dm_api.c \ + core/dbmgr.c \ + core/inode_buf.c \ + core/dm_apply.c \ + core/dm_import.c \ + core/db_upgrade.c \ + utils/dm_uci.c \ + utils/dm_list.c \ + utils/dm_log.c \ + utils/ubus_client.c \ + utils/utils.c \ + quickjs/qjs.c \ + quickjs/qjs_log.c \ + quickjs/qjs_dm_api.c \ + quickjs/qjs_ubus_api.c + +OBJS = $(SRCS:.c=.o) +DEPS = $(SRCS:.c=.d) + +CC = $(CROSS_COMPILE)gcc +STRIP = $(CROSS_COMPILE)strip +CFLAGS += \ + -I core \ + -I $(STAGING_DIR)/usr/include $(EXTRA_CFLAGS) \ + -I handlers/common \ + -I handlers/tr181 \ + -I include \ + -I utils \ + -I quickjs + +CFLAGS += -MMD -MP -std=gnu99 + +LDFLAGS = -shared +CFLAGS += -Wall -Werror -fpic + +LDFLAGS += -lquickjs -lsqlite3 -latomic +# END for src from mgmt-agent + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $^ $(LDFLAGS) -o $@ + +%.o: %.c + $(CC) -c $(CFLAGS) $^ -o $@ + +clean: + rm -f $(PROG) *.o core $(DEPS) + +-include $(DEPS) diff --git a/dm-framework/dm-api/src/core/._dm_api.c b/dm-framework/dm-api/src/core/._dm_api.c new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/core/._dm_api.h b/dm-framework/dm-api/src/core/._dm_api.h new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/core/._dm_import.c b/dm-framework/dm-api/src/core/._dm_import.c new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/core/._dm_linker.c b/dm-framework/dm-api/src/core/._dm_linker.c new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/core/._dm_linker.h b/dm-framework/dm-api/src/core/._dm_linker.h new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/core/db_upgrade.c b/dm-framework/dm-api/src/core/db_upgrade.c new file mode 100644 index 000000000..014cddca4 --- /dev/null +++ b/dm-framework/dm-api/src/core/db_upgrade.c @@ -0,0 +1,2053 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dm_log.h" +#include "utils.h" + +/* + * All SQL statement arguments must be escaped, except table name using + * [db_name].[table_name] notation which may not be escaped. Columns list + * elements in INSERT_STMT must be escaped by user. + */ +#define DROP_TABLE_STMT "drop table main.%s;" +#define RENAME_TABLE_STMT "alter table main.%s rename to \"%s\";" +#define SELECT_STMT "select * from main.%s;" +#define INSERT_STMT "insert into main.%s (%s) select %s from main.%s;" +#define CLONE_STMT "insert into main.%s select * from new.%s;" +#define CLEANUP_STMT "delete from main.%s where \"IndexOf%s\" not in (select \"IndexOf%s\" from main.%s);" +#define KEYS_UPDATE_STMT "update main.%s set \"NextIndexOf%s\"=(select max(\"IndexOf%s\") + 1 from main.%s where \"IndexOf%s\"=%u) where \"NextIndexOf%s\" <= (select max(\"IndexOf%s\") from main.%s where \"IndexOf%s\"=%u) and \"IndexOf%s\"=%u;" +#define SELECT_INDEX_STMT "select \"IndexOf%s\" from main.%s;" +#define KEYS_STATIC_UPDATE_STMT "update main.%s set \"NextIndexOf%s\"=(select max(\"IndexOf%s\") + 1 from main.%s) where \"NextIndexOf%s\" <= (select max(\"IndexOf%s\") from main.%s);" + +/* Columns in INSERT_STMT must be escaped and separated by a comma. */ +#define COLUMN_LIST_ELEM_PREFIX "\"" +#define COLUMN_LIST_ELEM_SUFFIX "\"," + +/* Temporary table name used on table alteration to backup old table */ +#define TMP_TABLE_NAME "__DB_UPGR_OLD_TAB__" + +/* Format of child table name. */ +#define CHILD_TABLE_NAME "%s_%s" + +/* Format of 'IndexOf...' field. */ +#define INDEX_NAME "IndexOf%s" + +/* Tables' names and SQL statements used for creation. */ +struct tables { + char **names; + char **schemas; + unsigned cnt; +}; + +/* Columns' names. */ +struct columns { + char **names; + char **types; + unsigned cnt; +}; + +/* 'NextIndexOf...' and 'IndexOf...' keys of tables. */ +struct keys { + char **indexes; + char **next_indexes; + unsigned indexes_cnt; + unsigned next_indexes_cnt; +}; + +/* 'IndexOf...' values of a table. */ +struct idx_values { + unsigned *values; + unsigned cnt; +}; + +/* DB handle. */ +static sqlite3 *db; + + +static int user_version_callback(void *data, int argc, char **argv, char **azColName) +{ + int *user_version = (int *)data; + *user_version = atoi(argv[0]); + return 0; +} + +int get_db_user_version(const char *db_path, int *user_version) +{ + sqlite3 *db; + int rc; + char *sql = "PRAGMA user_version;"; + char *err_msg = NULL; + + rc = sqlite3_open(db_path, &db); + if (rc) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + return rc; + } + + rc = sqlite3_exec(db, sql, user_version_callback, user_version, &err_msg); + if (rc != SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + } + + sqlite3_close(db); + return rc; +} + +/** + * Processes SQL statement given as 'sql' argument. For every row that might be + * returned, executes 'cb' function and provides it with 'data', if any was + * provided. If 'cb' is NULL, no callback is called when a row is returned. + * @pre Global DB handle (::db) must be open. + * @param sql SQL statement to be executed. + * @param cb Callback function. + * @param data Data to be handed to callback function on every row returned. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_process_stmt(const char *sql, void *cb, void *data) +{ + if (SQLITE_OK != sqlite3_exec(db, sql, cb, data, NULL)) { + dmlog_error("Could not process statement %s.", sql); + return -1; + } + + return 0; +} + +/** + * Begins a transaction. + * @pre Global DB handle (::db) must be open. + * @post In case of success, transaction is started. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_begin_trans(void) +{ + return db_upgr_process_stmt("begin transaction;", NULL, NULL); +} + +/** + * Commits a transaction. + * @pre Global DB handle (::db) must be open and transaction must be started. + * @post In case of success, transaction is committed. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_commit_trans(void) +{ + return db_upgr_process_stmt("commit;", NULL, NULL); +} + +/** + * Drops a table with provided name. + * @pre Global DB handle (::db) must be open. + * @post Specified table gets dropped from 'main' DB. + * @param table Table name to be dropped. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_drop_table(const char *table) +{ + int ret; + size_t size = strlen(DROP_TABLE_STMT) + strlen(table) + 1; + char sql[size]; + + sprintf(sql, DROP_TABLE_STMT, table); + ret = db_upgr_process_stmt(sql, NULL, NULL); + + return ret; +} + +/** + * Appends provided table 'name' and SQL 'schema' to a structure describing + * 'tables' (struct tables). Arrays in the structure get (re)allocated, values + * of 'name' and 'schema' get copied and counter is incremented in case of + * success. + * @post Caller must free the appended values and containing arrays. + * @param name Table name. + * @param schema SQL schema used to create table. + * @param tables Structure holding tables' names and schemas. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_append_tables_data(const char *name, const char *schema, + struct tables *tables) +{ + char **tmp_names = tables->names; + char **tmp_schemas = tables->schemas; + + unsigned int uint_add = 0; + if (UINT_MAX - 1 < tables->cnt) { + dmlog_error("Buffer over flow on addition"); + return -1; + } else { + uint_add = tables->cnt + 1; + } + + if (uint_add > SIZE_MAX / sizeof(char *)) { + dmlog_error("Buffer overflow on multiplication"); + return -1; + } + + size_t realloc_size = (uint_add) * sizeof(char *); + + tables->names = realloc(tables->names, realloc_size); + + if (NULL == tables->names) { + tables->names = tmp_names; + dmlog_error("Could not realloc names array."); + return -1; + } + + tables->names[tables->cnt] = NULL; + + tables->schemas = realloc(tables->schemas, realloc_size); + + if (NULL == tables->schemas) { + tables->schemas = tmp_schemas; + dmlog_error("Could not realloc schemas array."); + return -1; + } + + tables->schemas[tables->cnt] = NULL; + + tables->names[tables->cnt] = calloc(strlen(name) + 1, sizeof(char)); + + if (NULL == tables->names[tables->cnt]) { + dmlog_error("Could not calloc table name."); + return -1; + } + + tables->schemas[tables->cnt] = calloc(strlen(schema) + 1, sizeof(char)); + + if (NULL == tables->schemas[tables->cnt]) { + free(tables->names[tables->cnt]); + tables->names[tables->cnt] = NULL; + dmlog_error("Could not calloc table schema."); + return -1; + } + + strcpy(tables->names[tables->cnt], name); + strcpy(tables->schemas[tables->cnt], schema); + + tables->cnt += 1; + + return 0; +} + +/** + * Callback function for reading tables' names and SQL schemas. It gets called + * on every row returned and passes the read data to + * db_upgr_append_tables_data function, which adds the read values to a + * container, pointed by data. + * @post Caller must free the appended values and containing arrays. + * @param data Data to be passed to callback function. + * @param argc Number of columns returned. + * @param argv Returned values. + * @param col_name Columns' names. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_tables_cb(void *data, int argc, char **argv, + char **col_name) +{ + struct tables *tables = data; + + if (2 != argc || 0 != strcmp(col_name[0], "name") || + 0 != strcmp(col_name[1], "sql")) { + dmlog_error("Incorrect columns returned."); + return -1; + } + + if (NULL == argv[0] || NULL == argv[1]) { + dmlog_error("NULL values returned."); + return -1; + } + + if (0 != db_upgr_append_tables_data(argv[0], argv[1], tables)) { + dmlog_error("Failed to append returned table data."); + return -1; + } + + return 0; +} + +/** + * Reads tables' names and SQL statements used for tables creation from 'main' + * and 'new' DBs. Read values get added to provided structures. + * @post Caller must free the appended values and containing arrays. + * @param tables_old Structure holding values from running DB. + * @param tables_old Structure holding values from default DB. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_tables(struct tables *tables_old, + struct tables *tables_new) +{ + if (0 != db_upgr_process_stmt( + "select name, sql from main.sqlite_master where type='table';", + db_upgr_get_tables_cb, tables_old) + || 0 != db_upgr_process_stmt( + "select name, sql from new.sqlite_master where type='table';", + db_upgr_get_tables_cb, tables_new)) + return -1; + + return 0; +} + +/** + * Checks if provided table exists in the list of tables. + * @param table Table name to be checked. + * @param tables Structure holding tables' names and schemas. + * @return true if table exists in provided structure, false otherwise. + */ +static bool db_upgr_check_table_exists(const char *table, + struct tables *tables) +{ + unsigned i; // Initilised in for statement + + for (i = 0; i < tables->cnt; i++) { + if (0 == strcmp(table, tables->names[i])) + return true; + } + + return false; +} + +/** + * Checks if provided schema of a table is equal to the schema of table with + * the same name stored in provided structure. + * @param table Table name to be checked. + * @param schema Schema to be compared. + * @param tables Structure holding tables' names and schemas. + * @return true if schemas are equal, false otherwise. + */ +static bool db_upgr_check_tables_equal(const char *table, const char *schema, + struct tables *tables) +{ + unsigned i; // Initilised in for statement + + for (i = 0; i < tables->cnt; i++) { + if (0 == strcmp(table, tables->names[i]) && + 0 == strcmp(schema, tables->schemas[i])) + return true; + } + + return false; +} + +/** + * Renames a table in 'main' DB from 'table' to 'table_new'. + * @param table Old table name. + * @param table_new New table name. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_rename_table(const char *table, const char *table_new) +{ + int ret; + size_t size = strlen(RENAME_TABLE_STMT) + strlen(table) + strlen(table_new) + 1; + char sql[size]; + + sprintf(sql, RENAME_TABLE_STMT, table, table_new); + ret = db_upgr_process_stmt(sql, NULL, NULL); + + return ret; +} + +/** + * Creates a table in 'main' DB with name 'table' and schema from 'tables_new' + * structure. + * @param table Table to be created. + * @param tables Structure with tables' names and schemas. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_create_table(const char *table, struct tables *tables_new) +{ + unsigned i; // Initilised in for statement + + for (i = 0; i < tables_new->cnt; i++) { + if (0 == strcmp(table, tables_new->names[i])) + return db_upgr_process_stmt(tables_new->schemas[i], NULL, NULL); + } + + dmlog_error("Failed to create table."); + + return -1; +} + +/** + * Reads columns' names and types from a table with provided name and stores + * them in provided structure (struct columns). + * @post Caller must free the appended values and containing arrays. + * @param table Table to be read. + * @param columns Structure holding columns' names and types. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_columns(const char *table, struct columns *columns) +{ + int ret = 0; + sqlite3_stmt *stmt = NULL; + int col_cnt = 0; + int i; // Initilised in for statement + size_t size = strlen(SELECT_STMT) + strlen(table) + 1; + char sql[size]; + + sprintf(sql, SELECT_STMT, table); + + if (SQLITE_OK != sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) { + dmlog_error("Failed to prepare SELECT statement."); + ret = -1; + goto end; + } + + ret = sqlite3_step(stmt); + + /* + * Column names can be fetched even if no rows are returned, so SQLITE_ROW, + * _OK and _DONE are all acceptable. + */ + if (SQLITE_OK != ret && SQLITE_ROW != ret && SQLITE_DONE != ret) { + dmlog_error("Failed to process SELECT statement."); + ret = -1; + goto end; + } + + ret = 0; + + col_cnt = sqlite3_column_count(stmt); + + if (col_cnt < 0) { + dmlog_error("Incorrect columns count from SELECT statement."); + ret = -1; + goto end; + } + + for (i = 0; i < col_cnt; i++) { + const char *name = NULL; + const char *type = NULL; + char **tmp = NULL; + size_t size = 0; + + name = sqlite3_column_origin_name(stmt, i); + + if (NULL == name) { + dmlog_error("NULL column name from SELECT statement."); + ret = -1; + goto end; + } + + if (0 != sqlite3_table_column_metadata(db, "main", table, name, &type, + NULL, NULL, NULL, NULL)) { + dmlog_error("Failed to read column %s data type.", name); + ret = -1; + goto end; + } + + if (NULL == type) { + dmlog_error("Failed to read column %s data type.", name); + ret = -1; + goto end; + } + + tmp = columns->names; + + unsigned int uint_add = 0; + if (UINT_MAX - 1 < columns->cnt) { + dmlog_error("Buffer over flow on addition"); + ret = -1; + goto end; + } else { + uint_add = columns->cnt + 1; + } + + if (uint_add > SIZE_MAX / sizeof(char *)) { + dmlog_error("Buffer overflow on multiplication"); + ret = -1; + goto end; + } + + size_t names_size = uint_add * sizeof(char *); + columns->names = realloc(columns->names, + names_size); + + if (NULL == columns->names) { + dmlog_error("Could not realloc columns names array."); + columns->names = tmp; + ret = -1; + goto end; + } + + columns->names[columns->cnt] = NULL; + + tmp = columns->types; + + size_t types_size = uint_add * sizeof(char *); + columns->types = realloc(columns->types, + types_size); + + if (NULL == columns->types) { + dmlog_error("Could not realloc columns types array."); + columns->types = tmp; + ret = -1; + goto end; + } + + columns->types[columns->cnt] = NULL; + + size = strlen(name) + 1; + + columns->names[columns->cnt] = calloc(size, sizeof(char)); + + if (NULL == columns->names[columns->cnt]) { + dmlog_error("Could not calloc column name."); + ret = -1; + goto end; + } + + strcpy(columns->names[columns->cnt], name); + + size = strlen(type) + 1; + + columns->types[columns->cnt] = calloc(size, sizeof(char)); + + if (NULL == columns->types[columns->cnt]) { + dmlog_error("Could not calloc column type."); + free(columns->names[columns->cnt]); + columns->names[columns->cnt] = NULL; + ret = -1; + goto end; + } + + strcpy(columns->types[columns->cnt], type); + + columns->cnt += 1; + } + +end: + + sqlite3_finalize(stmt); + + return ret; +} + +/** + * Checks if a column of a given 'name' and 'type' exists in a provided + * structure containing columns' names and types. + * @param name Column name to be checked. + * @param type Data type of provided column. + * @param columns Structure containing list of columns' names and types. + * @return true if column found, false otherwise. + */ +static bool db_upgr_check_column_exists(const char *name, const char *type, + struct columns *columns) +{ + unsigned i; // Initilised in for statement + + for (i = 0; i < columns->cnt; i++) { + if (0 == strcmp(name, columns->names[i]) && + 0 == strcasecmp(type, columns->types[i])) + return true; + } + + return false; +} + +/** + * Appends provided column name to a string containing list of columns' names + * and appends COLUMN_LIST_ELEM_PREFIX and COLUMN_LIST_ELEM_SUFFIX in the + * beginning and at the end. + * @post Caller must free the allocated string. + * @param column Column name to be appended. + * @param list String to be extended. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_append_column_list(const char *column, char **list) +{ + char *tmp = NULL; + size_t size = 0; + size_t offset = 0; + + if (NULL != *list) + offset = strlen(*list); + + tmp = *list; + + size = strlen(column) + strlen(COLUMN_LIST_ELEM_PREFIX) + + strlen(COLUMN_LIST_ELEM_SUFFIX) + 1; + + *list = realloc(*list, offset + size); + + if (NULL == *list) { + dmlog_error("Could not realloc common columns list."); + *list = tmp; + return -1; + } + + sprintf(*list + offset, "%s%s%s", COLUMN_LIST_ELEM_PREFIX, column, + COLUMN_LIST_ELEM_SUFFIX); + + return 0; +} + +/** + * Searches for common columns in two structures holding names and types and + * appends a name to the list if a column with a given name and type exists in + * both structures. + * @post Caller must free the allocated string. + * @param column_src Structure holding array of 'new' DB columns' names. + * @param column_dst Structure holding array of 'main' DB columns' names. + * @param common_col String to be extended. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_common_columns(struct columns *columns_src, + struct columns *columns_dst, char **common_col) +{ + unsigned i; // Initilised in for statement + + for (i = 0; i < columns_src->cnt; i++) { + if (false == db_upgr_check_column_exists(columns_src->names[i], + columns_src->types[i], columns_dst)) + continue; + + if (0 != db_upgr_append_column_list(columns_src->names[i], + common_col)) { + dmlog_error("Could not append column %s to list.", + columns_src->names[i]); + + return -1; + } + } + + // Remove last ','. + if (NULL != *common_col && strlen(*common_col) > 0) + (*common_col)[strlen(*common_col)-1] = '\0'; + + return 0; +} + +/** + * Copy all the data from a table in 'new' DB to the same table in 'main' DB. + * @param table Table name. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_clone_data(const char *table) +{ + int ret = 0; + size_t stmt_len = strlen(CLONE_STMT); + size_t table_len = strlen(table); + // Return type of strlen is size_t + size_t size_mul = 0; + + if (table_len > SIZE_MAX / 2) { + dmlog_error("Buffer overflow on multiplication"); + return -1; + } else { + size_mul = 2 * table_len; + } + + size_t size_add = 0; + if (stmt_len > SIZE_MAX - size_mul) { + dmlog_error("Buffer overflow on addition"); + return -1; + } else { + size_add = size_mul + stmt_len; + } + + size_t size = 0; + if (size_add > SIZE_MAX - 1) { + dmlog_error("Buffer overflow on increment"); + return -1; + } else { + size = size_add + 1; + } + + char sql[size]; + + /* + * Prepare and process INSERT INTO ... SELECT ... FROM ... statement to + * clone data from default DB table to new table in running DB. + */ + sprintf(sql, CLONE_STMT, table, table); + + if (0 != db_upgr_process_stmt(sql, NULL, NULL)) { + dmlog_error("Could not process INSERT SELECT * statement."); + ret = -1; + } + return ret; +} + +/** + * Copies all the data from common columns in specified tables from 'table_src' + * to 'table_dst' in 'main' DB. + * @param table_src Source table name. + * @param table_dst Destination table name. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_copy_data(const char *table_src, const char *table_dst) +{ + int ret = 0; + unsigned i = 0; + struct columns columns_src = { 0 }; + struct columns columns_dst = { 0 }; + char *common_col = NULL; + size_t size = 0; + + if (0 != db_upgr_get_columns(table_src, &columns_src)) { + dmlog_error("Could not get source table columns."); + ret = -1; + goto end; + } + + if (0 != db_upgr_get_columns(table_dst, &columns_dst)) { + dmlog_error("Could not get destination table columns."); + ret = -1; + goto end; + } + + if (0 != db_upgr_get_common_columns(&columns_src, &columns_dst, + &common_col)) { + dmlog_error("Could not get common columns names."); + ret = -1; + goto end; + } + + /* + * No common columns - all data from original table will be dropped and + * default DB table data will be used. If all columns (including IDs) get + * dropped, this is equal to creating new table, which did not exist + * previously. Such an action is not likely to happen and should always be + * performed with caution, keeping in mind DB consistency, especially in + * case of tables representing DM child nodes. + */ + if (NULL == common_col) { + dmlog_warn("No common columns - cloning default DB table new.%s data.", + table_dst); + + if (0 != db_upgr_clone_data(table_dst)) { + dmlog_error("Could not clone data from default DB table."); + ret = -1; + goto end; + } + + ret = 0; + goto end; + } + + size = strlen(INSERT_STMT) + 2 * strlen(common_col) + strlen(table_src) + + strlen(table_dst) + 1; + + { + /* This is a separate block because it is not allowed to jump inside the scope + of a variable sized array; see + https://stackoverflow.com/questions/20654191/c-stack-memory-goto-and-jump-into-scope-of-identifier-with-variably-modified + By putting all code that references sql in a separate block this issue is avoided. + */ + char sql[size]; + + sprintf(sql, INSERT_STMT, table_dst, common_col, common_col, table_src); + + if (0 != db_upgr_process_stmt(sql, NULL, NULL)) { + dmlog_error("Could not process INSERT SELECT statement."); + ret = -1; + } + } + +end: + + for (i = 0; i < columns_src.cnt; i++) { + free(columns_src.names[i]); + free(columns_src.types[i]); + } + + for (i = 0; i < columns_dst.cnt; i++) { + free(columns_dst.names[i]); + free(columns_dst.types[i]); + } + + if (columns_src.names) + free(columns_src.names); + if (columns_src.types) + free(columns_src.types); + if (columns_dst.names) + free(columns_dst.names); + if (columns_dst.types) + free(columns_dst.types); + if (common_col) + free(common_col); + + return ret; +} + +static int update_db_version(const char *db_file, int version) +{ + sqlite3 *db; + int ret = sqlite3_open_v2(db_file, &db, SQLITE_OPEN_READWRITE, NULL); + + if (SQLITE_OK != ret) { + dmlog_error("sqlite3_open_v2 failed"); + sqlite3_close_v2(db); + return -1; + } + + char sql[128]; + + snprintf(sql, sizeof(sql), "PRAGMA user_version = %d;", version); + char *zErrMsg; + + ret = sqlite3_exec(db, sql, NULL, NULL, &zErrMsg); + if (ret != SQLITE_OK) { + dmlog_warn("SQL command(%s) error: %s", sql, zErrMsg); + sqlite3_free(zErrMsg); + sqlite3_close_v2(db); + return -1; + } + + sqlite3_close_v2(db); + + return 0; +} + +/** + * Adjusts the schema of a given table in 'main' DB to provided SQL schema and + * copies all the data from columns which are still in use after altering. + * @param table Table name. + * @param tables_new Structure holding tables' names and schemas. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_alter_table(const char *table, struct tables *tables_new) +{ + /* Rename table to TMP_TABLE_NAME to keep the data. */ + if (0 != db_upgr_rename_table(table, TMP_TABLE_NAME)) { + dmlog_error("Could not rename table to temporary name."); + return -1; + } + + /* Create new table with new schema. */ + if (0 != db_upgr_create_table(table, tables_new)) { + dmlog_error("Could not create table with new schema."); + return -1; + } + + /* Copy data from TMP_TABLE_NAME to new table. */ + if (0 != db_upgr_copy_data(TMP_TABLE_NAME, table)) { + dmlog_error("Could not copy data from old to new table."); + return -1; + } + + /* Drop TMP_TABLE_NAME table. */ + if (0 != db_upgr_drop_table(TMP_TABLE_NAME)) { + dmlog_error("Could not remove old table."); + return -1; + } + + return 0; +} + +/** + * Creates an exact copy of a given table from 'new' DB in 'main' DB. + * @param table Table name. + * @param tables_new Structure holding tables' names and schemas. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_clone_table(const char *table, struct tables *tables_new) +{ + /* Create table with new schema. */ + if (0 != db_upgr_create_table(table, tables_new)) { + dmlog_error("Could not create table with new schema."); + return -1; + } + + /* Select all rows from default table and insert into new table. */ + if (0 != db_upgr_clone_data(table)) { + dmlog_error("Could not clone data."); + return -1; + } + + return 0; +} + +/** + * Checks if a 'schema' of given 'table' in 'main' DB has changed in comparison + * to 'new' (default) DB and alters the table or drops it, if it is not used + * any more. + * @post If any changes were done, 'changed' is set to true. + * @param table Table name + * @param schema SQL schema of the table. + * @param tables_new Structure holding tables' names and schemas of 'new' DB. + * @param changed Flag indicating if any change was done. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_check_and_alter_table(const char *table, + const char *schema, struct tables *tables_new, bool *changed) +{ + bool exists = db_upgr_check_table_exists(table, tables_new); + + /* Table exists in both DBs - check if they are equal. */ + if (true == exists && + false == db_upgr_check_tables_equal(table, schema, tables_new)) { + dmlog_info("Tables %s in both DBs not equal - need to alter.", + table); + + if (0 != db_upgr_alter_table(table, tables_new)) { + dmlog_error("Could not alter table."); + return -1; + } + + *changed = true; + } + + /* Table exists in running DB, but not in new one - drop it. */ + if (false == exists) { + dmlog_info("Table %s does not exist in new DB schema - dropping.", + table); + + if (0 != db_upgr_drop_table(table)) { + dmlog_error("Could not drop table."); + return -1; + } + + *changed = true; + } + + return 0; +} + +/** + * Checks if a given 'table' from 'new' DB exists in 'main' DB and clones it if + * it does not. + * @post If any changes were done, 'changed' is set to true. + * @param table Table name + * @param tables_old Structure holding tables' names and schemas of 'main' DB. + * @param tables_new Structure holding tables' names and schemas of 'new' DB. + * @param changed Flag indicating if any change was done. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_check_and_add_table(const char *table, + struct tables *tables_old, struct tables *tables_new, bool *changed) +{ + /* + * Table does not exist in running DB - create it and copy all data from + * new firmware default DB. + */ + if (false == db_upgr_check_table_exists(table, tables_old)) { + dmlog_info("Table %s does not exist - cloning from default DB.", + table); + + if (0 != db_upgr_clone_table(table, tables_new)) { + dmlog_error("Could not clone table."); + return -1; + } + + *changed = true; + } + + return 0; +} + +/** + * Checks if a given table 'parent' contains 'index' IndexOf field contained + * in 'parent_keys'. + * @param parent Table name. + * @param index Index name. + * @param parent_keys Structure containing keys of table. + * @return true if 'parent' contains 'index', false otherwise. + */ +static bool db_upgr_check_dynamic_parent_index(const char *parent, + const char *index, struct keys *parent_keys) +{ + char *tmp = NULL; + const char *p_tab = parent; + unsigned i = 0; + const char *index_no_prefix = index; + + if (*index_no_prefix == '_') + index_no_prefix += 1; + + /* If index is parent's IndexOf, it will be in the end of its name. */ + for (;;) { + tmp = strstr(p_tab, index); + + if (NULL == tmp) + return false; + + if (0 == strcmp(tmp, index)) { + for (i = 0; i < parent_keys->indexes_cnt; i++) { + if (0 == strcmp(index_no_prefix, parent_keys->indexes[i])) + return true; + } + } + + if (strlen(tmp) > 0) { + tmp += 1; + p_tab = tmp; + continue; + } + + return false; + } + /* Return not required, return's from inside for loop */ +} + +/** + * Gets common 'IndexOf...' field name of 'parent' and 'child' tables. + * For example, when we have tables representing dynamic TR objects A.i.B.i.C.i + * (child) and A.i.B.i (parent), their common index is 'IndexOfB'. + * @post If common index is found, it gets allocated under cmn_index and + * caller must free it when done processing. + * @param parent Parent table name. + * @param parent_keys Structure holding parent's indexes. + * @param child Child table name. + * @param child_keys Structure holding child's indexes. + * @param cmn_index Output common index name. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_common_index_of(const char *parent, + struct keys *parent_keys, const char *child, struct keys *child_keys, + char **cmn_index) +{ + unsigned i; // Initialised in for statement + size_t size = 0; + + *cmn_index = NULL; + + for (i = 0; i < child_keys->indexes_cnt; i++) { + char *tmp = NULL; + + size = strlen(CHILD_TABLE_NAME) + strlen(child_keys->indexes[i]) + 1; + + tmp = calloc(size, sizeof(char)); + + if (NULL == tmp) { + dmlog_error("Failed to calloc temporary index name."); + return -1; + } + + sprintf(tmp, CHILD_TABLE_NAME, "", child_keys->indexes[i]); + + if (true == db_upgr_check_dynamic_parent_index(parent, tmp, + parent_keys)) { + free(tmp); + tmp = NULL; + + size = strlen(child_keys->indexes[i]) + 1; + + *cmn_index = calloc(size, sizeof(char)); + + if (NULL == *cmn_index) { + dmlog_error("Failed to calloc index %s.", + child_keys->indexes[i]); + return -1; + } + + strcpy(*cmn_index, child_keys->indexes[i]); + + break; + } + + free(tmp); + } + + return 0; +} + +/** + * Checks if 'index' is own index of 'table'. + * @param table Table name. + * @param index Index name. + * @return true if 'index' is a correct index, false otherwise. + */ +static bool db_upgr_check_own_index(const char *table, const char *index) +{ + char *tmp = NULL; + const char *p_tab = table; + + for (;;) { + tmp = strstr(p_tab, index); + + if (NULL == tmp) + return false; + + if (0 == strcmp(tmp, index)) + return true; + + if (strlen(tmp) > 0) { + tmp += 1; + p_tab = tmp; + continue; + } + + return false; + } + /* Return from insde for loop, no need of return here */ +} + +/** + * Gets own 'IndexOf...' index of table 'child'. For example, if we have a + * table representing dynamic TR object A.i.B.i, its own index is 'IndexOfB'. + * @post If index is found, it gets allocated under own_index and caller must + * free it when done processing. + * @param child Table name. + * @param child_keys Structure holding table's indexes. + * @param own_index Output index name. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_own_index_of(const char *child, struct keys *child_keys, + char **own_index) +{ + unsigned i; // Initialised in for statement + size_t size = 0; + + *own_index = NULL; + + for (i = 0; i < child_keys->indexes_cnt; i++) { + char *tmp = NULL; + + size = strlen(CHILD_TABLE_NAME) + strlen(child_keys->indexes[i]) + 1; + + tmp = calloc(size, sizeof(char)); + + if (NULL == tmp) { + dmlog_error("Failed to calloc temporary index name."); + return -1; + } + + sprintf(tmp, CHILD_TABLE_NAME, "", child_keys->indexes[i]); + + if (true == db_upgr_check_own_index(child, tmp)) { + free(tmp); + tmp = NULL; + + size = strlen(child_keys->indexes[i]) + 1; + + *own_index = calloc(size, sizeof(char)); + + if (NULL == *own_index) { + dmlog_error("Failed to calloc index %s.", + child_keys->indexes[i]); + return -1; + } + + strcpy(*own_index, child_keys->indexes[i]); + + break; + } + + free(tmp); + } + + return 0; +} + +/** + * Gets 'index' values of 'table'. + * @post If values are found, they get allocated under 'values' and caller must + * free them when done processing. + * @param table Table name. + * @param index Index name. + * @param values Output structure containing read values and count. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_index_values(const char *table, const char *index, + struct idx_values *values) +{ + int ret = 0; + sqlite3_stmt *stmt = NULL; + int retval = 0; + unsigned *tmp = NULL; + size_t size = strlen(SELECT_INDEX_STMT) + strlen(table) + strlen(index) + 1; + char sql[size]; + + sprintf(sql, SELECT_INDEX_STMT, index, table); + + if (SQLITE_OK != sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) { + dmlog_error("Failed to prepare SELECT statement."); + ret = -1; + goto end; + } + + for (;;) { + ret = sqlite3_step(stmt); + + if (SQLITE_ROW == ret) { + + if (1 != sqlite3_column_count(stmt) || + SQLITE_INTEGER != sqlite3_column_type(stmt, 0)) { + dmlog_error("Wrong column returned."); + ret = -1; + goto end; + } + + retval = sqlite3_column_int(stmt, 0); + + tmp = values->values; + + unsigned int uint_add = 0; + if (UINT_MAX - 1 < values->cnt) { + dmlog_error("Buffer over flow on addition"); + ret = -1; + goto end; + } else { + uint_add = values->cnt + 1; + } + + if (uint_add > SIZE_MAX / sizeof(unsigned int)) { + dmlog_error("Buffer overflow on multiplication"); + ret = -1; + goto end; + } + + size_t values_size = uint_add * sizeof(unsigned int); + values->values = realloc(values->values, + values_size); + + if (NULL == values->values) { + dmlog_error("Failed to realloc keys values array."); + values->values = tmp; + ret = -1; + goto end; + } + + values->values[values->cnt] = retval; + values->cnt += 1; + + } else if (SQLITE_DONE == ret) { + ret = 0; + goto end; + } else { + dmlog_error("Failure when processing %s statement.", sql); + ret = -1; + goto end; + } + } + +end: + + sqlite3_finalize(stmt); + + return ret; +} + +/** + * Updates 'NextIndexOf...' of 'parent' table, when this table is dynamic. In + * this case, every index of 'parent' is processed on its own as SQLite does + * not allow joining in update statements. Updating is done after checking if + * corresponding 'IndexOf...' are higher than parent's 'NextIndexOf...'. + * @param parent Parent table name. + * @param par_index Parent's index. + * @param par_next_index Parent's 'NextIndexOf...' index. + * @param child Child table name. + * @param child_index Child's index. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_update_dynamic_parent_key(const char *parent, + const char *par_index, const char *par_next_index, const char *child, + const char *child_index) { + unsigned j; // Initialised in for statement + int ret = 0; + struct idx_values values = { 0 }; + size_t size = 0; + int uint_len = 0; + char buf[24] = ""; + + if (0 != db_upgr_get_index_values(parent, par_index, &values)) { + dmlog_error("Failed to get NextIndexOf%s values from %s.", + par_next_index, parent); + ret = -1; + goto end; + } + + for (j = 0; j < values.cnt; j++) { + snprintf(buf, sizeof(buf), "%u", values.values[j]); + uint_len = strlen(buf); + + if (uint_len <= 0) { + dmlog_error("Wrong index value returned."); + ret = -1; + goto end; + } + + size = strlen(KEYS_UPDATE_STMT) + strlen(parent) + + strlen(par_next_index) + + strlen(child_index) + strlen(child) + + strlen(par_index) + strlen(par_next_index) + + strlen(child_index) + strlen(child) + + strlen(par_index) + strlen(par_index) + 3 * uint_len + 1; + + char sql[size]; + + sprintf(sql, KEYS_UPDATE_STMT, parent, par_next_index, child_index, + child, par_index, values.values[j], par_next_index, child_index, + child, par_index, values.values[j], par_index, values.values[j]); + + if (0 != db_upgr_process_stmt(sql, NULL, NULL)) { + dmlog_error("Failed to process statement %s.", sql); + ret = -1; + goto end; + } + + } +end: + + if (values.values) + free(values.values); + + return ret; +} + +/** + * Updates 'NextIndexOf...' of 'parent' table. Updating is done after checking + * if corresponding 'IndexOf...' are higher than parent's 'NextIndexOf...'. + * @param parent Parent table name. + * @param par_index Parent's index. + * @param child Child table name. + * @param child_index Child's index. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_update_static_parent_key(const char *parent, + const char *parent_index, const char *child, const char *child_index) +{ + size_t size = 0; + char *sql = NULL; + int ret = 0; + + size = strlen(KEYS_STATIC_UPDATE_STMT) + strlen(parent) + + strlen(parent_index) + strlen(child_index) + strlen(child) + + strlen(parent_index) + strlen(child_index) + strlen(child) + 1; + + sql = calloc(size, sizeof(char)); + + if (NULL == sql) { + dmlog_error("Failed to calloc keys update statement for table %s.", + parent); + ret = -1; + goto end; + } + + sprintf(sql, KEYS_STATIC_UPDATE_STMT, parent, parent_index, child_index, + child, parent_index, child_index, child); + + if (0 != db_upgr_process_stmt(sql, NULL, NULL)) { + dmlog_error("Failed to process statement %s.", sql); + ret = -1; + goto end; + } + +end: + + if (sql) + free(sql); + + return ret; +} + +/** + * Compares 'parent' and 'child' keys to determine if they are connected and + * updates the values after checking if parent table is dynamic or static. + * @param parent Parent table name. + * @param par_keys Parent's keys. + * @param par_next_index Parent's 'NextIndexOf...' key. + * @param child Child table name. + * @param child_keys Child's keys. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_update_keys(const char *parent, struct keys *par_keys, + const char *par_next_index, const char *child, struct keys *child_keys) +{ + int ret = 0; + char *child_index = NULL; + char *par_index = NULL; + + if (0 != db_upgr_get_own_index_of(child, child_keys, &child_index)) { + dmlog_error("Failed to get index of %s.", child); + ret = -1; + goto end; + } + + if (NULL != child_index && 0 == strcmp(child_index, par_next_index)) { + + if (0 != db_upgr_get_own_index_of(parent, par_keys, &par_index)) { + dmlog_error("Failed to get index of %s.", parent); + ret = -1; + goto end; + } + + /* + * Static parent - set 'NextIndexOf...' once for all children + * 'IndexOf...' max value. + */ + if (NULL == par_index) { + if (0 != db_upgr_update_static_parent_key(parent, par_next_index, + child, child_index)) { + dmlog_error("Failed to update NextIndexOf%s of %s.", + child_index, parent); + ret = -1; + goto end; + } + /* + * Dynamic parent - set 'NextIndexOf...' per parent 'IndexOf...'. + */ + } else { + if (0 != db_upgr_update_dynamic_parent_key(parent, par_index, + par_next_index, child, child_index)) { + dmlog_error("Failed to update NextIndexOf%s of %s.", + child_index, parent); + ret = -1; + goto end; + } + } + } + +end: + if (par_index) + free(par_index); + if (child_index) + free(child_index); + + return ret; +} + +/** + * Updates 'NextIndexOf...' fields of 'parent' if its value is too low in + * context of children 'IndexOf...' values. + * @param parent Parent table name. + * @param tables_new All tables from new DB schema. + * @param par_keys Parent's keys. + * @param keys Keys of all tables. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_update_parent_keys(const char *parent, + struct tables *tables_new, struct keys *parent_keys, struct keys *keys) +{ + int ret = 0; + unsigned i; // Initialised in for statement + unsigned j = 0; + char *child = NULL; + size_t size = 0; + + dmlog_info("Updating 'NextIndexOf...' of %s.", parent); + + for (i = 0; i < parent_keys->next_indexes_cnt; i++) { + size = strlen(CHILD_TABLE_NAME) + strlen(parent) + + strlen(parent_keys->next_indexes[i]) + 1; + + child = calloc(size, sizeof(char)); + + if (NULL == child) { + dmlog_error("Failed to calloc child of %s table name.", parent); + ret = -1; + goto end; + } + + sprintf(child, CHILD_TABLE_NAME, parent, parent_keys->next_indexes[i]); + + if (false == db_upgr_check_table_exists(child, tables_new)) { + free(child); + child = NULL; + continue; + } + + for (j = 0; j < tables_new->cnt; j++) { + if (0 == strcmp(child, tables_new->names[j])) + break; + } + + if (0 != db_upgr_get_update_keys(parent, parent_keys, + parent_keys->next_indexes[i], child, &keys[j])) { + dmlog_error("Failed to update keys of %s.", parent); + ret = -1; + goto end; + } + + free(child); + child = NULL; + } + +end: + + if (child) + free(child); + + return ret; +} + +/** + * Removes rows from 'child' table if their parent does not exist in 'parent' + * table. + * @param parent Parent table name. + * @param child Child table name. + * @param cmn_index Common index of parent and child. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_remove_orphans_of(const char *parent, const char *child, + const char *cmn_index) +{ + size_t size = 0; + char *sql = NULL; + int ret = 0; + + size = strlen(CLEANUP_STMT) + strlen(child) + strlen(parent) + + 2 * strlen(cmn_index) + 1; + + sql = calloc(size, sizeof(char)); + + if (NULL == sql) { + dmlog_error("Failed to calloc cleanup statement."); + ret = -1; + goto end; + } + + sprintf(sql, CLEANUP_STMT, child, cmn_index, cmn_index, + parent); + + if (0 != db_upgr_process_stmt(sql, NULL, NULL)) { + dmlog_error("Failed to process statement %s.", sql); + ret = -1; + goto end; + } + +end: + + if (sql) + free(sql); + + return ret; +} + +/** + * Checks if a common key columns exists in 'parent' and 'child' tables and + * removes orphans if needed. + * @param parent Parent table name. + * @param parent_keys Parent's keys. + * @param child Child table name. + * @param child_keys Child's keys. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_remove_orphans(const char *parent, + struct keys *parent_keys, const char *child, struct keys *child_keys) +{ + int ret = 0; + char *cmn_index = NULL; + + if (0 != db_upgr_get_common_index_of(parent, parent_keys, child, + child_keys, &cmn_index)) { + dmlog_error("Failed to get common index of %s and %s.", child, + parent); + ret = -1; + goto end; + } + + if (NULL != cmn_index) { + if (0 != db_upgr_remove_orphans_of(parent, child, cmn_index)) { + dmlog_error("Failed to remove orphans of %s from %s.", + parent, child); + ret = -1; + goto end; + } + } + +end: + + if (cmn_index) + free(cmn_index); + + return ret; +} + +/** + * Checks if 'parent' table has any children and if those children refer to + * existing parent rows and removes them if not. + * @param parent Parent table name. + * @param tables_new All tables. + * @param parent_keys Parent's keys + * @param keys All keys. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_remove_orphans(const char *parent, + struct tables *tables_new, struct keys *parent_keys, struct keys *keys) +{ + int ret = 0; + unsigned i; // Initialised in for statement + unsigned j = 0; + char *child = NULL; + size_t size = 0; + + dmlog_info("Removing orphans of %s.", parent); + + for (i = 0; i < parent_keys->next_indexes_cnt; i++) { + size = strlen(CHILD_TABLE_NAME) + strlen(parent) + + strlen(parent_keys->next_indexes[i]) + 1; + + child = calloc(size, sizeof(char)); + + if (NULL == child) { + dmlog_error("Failed to calloc child table name of %s.", parent); + ret = -1; + goto end; + } + + sprintf(child, CHILD_TABLE_NAME, parent, parent_keys->next_indexes[i]); + + if (false == db_upgr_check_table_exists(child, tables_new)) { + free(child); + child = NULL; + continue; + } + + for (j = 0; j < tables_new->cnt; j++) { + if (0 == strcmp(child, tables_new->names[j])) { + break; + } + } + + if (0 != db_upgr_get_remove_orphans(parent, parent_keys, child, + &keys[j])) { + dmlog_error("Failed to get and remove orphans of %s.", parent); + ret = -1; + goto end; + } + + free(child); + child = NULL; + } + +end: + + if (child) + free(child); + + return ret; +} + +/** + * Gets all 'IndexOf...' and 'NextIndexOf...' columns of a table and also + * reads level of table - how many 'IndexOf...' columns it contains. + * @post All keys are allocated and caller must free them when done + * processing. + * @param table Table name. + * @param columns_new All columns of a table. + * @param level Output level of table. + * @param keys Output set of keys of table. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_get_table_keys(const char *table, + struct columns *columns_new, unsigned *level, struct keys *keys) +{ + unsigned i; // Initialised in for statement + + for (i = 0; i < columns_new->cnt; i++) { + char *name = columns_new->names[i]; + int pk = 0; + size_t size = 0; + char **tmp = NULL; + + if (0 == strncmp(name, "IndexOf", 7) && strlen(name) > 7 && + 0 == strcasecmp(columns_new->types[i], "integer")) { + + if (0 != sqlite3_table_column_metadata(db, "main", table, + name, NULL, NULL, NULL, &pk, NULL)) { + dmlog_error("Failed to get %s field properties.", + name); + return -1; + } + + if (0 == pk) + continue; + + name += 7; + + tmp = keys->indexes; + + unsigned int uint_add = 0; + if (UINT_MAX - 1 < keys->indexes_cnt) { + dmlog_error("Buffer over flow on addition"); + return -1; + } else { + uint_add = keys->indexes_cnt + 1; + } + + if (uint_add > SIZE_MAX / sizeof(char *)) { + dmlog_error("Buffer overflow on multiplication"); + return -1; + } + + size_t k_index_size = uint_add * sizeof(char *); + keys->indexes = realloc(keys->indexes, + k_index_size); + + if (NULL == keys->indexes) { + dmlog_error("Failed to realloc keys array."); + keys->indexes = tmp; + return -1; + } + + keys->indexes[keys->indexes_cnt] = NULL; + + size = strlen(name) + 1; + + keys->indexes[keys->indexes_cnt] = calloc(size, sizeof(char)); + + if (NULL == keys->indexes[keys->indexes_cnt]) { + dmlog_error("Failed to calloc index %s.", name); + return -1; + } + + strcpy(keys->indexes[keys->indexes_cnt], name); + keys->indexes_cnt += 1; + + *level += 1; + + continue; + } + + if (0 == strncmp(name, "NextIndexOf", 11) && strlen(name) > 11 && + 0 == strcasecmp(columns_new->types[i], "integer")) { + + name += 11; + + tmp = keys->next_indexes; + + unsigned int uint_add = 0; + if (UINT_MAX - 1 < keys->next_indexes_cnt) { + dmlog_error("Buffer over flow on addition"); + return -1; + } else { + uint_add = keys->next_indexes_cnt + 1; + } + + if (uint_add > SIZE_MAX / sizeof(char *)) { + dmlog_error("Buffer overflow on multiplication"); + return -1; + } + + size_t k_next_index_size = uint_add * sizeof(char *); + + keys->next_indexes = realloc(keys->next_indexes, + k_next_index_size); + + if (NULL == keys->next_indexes) { + dmlog_error("Failed to realloc keys array."); + keys->next_indexes = tmp; + return -1; + } + + keys->next_indexes[keys->next_indexes_cnt] = NULL; + + size = strlen(name) + 1; + + keys->next_indexes[keys->next_indexes_cnt] = calloc(size, + sizeof(char)); + + if (NULL == keys->next_indexes[keys->next_indexes_cnt]) { + dmlog_error("Failed to calloc next_index %s.", name); + return -1; + } + + strcpy(keys->next_indexes[keys->next_indexes_cnt], name); + keys->next_indexes_cnt += 1; + } + } + + return 0; +} + +/** + * This function removes orphans (rows which refer to non existing parent rows) + * and updates all 'NextIndexOf...' fields. If any value is too low in context + * of max 'IndexOf...' field of a child, parent's 'NextIndexOf...' gets set + * to [max(IndexOfChild) + 1]. Orphans are removed before updating parent + * keys. Keys checking, orphans removal and updating is done per level of a + * table, starting from lowest level. + * @param tables_new All tables. + * @return 0 on success, -1 on failure. + */ +static int db_upgr_check_and_update_keys(struct tables *tables_new) +{ + int ret = 0; + unsigned i; // Initialised in for statement + unsigned j = 0; + struct columns columns_new = { 0 }; + unsigned *levels = NULL; + struct keys *keys = NULL; + unsigned level_max = 0; + + dmlog_info("Removing orphans and updating 'NextIndexOf' values..."); + + levels = calloc(tables_new->cnt, sizeof(unsigned)); + + if (NULL == levels) { + dmlog_error("Could not calloc tables levels array."); + ret = -1; + goto end; + } + + keys = calloc(tables_new->cnt, sizeof(*keys)); + + if (NULL == keys) { + dmlog_error("Could not calloc keys array."); + ret = -1; + goto end; + } + + for (i = 0; i < tables_new->cnt; i++) { + if (0 != db_upgr_get_columns(tables_new->names[i], &columns_new)) { + dmlog_error("Failed to get columns of table %s.", + tables_new->names[i]); + ret = -1; + goto end; + } + + if (0 != db_upgr_get_table_keys(tables_new->names[i], &columns_new, + &levels[i], &keys[i])) { + dmlog_error("Failed to get keys of table %s.", + tables_new->names[i]); + ret = -1; + goto end; + } + + if (levels[i] > level_max) + level_max = levels[i]; + + for (j = 0; j < columns_new.cnt; j++) { + free(columns_new.names[j]); + free(columns_new.types[j]); + } + + /* Some older implementations of free may not check NULL, + * so it is a good idea to check before we free (just like + * it is done later in this function). + */ + if (columns_new.names) + free(columns_new.names); + + if (columns_new.names) + free(columns_new.types); + + memset(&columns_new, 0, sizeof(columns_new)); + } + + /* + * Remove orphans first, before updating 'NextIndexOf...' as this might + * effect the maximum values of corresponding 'IndexOf...'. Check only + * tables which are dynamic (level > 0) and may be parents + * (level < max). If a table has level 0 it may be a parent of + * dynamic child, but is a static table and so its children can never be + * orphans. + */ + for (j = 1; j < level_max; j++) { + for (i = 0; i < tables_new->cnt; i++) { + if (levels[i] != j) + continue; + + if (0 != db_upgr_remove_orphans(tables_new->names[i], tables_new, + &keys[i], keys)) { + dmlog_error("Failed to remove orphans of table %s.", + tables_new->names[i]); + ret = -1; + goto end; + } + } + } + + /* + * After adjusting DB schema there is a chance that 'NextIndexOf...' value + * of parent may be invalid - update 'NextIndexOf...' to max corresponding + * 'IndexOf...' + 1 if that value is lower. Process all tables that may be + * parents (level < max), both static and dynamic (level >= 0). + */ + for (j = 0; j < level_max; j++) { + for (i = 0; i < tables_new->cnt; i++) { + if (levels[i] != j) + continue; + + if (0 != db_upgr_update_parent_keys(tables_new->names[i], + tables_new, &keys[i], keys)) { + dmlog_error("Failed to update 'NextIndexOf...' of table %s.", + tables_new->names[i]); + ret = -1; + goto end; + } + } + } + +end: + + for (i = 0; i < columns_new.cnt; i++) { + free(columns_new.names[i]); + free(columns_new.types[i]); + } + + if (columns_new.names) + free(columns_new.names); + if (columns_new.names) + free(columns_new.types); + + if (keys) { + for (i = 0; i < tables_new->cnt; i++) { + for (j = 0; j < keys[i].indexes_cnt; j++) { + if (keys[i].indexes[j]) { + free(keys[i].indexes[j]); + } + } + if (keys[i].indexes) + free(keys[i].indexes); + for (j = 0; j < keys[i].next_indexes_cnt; j++) { + free(keys[i].next_indexes[j]); + } + if (keys[i].next_indexes) + free(keys[i].next_indexes); + } + + free(keys); + } + + if (levels) + free(levels); + + return ret; +} + +int upgrade_db(const char *curr_db, const char *new_db, int new_version) +{ + int ret = 0; + unsigned i; // Initialised in for statement + struct tables tables_old = { 0 }; + struct tables tables_new = { 0 }; + /* Used to avoid unnecessary committing and writing to UBIFS. */ + bool changed = false; + + dmlog_info("DB upgrade started..."); + + /* Open DB as 'main'. */ + if (SQLITE_OK != sqlite3_open_v2(curr_db, &db, SQLITE_OPEN_READWRITE, + NULL)) { + dmlog_error("Could not open running DB file."); + ret = -1; + goto end; + } + + char *attach_sql = NULL; + asprintf(&attach_sql, "attach database '%s' as new;", new_db); + /* Attach new DB as 'new'. */ + ret = db_upgr_process_stmt(attach_sql, NULL, NULL); + free(attach_sql); + if (0 != ret) { + dmlog_error("Could not attach default DB file."); + ret = -1; + goto end; + } + + if (0 != db_upgr_begin_trans()) { + dmlog_error("Could not start transaction."); + ret = -1; + goto end; + } + + /* Get tables' names and full schema from both DBs. */ + if (0 != db_upgr_get_tables(&tables_old, &tables_new)) { + dmlog_error("Could not get tables names and schemas."); + ret = -1; + goto end; + } + + /* + * Loop through all tables from running DB to determine if they need to be + * dropped or modified. + */ + for (i = 0; i < tables_old.cnt; i++) { + if (0 != db_upgr_check_and_alter_table(tables_old.names[i], + tables_old.schemas[i], &tables_new, &changed)) { + dmlog_error("Could not alter or drop table."); + ret = -1; + goto end; + } + } + + /* + * Loop through all tables from new default DB to determine if they need to + * be added to running DB. + */ + for (i = 0; i < tables_new.cnt; i++) { + if (0 != db_upgr_check_and_add_table(tables_new.names[i], + &tables_old, &tables_new, &changed)) { + dmlog_error("Could not add table."); + ret = -1; + goto end; + } + } + + /* + * After schema is upgraded and all data is copied, keys must be checked + * and updated accordingly to remove orphan rows and update + * 'NextIndexOf...' fields. + */ + if (true == changed && 0 != db_upgr_check_and_update_keys(&tables_new)) { + dmlog_error("Could not update constraints."); + ret = -1; + goto end; + } + + /* Commit transaction only if any changes were done. */ + if (true == changed && 0 != db_upgr_commit_trans()) { + dmlog_error("Could not commit transaction."); + ret = -1; + goto end; + } + + /* Closing DB while transaction is on reverts it. */ + if (SQLITE_OK != sqlite3_close(db)) { + db = NULL; + dmlog_error("Could not close DB."); + ret = -1; + goto end; + } + + db = NULL; + + if (update_db_version(curr_db, new_version) < 0) { + dmlog_error("update_db_version failed"); + return -1; + } + +end: + + dmlog_info("DB upgrade finished %s changes (result: %d).", + ((true == changed) ? ("with") : ("without")), ret); + + sqlite3_close(db); + db = NULL; + + for (i = 0; i < tables_old.cnt; i++) { + free(tables_old.names[i]); + free(tables_old.schemas[i]); + } + + if (tables_old.names) + free(tables_old.names); + if (tables_old.schemas) + free(tables_old.schemas); + + for (i = 0; i < tables_new.cnt; i++) { + free(tables_new.names[i]); + free(tables_new.schemas[i]); + } + + if (tables_new.names) + free(tables_new.names); + if (tables_new.schemas) + free(tables_new.schemas); + + return ret; +} diff --git a/dm-framework/dm-api/src/core/db_upgrade.h b/dm-framework/dm-api/src/core/db_upgrade.h new file mode 100644 index 000000000..14c066c8c --- /dev/null +++ b/dm-framework/dm-api/src/core/db_upgrade.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DB_UPGRADE_H +#define DB_UPGRADE_H + +int get_db_user_version(const char *db_path, int *user_version); +int upgrade_db(const char *curr_db, const char *new_db, int new_version); +#endif diff --git a/dm-framework/dm-api/src/core/dbmgr.c b/dm-framework/dm-api/src/core/dbmgr.c new file mode 100644 index 000000000..1e2fb7bcc --- /dev/null +++ b/dm-framework/dm-api/src/core/dbmgr.c @@ -0,0 +1,681 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "dbmgr.h" + +#include +#include +#include +#include + +#include "dm.h" +#include "dm_log.h" +#include "dm_node.h" +#include "sqlite3.h" + +#define MAX_SQL_STATEMENT_LEN 4096 +static sqlite3 *transaction_db = NULL; +static char sql_statement[MAX_SQL_STATEMENT_LEN]; + +// sqlite busy handler: the SQLITE_BUSY could happen when one process trys to access db +// (read out of session in our case) while another process is in the process of commiting +// a transaction. the busy time is normally short within 100 ms. +static int db_busy_handler(void *context, int count) +{ + dmlog_debug("db_busy_handler %s, %d", sql_statement, count); + // non-zero means retry.. + return 1; +} + +static char *_get_node_rel_path_name(const struct dm_node_info *head, + const struct dm_node_info *node) +{ + static char _path_name[512] = {'\0'}; + + if (node != NULL && node != head) { + _get_node_rel_path_name(head, node->parent); + } + + if ((node == head) || (node == NULL)) /* end of recursion make sure pathname is the empty string */ + { + memset(_path_name, 0, sizeof(_path_name)); + } else { + strcat(_path_name, node->name); + strcat(_path_name, "_"); + } + + return _path_name; +} + +static const char *get_node_rel_path_name(const struct dm_node_info *head, + const struct dm_node_info *node) +{ + char *name = _get_node_rel_path_name(head, node); + int len = strlen(name); + // remove last '_' + if (len > 0) { + name[len - 1] = '\0'; + } + + return name; +} + +static const char *get_table_field_name(const struct dm_node_info *node) +{ + const struct dm_node_info *p = node; + + while (p->parent != dm_node_get_root()) { + if (p->type == DM_NODE_OBJECT_LIST) { + break; + } + p = p->parent; + } + + return get_node_rel_path_name(p, node); +} + +static const char *get_int_str(int i) +{ + static char digit[16]; + + sprintf(digit, "%d", i); + return digit; +} + +static void get_db_index_condition(const struct dm_node_info *info, char *buf, const dm_index_t indexes[], int *cnt) +{ + if (info == NULL) { + return; + } + + if (info != dm_node_get_root()) { + get_db_index_condition(info->parent, buf, indexes, cnt); + } + + if (info->type == DM_NODE_OBJECT_LIST && indexes[*cnt] > 0) { + if (*cnt > 0) { + strcat(buf, " AND "); + } + strcat(buf, "\"IndexOf"); + strcat(buf, info->name); + strcat(buf, "\""); + + strcat(buf, "="); + strcat(buf, get_int_str(indexes[*cnt])); + (*cnt)++; + } +} + +static void get_db_index_field(const struct dm_node_info *info, char *buf, int *cnt) +{ + if (info == NULL) + return; + + if (info != dm_node_get_root()) + get_db_index_field(info->parent, buf, cnt); + + if (info->type == DM_NODE_OBJECT_LIST) { + if (*cnt > 0) { + strcat(buf, ","); + } + strcat(buf, "\"IndexOf"); + strcat(buf, info->name); + strcat(buf, "\""); + (*cnt)++; + } +} + +static const char *get_query_sql(const dm_node_t *node, int max_in_list) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + + if (info == NULL) { + dmlog_error("get_query_sql, node id not found"); + return NULL; + } + + strcpy(sql_statement, "select "); + if (dm_node_is_parameter(node->id)) { + if (max_in_list) + strcat(sql_statement, "MAX("); + strcat(sql_statement, "\""); + strcat(sql_statement, get_table_field_name(info)); + strcat(sql_statement, "\""); + if (max_in_list) + strcat(sql_statement, ")"); + } else if (dm_node_is_objectlist(node->id)) { + const char *node_name = dm_node_name(node->id); + if (node_name == NULL) + return NULL; + + strcat(sql_statement, "\"IndexOf"); + strcat(sql_statement, node_name); + strcat(sql_statement, "\""); + } else { + return NULL; + } + + strcat(sql_statement, " FROM "); + + strcat(sql_statement, dm_node_get_table_name(info)); + + if (node->cnt > 0) { + strcat(sql_statement, " WHERE "); + int cnt = 0; + + if (max_in_list) { + // start from parant of the object. + info = dm_node_get_info(dm_node_id_parent(dm_node_id_parent(node->id))); + } + get_db_index_condition(info, sql_statement, node->index, &cnt); + } + + strcat(sql_statement, ";"); + return sql_statement; +} + +static const char *get_query_all_indexes_sql(const dm_node_t *node, const char *keys, int sort) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + + if (info == NULL) { + dmlog_error("get_query_all_indexes_sql, node id not found"); + return NULL; + } + + if (dm_node_is_counter(node->id)) { + dm_node_id_t counter = dm_node_counter_id(node->id); + + if (counter == INVALID_DM_NODE_ID) { + dmlog_error("invalid counter id %d", counter); + return NULL; + } + + info = dm_node_get_info(counter); + if (info == NULL) { + dmlog_error("info is NULL"); + return NULL; + } + + strcpy(sql_statement, "select COUNT(\"IndexOf"); + strcat(sql_statement, info->name); + strcat(sql_statement, "\")"); + } else { + strcpy(sql_statement, "select"); + strcat(sql_statement, " IndexOf"); + strcat(sql_statement, info->name); + + int index_cnt = dm_node_index_cnt(node->id); + if (index_cnt > 1 && node->cnt < index_cnt - 1) { + const struct dm_node_info *pinfo = dm_node_get_info(dm_node_i_parent_id(node->id)); + if (pinfo) { + strcat(sql_statement, ",IndexOf"); + strcat(sql_statement, pinfo->name); + } else { + dmlog_error("get_query_all_indexes_sql, invalid node: %s", dm_node_str(node)); + } + } + struct dm_object *obj = (struct dm_object *)info; + if (obj->key_param_names != NULL) { + strcat(sql_statement, ",_key"); + char *tmp = strdup(obj->key_param_names); + char *key = strtok(tmp, ","); + while (key != NULL) { + strcat(sql_statement, ",\""); + strcat(sql_statement, key); + strcat(sql_statement, "\""); + key = strtok(NULL, ","); + } + + free(tmp); + } + } + + strcat(sql_statement, " FROM "); + strcat(sql_statement, dm_node_get_table_name(info)); + + if (dm_node_index_cnt(info->parent->node_id) > 0 && node->cnt > 0) { + strcat(sql_statement, " WHERE "); + int cnt = 0; + + get_db_index_condition(info->parent, sql_statement, node->index, &cnt); + if (keys != NULL && keys[0] != '\0') { + strcat(sql_statement, " AND "); + } + } else if (keys != NULL && keys[0] != '\0') { + strcat(sql_statement, " WHERE "); + } + + if (keys != NULL && keys[0] != '\0') { + strcat(sql_statement, keys); + } + + if (sort && info->flag & FLAG_HAS_ORDER) + strcat(sql_statement, " ORDER BY \"Order\""); + + strcat(sql_statement, ";"); + return sql_statement; +} + +// The SQL standard specifies that single-quotes in strings are escaped +// by putting two single quotes in a row. +static void strcat_str_data(char *dest, const char *data) +{ + int len = strlen(dest); + + for (; *data != '\0'; data++, len++) { + if (*data == '\'') { + dest[len] = '\''; + len++; + dest[len] = '\''; + } else { + dest[len] = *data; + } + } + + dest[len] = '\0'; +} + +static const char *get_update_sql(const dm_node_t *node, const char *data) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + + if (info == NULL) { + dmlog_error("get_update_sql, node id not found"); + return NULL; + } + + strcpy(sql_statement, "UPDATE "); + strcat(sql_statement, dm_node_get_table_name(info)); + strcat(sql_statement, " SET \""); + strcat(sql_statement, get_table_field_name(info)); + strcat(sql_statement, "\" = "); + + strcat(sql_statement, "\'"); + if (strchr(data, '\'') != NULL) + // containing single quotes, needs special handling. + strcat_str_data(sql_statement, (const char *)data); + else + strcat(sql_statement, (const char *)data); + + strcat(sql_statement, "\'"); + + if (node->cnt > 0) { + strcat(sql_statement, " WHERE "); + int cnt = 0; + + get_db_index_condition(info, sql_statement, node->index, &cnt); + } + + strcat(sql_statement, ";"); + + return sql_statement; +} + +static int db_next_index_callback(void *context, int argc, char *argv[], char *cols[]) +{ + (void)argc; + (void)cols; + + int *next_index = (int *)context; + + if (argv[0]) { + *next_index = atoi(argv[0]); + } + + return 0; +} + +static int exec_sql(const char *sql, sqlite_callback callback, void *context) +{ + if (transaction_db == NULL) { + dmlog_error("db is not opened"); + return -1; + } + + char *err_msg = NULL; + int ret = sqlite3_exec(transaction_db, sql, callback, context, &err_msg); + if (ret != SQLITE_OK) { + dmlog_error("SQL command(%s) error: %s", sql, err_msg); + sqlite3_free(err_msg); + return -1; + } + + return 0; +} + +// sql in transaction +static int exec_trans_sql(const char *sql, sqlite_callback callback, void *context) +{ + return exec_sql(sql, callback, context); +} + +static int get_object_next_index(const dm_node_t *node) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + if (info == NULL) + return -1; + + const struct dm_node_info *p = info->parent; + + while (p != dm_node_get_root() && p->parent != dm_node_get_root()) { + if (p->type == DM_NODE_OBJECT_LIST) { + break; + } + p = p->parent; + } + + strcpy(sql_statement, "select "); + strcat(sql_statement, "\"NextIndexOf"); + strcat(sql_statement, get_node_rel_path_name(p, info)); + + strcat(sql_statement, "\" FROM "); + + if (p->parent == dm_node_get_root()) { + strcat(sql_statement, dm_node_get_root()->name); + strcat(sql_statement, "_"); + strcat(sql_statement, p->name); + } else { + const char *tb = dm_node_get_table_name(p); + if (tb == NULL) { + dmlog_error("no db table for node %s", dm_node_str(node)); + return -1; + } + strcat(sql_statement, tb); + + if (dm_node_index_cnt(node->id) > 1) { + strcat(sql_statement, " WHERE "); + int cnt = 0; + + get_db_index_condition(info->parent, sql_statement, node->index, &cnt); + } + } + + strcat(sql_statement, ";"); + + int next_index = -1; + + exec_sql(sql_statement, db_next_index_callback, &next_index); + + return next_index; +} + +static int update_object_next_index(const dm_node_t *node, int next_index) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + if (info == NULL) + return -1; + + const struct dm_node_info *p = info->parent; + + while (p != dm_node_get_root() && p->parent != dm_node_get_root()) { + if (p->type == DM_NODE_OBJECT_LIST) { + break; + } + p = p->parent; + } + + strcpy(sql_statement, "UPDATE "); + + if (p->parent == dm_node_get_root()) { + strcat(sql_statement, dm_node_get_root()->name); + strcat(sql_statement, "_"); + strcat(sql_statement, p->name); + } else { + strcat(sql_statement, dm_node_get_table_name(p)); + } + + strcat(sql_statement, " SET \"NextIndexOf"); + strcat(sql_statement, get_node_rel_path_name(p, info)); + + strcat(sql_statement, "\" = "); + strcat(sql_statement, get_int_str(next_index)); + + if (dm_node_index_cnt(node->id) > 1) { + strcat(sql_statement, " WHERE "); + int cnt = 0; + + get_db_index_condition(info->parent, sql_statement, node->index, &cnt); + } + + strcat(sql_statement, ";"); + + return exec_trans_sql(sql_statement, NULL, NULL); +} + +static const char *get_insert_sql(const dm_node_t *node, int new_index) +{ + int i; + int cnt = 0; + + const struct dm_node_info *info = dm_node_get_info(node->id); + + if (info == NULL) { + dmlog_error("get_insert_sql, node id not found"); + return NULL; + } + + strcpy(sql_statement, "insert INTO "); + strcat(sql_statement, dm_node_get_table_name(info)); + strcat(sql_statement, " ("); + + get_db_index_field(info, sql_statement, &cnt); + strcat(sql_statement, ") VALUES("); + + cnt = dm_node_index_cnt(node->id); + for (i = 0; i < cnt - 1; i++) { + strcat(sql_statement, get_int_str(node->index[i])); + strcat(sql_statement, ","); + } + + strcat(sql_statement, get_int_str(new_index)); + + strcat(sql_statement, ");"); + + return sql_statement; +} + +static const char *get_delete_sql_condition(const dm_node_t *node) +{ + static char condition[256]; + const struct dm_node_info *info = dm_node_get_info(node->id); + + if (info == NULL) { + dmlog_error("get_delete_sql_condition, node id not found"); + return NULL; + } + + if (node->cnt > 0) { + strcpy(condition, " WHERE "); + int cnt = 0; + + get_db_index_condition(info, condition, node->index, &cnt); + } else { + return NULL; + } + + return condition; +} + +int dbmgr_init(const char *db_file) +{ + int ret = sqlite3_open_v2(db_file, &transaction_db, SQLITE_OPEN_READWRITE, NULL); + if (SQLITE_OK != ret || transaction_db == NULL) { + dmlog_error("sqlite3_open_v2 failed"); + return -1; + } + sqlite3_busy_timeout(transaction_db, SQLITE_BUSY_TIMEOUT); + sqlite3_busy_handler(transaction_db, db_busy_handler, NULL); + + dmlog_info("dbmgr_init OK"); + return 0; +} + +int dbmgr_finalize(void) +{ + if (transaction_db) { + sqlite3_close(transaction_db); + transaction_db = NULL; + } + + return 0; +} + +int dbmgr_tranx_begin(void) +{ + return exec_trans_sql("BEGIN;", NULL, NULL); +} + +int dbmgr_tranx_revert(void) +{ + return exec_trans_sql("ROLLBACK;", NULL, NULL); +} + +int dbmgr_tranx_commit(void) +{ + int ret = exec_trans_sql("COMMIT;", NULL, NULL); + + if (ret != 0) { + dmlog_error("dbmgr_tranx_commit, sql COMMIT; failed"); + return -1; + } + + return 0; +} + +static int db_get_data_callback(void *context, int cnt, char *data[], char *names[]) +{ + if (cnt <= 0 || data[0] == NULL) { + dmlog_debug("db_get_data_callback, no data"); + return 0; + } + + char **val = context; + *val = strdup(data[0]); + return 0; +} + +static int _dbmgr_get(const dm_node_t *node, char **value, int get_max_in_list) +{ + const char *sql = get_query_sql(node, get_max_in_list); + + if (sql == NULL) { + return -1; + } + + return exec_sql(sql, db_get_data_callback, value); +} + +int dbmgr_get(const dm_node_t *node, char **value) +{ + int retval = _dbmgr_get(node, value, 0); + return retval; +} + +int dbmgr_get_child(const dm_node_t *node, const char *child_name, char **value) +{ + dm_node_t child_node = *node; + child_node.id = dm_node_get_child_id(node->id, child_name); + return dbmgr_get(&child_node, value); +} + +int dbmgr_get_max(const dm_node_t *node, unsigned int *max) +{ + char *value = NULL; + if (_dbmgr_get(node, &value, 1) < 0 || value == NULL) { + return -1; + } + *max = atoi(value); + free(value); + return 0; +} + +int dbmgr_set(const dm_node_t *node, const char *data) +{ + const char *sql = get_update_sql(node, data); + int ret = exec_trans_sql(sql, NULL, NULL); + + if (ret != 0) + return -1; + + return 0; +} + +int dbmgr_set_uint(const dm_node_t *node, unsigned int data) +{ + char *str; + asprintf(&str, "%u", data); + + int ret = dbmgr_set(node, str); + free(str); + return ret; +} + +int dbmgr_get_next_free_index(const dm_node_t *node) +{ + return get_object_next_index(node); +} + +int dbmgr_add(const dm_node_t *node) +{ + int next_index = get_object_next_index(node); + + if (next_index <= 0) { + dmlog_error("dbmgr_add Error: next_index is %d", next_index); + return 0; + } + + const char *sql = get_insert_sql(node, next_index); + + int ret = exec_trans_sql(sql, NULL, NULL); + + if (ret != 0) + return -1; + + if (update_object_next_index(node, next_index + 1) != 0) { + return 0; // 0 means invalid index + } + + return next_index; +} + +static int delete_db_instances(const struct dm_node_info *info, const char *conditions) +{ + if (info == NULL) { + dmlog_info("delete_db_instances node not found"); + return -1; + } + + const char *table_name = dm_node_get_table_name(info); + if (info->type == DM_NODE_OBJECT_LIST && table_name) { + strcpy(sql_statement, "DELETE FROM "); + strcat(sql_statement, table_name); + if (conditions != NULL && *conditions != '\0') { + strcat(sql_statement, conditions); + } + strcat(sql_statement, ";"); + + return exec_trans_sql(sql_statement, NULL, NULL); + } + + return 0; +} + +int dbmgr_del(const dm_node_t *node) +{ + return delete_db_instances(dm_node_get_info(node->id), get_delete_sql_condition(node)); +} + +int dbmgr_query_indexes(const dm_node_t *node, const char *keys, sqlite_callback cb, void *context, int sort) +{ + const char *sql = get_query_all_indexes_sql(node, keys, sort); + return exec_sql(sql, cb, context); +} diff --git a/dm-framework/dm-api/src/core/dbmgr.h b/dm-framework/dm-api/src/core/dbmgr.h new file mode 100644 index 000000000..93e4861fa --- /dev/null +++ b/dm-framework/dm-api/src/core/dbmgr.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DBMGR_H +#define DBMGR_H + +#include "dm_types.h" + +typedef int (*sqlite_callback)(void *, int, char *[], char *[]); + +int dbmgr_init(const char *db_file); +int dbmgr_finalize(void); + +int dbmgr_tranx_begin(void); +int dbmgr_tranx_revert(void); +int dbmgr_tranx_commit(void); + +int dbmgr_get(const dm_node_t *node, char **value); +int dbmgr_get_child(const dm_node_t *node, const char *child_name, char **value); +int dbmgr_get_max(const dm_node_t *node, unsigned int *max); + +int dbmgr_set(const dm_node_t *node, const char *data); +int dbmgr_set_uint(const dm_node_t *node, unsigned int data); + +int dbmgr_add(const dm_node_t *node); +int dbmgr_del(const dm_node_t *node); +int dbmgr_query_indexes(const dm_node_t *node, const char *keys, sqlite_callback cb, void *context, + int sort); +int dbmgr_get_next_free_index(const dm_node_t *node); + +#endif /* DBMGR_H */ diff --git a/dm-framework/dm-api/src/core/dm_api.c b/dm-framework/dm-api/src/core/dm_api.c new file mode 100644 index 000000000..df469df75 --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_api.c @@ -0,0 +1,1494 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "dm_api.h" + +#include +#include +#include +#include +#include +#include + +#include "dbmgr.h" +#include "dm_apply.h" +#include "dm_log.h" +#include "inode_buf.h" +#include "db_upgrade.h" +#include "qjs.h" +#include "utils.h" + +extern int importDM(); + +#define DEFFAULT_DB_PATH "/etc/default_dm.db" +#define DB_PATH "/etc/dm.db" +#define BACKUP_DB_PATH "/etc/dm-backup.db" +#define NEW_DB_PATH "/etc/dm-new.db" + +#define INODE_BUF_EXPIRE_TIME 2000 // in ms +#define NODELIST_DEFAULT_CNT 32 + +enum SESSION_STATE { + SESSION_STATE_IDLE, + SESSION_STATE_STARTED, + SESSION_STATE_MODIFYING +}; + +struct dmapi_context { + // will start session automatically when set/add/del are called + int session_on_fly; + int session_state; + int import_mode; +}; + +struct nodelist_struct { + int index_capacity; + dm_node_t node; + dm_index_t *db_indices; // index in db + dm_index_t *db_indices2; // index in db + unsigned int db_index_cnt; // number of index in db + dm_index_t dynamic_base; // dynamic start index + unsigned int dynamic_cnt; // dynamic end index + unsigned int iterator; // used for first&next; + JSValue *db_keys; +}; + +static struct dmapi_context global_ctx; + +static int import_default_uci(const char *db_file) +{ + if (copy_file(DEFFAULT_DB_PATH, db_file) < 0) { + dmlog_error("copy_file failed."); + return -1; + } + + global_ctx.import_mode = 1; + if (dbmgr_init(db_file) != 0) { + dmlog_error("dbmgr_init failed"); + return -1; + } + + dmapi_session_start(); + // import DMs defined in JSON file + importDM(); + dmapi_session_end(TRANX_COMMIT); + global_ctx.import_mode = 0; + dmlog_info("imported UCI to db %s sucessfully.", db_file); + return 0; +} + +int dmapi_init() +{ + memset(&global_ctx, 0, sizeof(struct dmapi_context)); + global_ctx.session_state = SESSION_STATE_IDLE; + + dmlog_init("dmapi", 0); + dmlog_debug("dmapi_init start"); + + if (qjs_init() != 0) { + dmlog_error("qjs_init failed"); + return -1; + } + + if (access(DB_PATH, F_OK) != 0) { + dmlog_debug("Start importing UCI into DB"); + if (import_default_uci(DB_PATH) < 0) { + dmlog_error("import_default_uci failed (%s).", DB_PATH); + return -1; + } + dmlog_debug("End of importing UCI into DB"); + } else { + // try to do db upgrade + int version, new_version; + int ret = get_db_user_version(DB_PATH, &version); + if (ret != 0) { + dmlog_error("failed to get user version of current db."); + return -1; + } + + ret = get_db_user_version(DEFFAULT_DB_PATH, &new_version); + if (ret != 0) { + dmlog_error("failed to get user version of default db."); + return -1; + } + + if (version != new_version) { + dmlog_debug("will do db upgrade(old db ver: %d, new db ver: %d)", version, new_version); + // generate the new db by calling import handlers and merge it into current db. + if (import_default_uci(NEW_DB_PATH) < 0) { + dmlog_error("import_default_uci failed (%s).", NEW_DB_PATH); + return -1; + } + // close the db used in import_default_uci + dbmgr_finalize(); + // backup the current db + if (copy_file(DB_PATH, BACKUP_DB_PATH) < 0) { + dmlog_error("failed to backup db."); + return -1; + } + + if (upgrade_db(DB_PATH, NEW_DB_PATH, new_version) < 0) { + // failsafe to use the new generated db + dmlog_error("upgrade_db failed, use new imported db instead"); + copy_file(NEW_DB_PATH, DB_PATH); + } + remove(NEW_DB_PATH); + } else { + dmlog_info("no db upgrade is needed (version: %d))", version); + } + + if (dbmgr_init(DB_PATH) != 0) { + dmlog_error("dbmgr_init failed"); + return -1; + } + } + + dmlog_debug("dmapi_init end"); + return 0; +} + +void dmapi_quit(void) +{ + dbmgr_finalize(); + dmlog_info("dmapi_quit"); +} + +int dmapi_in_session(void) +{ + return (global_ctx.session_state != SESSION_STATE_IDLE); +} + +int dmapi_session_start(void) +{ + if (global_ctx.session_state == SESSION_STATE_IDLE) { + global_ctx.session_state = SESSION_STATE_STARTED; + dm_apply_reset(); + dmlog_info("session started"); + return 0; + } + + dmlog_error("session is already in use"); + return 0; +} + +int dmapi_session_end(int action) +{ + if (!dmapi_in_session()) { + dmlog_error("dmapi_session_end, not in session"); + return 0; + } + + switch (action) { + case TRANX_ROLLBACK: + dmapi_session_revert(); + break; + case TRANX_COMMIT: + dmapi_session_commit(); + break; + case TRANX_COMMIT_AND_APPLY: + dmapi_session_commit(); + dm_apply_do_apply(); + break; + case TRANX_NO_ACTION: + break; + default: + dmlog_error("invalid action %d", action); + return -1; + } + + global_ctx.session_state = SESSION_STATE_IDLE; + + dmlog_info("session ended"); + return 0; +} + +int dmapi_session_commit(void) +{ + dmlog_info("dmapi_session_commit"); + if (!dmapi_in_session()) { + dmlog_error("not in session"); + return -1; + } + + if (global_ctx.session_state == SESSION_STATE_MODIFYING) { + global_ctx.session_state = SESSION_STATE_STARTED; + if (dbmgr_tranx_commit() == 0) { + return 0; + } else { + dmlog_error("dbmgr_tranx_commit failed"); + return -1; + } + } + // nothing to commit, still return 0 + return 0; +} + +int dmapi_session_revert(void) +{ + dmlog_info("dmapi_session_revert"); + + if (!dmapi_in_session()) { + dmlog_error("dmapi_session_revert, not in session"); + return -1; + } + + if (global_ctx.session_state == SESSION_STATE_MODIFYING) { + dbmgr_tranx_revert(); + global_ctx.session_state = SESSION_STATE_STARTED; + } + + dm_apply_reset_changes(); + return 0; +} + +void dmapi_set_session_on_fly(int enable) +{ + global_ctx.session_on_fly = enable; +} + +/** + * This file contains the management framework node related functions. + * It includes creating and deleting nodes, and getting attributes and values of nodes. + */ + +static int check_node(const dm_node_t *node) +{ + if (node == NULL || !dm_node_is_valid(node->id) || (node->cnt != dm_node_index_cnt(node->id))) { + return -1; + } + return 0; +} + +static int indexes_counter_callback(void *context, int cnt, char *data[], char *names[]) +{ + int *info = (int *)context; + (*info)++; + + return 0; +} +static void convert_boolean_if_needed(char **value) { + if (strcmp(*value, "1") == 0) { + free(*value); + *value = strdup("true"); + } else if (strcmp(*value, "0") == 0) { + free(*value); + *value = strdup("false"); + } +} + +int dmapi_param_get(const dm_node_t *node, char **value) +{ + // dmlog_debug("get node: %s", dm_node_str(node)); + if ((check_node(node) < 0) || (value == NULL)) { + dmlog_error("check_node failed or value is NULL!, %s", dm_node_str(node)); + return -1; + } + + if (!dm_node_is_parameter(node->id)) { + dmlog_error("dm_node_is_parameter failed, %s", dm_node_str(node)); + return -1; + } + + if (dm_node_is_confidential(node->id)) { + *value = strdup(""); + return 0; + } + + const struct dm_parameter *param = dm_node_get_parameter(node->id); + const bool is_boolean_param = (param->data_type == DM_DATA_BOOLEAN); + if (param->const_val) { + *value = strdup(param->const_val); + return 0; + } + else if (dm_node_is_counter(node->id)) { + int count = 0; + dm_node_t counter_node = *node; + counter_node.id = dm_node_counter_id(node->id); + if (counter_node.id == INVALID_DM_NODE_ID) { + dmlog_error("invalid counter id %d", counter_node.id); + return -1; + } + + if (dm_node_has_db(counter_node.id)) { + int ret = dbmgr_query_indexes(&counter_node, NULL, indexes_counter_callback, &count, 0); + if (ret < 0) { + dmlog_error("dbmgr_query_indexes failed %s", dm_node_str(&counter_node)); + return -1; + } + } + + if (qjs_has_get_handler(counter_node.id)) { + JSValue res = qjs_call_get_instance_handler(&counter_node, 0); + if (!JS_IsUndefined(res)) { + unsigned int len; + JSValue length = JS_GetPropertyStr(qjs_ctx(), res, "length"); + JS_ToUint32(qjs_ctx(), &len, length); + JS_FreeValue(qjs_ctx(), length); + count += len; + } + } + asprintf(value, "%d", count); + return 0; + } + + int has_db = dm_node_has_db(node->id); + int has_handler = qjs_has_get_handler(node->id); + if (!has_db && has_handler) { + const int rv = qjs_call_get_handler(node, value); + if ((0 == rv) && is_boolean_param) { + convert_boolean_if_needed(value); + } + return rv; + } + + dm_node_t pnode; + if (dm_node_i_parent(node, &pnode) != -1 && qjs_has_get_handler(pnode.id) && dm_node_has_db(pnode.id)) { + // hybrid dynamic instance. + unsigned int db_max = (unsigned int)dbmgr_get_next_free_index(&pnode); + if (node->index[node->cnt - 1] < db_max) { + // the instance is in db. + if (has_db) { + return dbmgr_get(node, value); + } + } else { + // instance is from handler + // special handling for "_key" + if (dm_node_is_internal(node->id)) { + *value = strdup(""); + return 0; + } + if (!has_handler) { + // default handling for some special parameters + if (strcmp(param->node.name, "Alias") == 0) { + asprintf(value, "cpe-dyn-%d", node->index[node->cnt - 1] - db_max + 1); + return 0; + } + if (strcmp(param->node.name, "Enable") == 0) { + asprintf(value, "%s", "true"); + return 0; + } + } + } + } + + if (has_handler) { + const int rv = qjs_call_get_handler(node, value); + if ((0 == rv) && is_boolean_param) { + convert_boolean_if_needed(value); + } + return rv; + } + + if (dm_node_has_db(node->id)) { + return dbmgr_get(node, value); + } + + dmlog_error("dmapi_param_get, missing handling for node %s", dm_node_str(node)); + return -1; +} + +static int dmapi_param_get_by_id(dm_node_id_t id, char **value) +{ + dm_node_t node; + memset(&node, 0, sizeof(node)); + node.id = id; + return dmapi_param_get(&node, value); +} + +// return true if valid, othewise return false +static dm_bool_t validate_dm_path(const dm_node_t *node, const char *path_name) +{ + if (path_name == NULL) + return dm_false; + + // empty value is valid + if (*path_name == '\0') { + return dm_true; + } + + dm_path_t paths; + + strlcpy(paths, path_name, sizeof(paths)); + + char *savedptr; + char *path = strtok_r(paths, ",", &savedptr); + + while (path != NULL) { + dm_node_t path_node; + int ret = dm_path2node(path, &path_node); + + if (ret != 0) { + dmlog_error("dm_path2node failed"); + return dm_false; + } + + if (!dm_node_has_path(node->id, path_node.id)) { + dmlog_error("path is not allowed: %s", path); + return dm_false; + } + + if (!global_ctx.import_mode) { + ret = dmapi_node_exist(&path_node, 1); + if (ret == 0) { + dmlog_error("node not exist: %s", path); + return dm_false; + } + } + + path = strtok_r(NULL, ",", &savedptr); + } + + return dm_true; +} + +static int dmapi_param_set_uint(const dm_node_t *node, unsigned int value) +{ + char *str; + asprintf(&str, "%u", value); + int ret = dmapi_param_set(node, str); + free(str); + return ret; +} + +// Order value will be adjusted according to TR-181's description: +// When this value is modified, if the value matches that of an existing entry, +// the Order value for the existing entry and all lower Order entries is incremented +// (lowered in precedence) to ensure uniqueness of this value. +// Order Name +// 1, item1 +// 2, item2 +// 3, item3 +// 4, item4 +// if order #3 is set to #1, the reuslt will be as following: +// 1, item3 +// 2, item1 +// 3, item2 +// 4, item4 +static int set_order_values(const dm_node_t *node, unsigned int order, int target_index) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + char *key; + asprintf(&key, "\"%s\"=%d", info->name, order); + + dm_node_t node2; + dm_node_parent(node, &node2); + int found = dm_nodelist_find_first(&node2, key, &node2, 1); + free(key); + + if (found && node2.index[node2.cnt - 1] != target_index) { + node2.id = node->id; + if (set_order_values(&node2, order + 1, target_index) < 0) { + dmlog_error("set_order_values failed"); + return -1; + } + } + char *order_str; + asprintf(&order_str, "%d", order); + inode_buf_update_param_value(node, order_str); + free(order_str); + return dbmgr_set_uint(node, order); +} + +// Order value will be adjusted according to TR-181's description: +// A deletion causes Order values to be compacted. +// Order Name +// 1, item1 +// 2, itme2 +// 3, itme3 +// 4, itme4 +// if order #2 is deleted, the reuslt will be as following: +// 1, item1 +// 2, itme3 +// 3, itme4 +static int compact_order_values(const dm_node_t *node, unsigned int order) +{ + const struct dm_node_info *info = dm_node_get_info(node->id); + char *key; + asprintf(&key, "\"%s\" = %d", info->name, order + 1); + + dm_node_t node2; + dm_node_parent(node, &node2); + int found = dm_nodelist_find_first(&node2, key, &node2, 1); + free(key); + if (found) { + node2.id = node->id; + if (dmapi_param_set_uint(&node2, order) != 0) { + dmlog_error("dbmgr_set failed"); + return -1; + } + + return compact_order_values(&node2, order + 1); + } + + return 0; +} + +static int start_session_on_fly() +{ + if (!dmapi_in_session()) { + if (global_ctx.session_on_fly) { + if (dmapi_session_start()) { + dmlog_error("session start failed"); + return -1; + } + } else { + dmlog_error("not in a session"); + return -1; + } + } + + return 0; +} + +int dmapi_param_set(const dm_node_t *node, const char *value) +{ + if (!dm_node_is_confidential(node->id)) { + dmlog_debug("dmapi_param_set %s = %s", dm_node_str(node), value); + } else { + dmlog_debug("dmapi_param_set confidential %s", dm_node_str(node)); + } + + if (check_node(node) < 0) { + dmlog_error("dmapi_param_set, invalid node: %s", dm_node_str(node)); + return -1; + } + + if (!dm_node_verify_param_data(node->id, value)) { + dmlog_error("Failure setting parameter %s (data verification).", + dm_node_str(node)); + return -1; + } + + if (!dm_node_is_parameter(node->id)) { + dmlog_error("Tried to set value for %s (not a parameter).", dm_node_str(node)); + return -1; + } + + if (!global_ctx.import_mode && !dm_node_is_writable(node->id)) { + dmlog_error("Not able to set readonly parameter %s.", dm_node_str(node)); + return -1; + } + + if (!dm_node_has_db(node->id) && !qjs_has_set_handler(node->id)) { + dmlog_error("Failure setting parameter %d (not in DB and no custom handler).", + node->id); + return -1; + } + + start_session_on_fly(); + + if (!dmapi_in_session()) { + dmlog_error("not in session"); + return -1; + } + + const struct dm_parameter *param_info = dm_node_get_parameter(node->id); + if (!param_info) { + dmlog_error("node info not found"); + return -1; + } + + if (param_info->data_type == DM_PATH_NAME) { + // todo: remove the check as aworkaround for the PoC + if (!validate_dm_path(node, value)) { + dmlog_error("invalid path name to set %s", value); + // return -1; + } + } + + // Enforce to only store true/false values for boolean parameters + const char *v = value; + if (param_info->data_type == DM_DATA_BOOLEAN) { + if (strcmp(value, "1") == 0) { + v = "true"; + } else if (strcmp(value, "0") == 0) { + v = "false"; + } + } + + if (!global_ctx.import_mode && qjs_has_set_handler(node->id)) { + int ret = qjs_call_set_handler(node, v); + if (ret <= 0) { + dmlog_error("qjs_call_set_handler, returned %d", ret); + return ret; + } + } + + // db + if (global_ctx.session_state != SESSION_STATE_MODIFYING) { + dbmgr_tranx_begin(); + global_ctx.session_state = SESSION_STATE_MODIFYING; + } + + int ret; + const struct dm_node_info *info = dm_node_get_info(node->id); + if (info->flag & FLAG_HAS_ORDER) { + if (node->cnt == 0) { + dmlog_error("%s, invalid node->cnt, 0", + __FUNCTION__); + return -1; + } + unsigned int order_val = atoi(v); + ret = set_order_values(node, order_val, node->index[node->cnt - 1]); + } else { + ret = dbmgr_set(node, v); + } + + if (ret != 0) { + dmlog_error("dbmgr_set failed"); + return -1; + } + + if (!global_ctx.import_mode) { + dm_node_id_t parent_id = dm_node_i_parent_id(node->id); + if (!(info->flag & FLAG_HAS_ORDER) && dm_node_is_objectlist(parent_id)) { + const char *keys = dm_node_object_keys(parent_id); + if (keys && strstr(keys, dm_node_get_info(node->id)->name) != NULL) { + inode_buf_update_param_value(node, v); + } + } + if (qjs_call_changed_handler(node) < 0) { + dmlog_error("qjs_call_changed_handler failed %s", dm_node_str(node)); + return -1; + } + + dm_appy_add_change(DATA_MODEL_SET, node); + } + + return 0; +} + +int dmapi_param_set_by_id(dm_node_id_t id, const char *param_data) +{ + dm_node_t node; + memset(&node, 0, sizeof(node)); + node.id = id; + return dmapi_param_set(&node, param_data); +} + +/** + * example: A.1.B.2.C + * need to check if A.1 and A.1.B.2 are both exist + */ +int dmapi_node_exist(const dm_node_t *node, int db_only) +{ + if (node == NULL) { + // parameter or object without instance. + return 1; + } + + if (!dm_node_is_index_complete(node)) { + return 0; + } + + if (node->cnt == 0) { + return 1; + } + + dm_node_t n = *node; + dm_node_parent(node, &n); + if (!dmapi_node_exist(&n, db_only)) { + return 0; + } + + if (dm_node_is_objectlist(node->id)) { + dm_nodelist_h list = dm_nodelist_find(node, NULL, db_only); + int exist = 0; + const dm_node_t *pnode = dm_nodelist_first(list); + while (pnode) { + if (dm_node_last_index(pnode) == dm_node_last_index(node)) { + exist = 1; + break; + } + pnode = dm_nodelist_next(list); + } + dm_nodelist_free(list); + return exist; + } + + return 1; +} + +void dmapi_refresh_instance_buf(const dm_node_t *node) +{ + // dmlog_debug("dmapi_refresh_instance_buf: %s", dm_node_str(node)); + dm_nodelist_h list = dm_nodelist_get(node); + dm_nodelist_free(list); +} + +static int add_db_instance_to_inode_buf(const dm_node_t *node, dm_index_t index) +{ + JSValue obj = JS_NewObject(qjs_ctx()); + char *_key_val = NULL; + dm_node_t n = *node; + n.index[n.cnt] = index; + n.cnt++; + + JS_SetPropertyStr(qjs_ctx(), obj, ".index", JS_NewInt32(qjs_ctx(), index)); + JS_SetPropertyStr(qjs_ctx(), obj, ".db", JS_NewBool(qjs_ctx(), 1)); + dbmgr_get_child(&n, "_key", &_key_val); + dmlog_debug("%s, _key: %s", dm_node_str(&n), _key_val); + if (_key_val) { + JS_SetPropertyStr(qjs_ctx(), obj, "_key", JS_NewString(qjs_ctx(), _key_val)); + free(_key_val); + } else { + dmlog_error("failed to get uci key value %s", dm_node_str(node)); + } + const char *keys = dm_node_object_keys(node->id); + if (keys) { + char *keys_str = strdup(keys); + char *key = strtok(keys_str, ","); + while ((key != NULL)) { + char *value = NULL; + dbmgr_get_child(&n, key, &value); + if (value != NULL) { + JS_SetPropertyStr(qjs_ctx(), obj, key, JS_NewString(qjs_ctx(), value)); + free(value); + } else { + dmlog_error("failed to get object key value: %s for node: %s", key, dm_node_str(node)); + } + key = strtok(NULL, ","); + } + free(keys_str); + } + + inst_entry_t ins = { + index, + obj, + NULL, + 0 + }; + return inode_buf_append_db_inst(node, &ins); +} + +static int set_on_creation(const dm_node_t *inst) +{ + const struct dm_object *obj = dm_node_get_object(inst->id); + for (int i = 0; i < obj->param_num; i++) { + char *buf = NULL; + const struct dm_parameter *param = (const struct dm_parameter *)obj->param_list[i]; + dm_node_t param_node = *inst; + param_node.id = param->node.node_id; + + if (param->set_on_create == NULL && strcmp(param->node.name, "Alias") != 0) { + continue; + } + + if (param->set_on_create != NULL) { + asprintf(&buf, "%s%d", param->set_on_create, inst->index[inst->cnt - 1]); + } else { + asprintf(&buf, "cpe-%d", inst->index[inst->cnt - 1]); + } + + if (dbmgr_set(¶m_node, buf) != 0) { + dmlog_error("failed to set set_on_creation value %s for %s", buf, dm_node_str(inst)); + free(buf); + return -1; + } + free(buf); + } + + return 0; +} + +int dmapi_object_add(dm_node_t *node) +{ + int ret = 0; + if (!dm_node_is_objectlist(node->id)) { + dmlog_error("invalid type!"); + return -1; + } + + int index_cnt = dm_node_index_cnt(node->id); + if (index_cnt != node->cnt && index_cnt != (node->cnt + 1)) { + dmlog_error("invalid cnt!"); + return -1; + } + + if (!global_ctx.import_mode && !dm_node_is_writable(node->id)) { + dmlog_error("not allowed to add readonly object %s", dm_node_str(node)); + return -1; + } + + if (!dm_node_has_db(node->id)) { + dmlog_error("not db or handler!"); + return -1; + } + + if (!global_ctx.import_mode && dm_node_is_fixed_objects(node->id)) { + dmlog_error("not allowed to add fixed object"); + return -1; + } + + if (!dmapi_in_session()) { + dmlog_error("not in session"); + return -1; + } + + if (global_ctx.session_state != SESSION_STATE_MODIFYING) { + dbmgr_tranx_begin(); + global_ctx.session_state = SESSION_STATE_MODIFYING; + } + + unsigned int max_order = 0; + dm_node_t order_node; + const struct dm_node_info *info = dm_node_get_info(node->id); + if (info == NULL) { + dmlog_error("%s, node info is null", + __FUNCTION__); + return -1; + } + + if (info->flag & FLAG_HAS_ORDER) { + if (dm_node_find_order_param(node, &order_node)) { + dmlog_error("dm_node_find_order_param failed %s", dm_node_str(node)); + return -1; + } + + order_node.cnt = index_cnt - 1; + dbmgr_get_max(&order_node, &max_order); + } + + ret = dbmgr_add(node); + if (ret <= 0) { + dmlog_error("dbmgr_add failed %s", dm_node_str(node)); + return -1; + } + + dm_node_t n = *node; + // update new allocated index + node->cnt = index_cnt; + node->index[node->cnt - 1] = (dm_index_t)ret; + + dmlog_info("added instance: %s", dm_node_str(node)); + + if (info->flag & FLAG_HAS_ORDER) { + // Tr181: the value of Order on creation of a entry MUST be one greater than + // the largest current value (initially assigned the lowest precedence) + max_order++; + order_node.cnt = index_cnt; + order_node.index[index_cnt - 1] = (dm_index_t)ret; + if (dbmgr_set_uint(&order_node, max_order) != 0) { + dmlog_error("failed to set order value %d", max_order); + return -1; + } + } + + set_on_creation(node); + if (!global_ctx.import_mode) { + char *key = NULL; + add_db_instance_to_inode_buf(&n, (dm_index_t)ret); + qjs_call_key_handler(node, &key); + if (key) { + dm_node_t param_node = *node; + param_node.id = dm_node_get_child_id(node->id, "_key"); + if (dbmgr_set(¶m_node, key) != 0) { + dmlog_error("failed to set key value %s for %s", key, dm_node_str(node)); + free(key); + return -1; + } + inode_buf_update_param_value(¶m_node, key); + free(key); + } + dm_appy_add_change(DATA_MODEL_ADD, node); + } + + + if (!global_ctx.import_mode) { + if (qjs_call_changed_handler(node) < 0) { + dmlog_error("qjs_call_changed_handler failed %s", dm_node_str(node)); + return -1; + } + } + + return 0; +} + +/* This function tries to remove all the referenced parameter value for the deleted object. + Tr181: "If the referenced object is deleted, the parameter value MUST be set to an empty string." + @param deleted referenced node. +*/ +static int remove_path_references_to_node(const dm_node_t *deleted_node) +{ + int i; + dm_path_t deleted_node_path = ""; + + const struct dm_object *obj = dm_node_get_object(deleted_node->id); + if (obj == NULL) + return -1; + + for (i = 0; i < obj->paths_refs_num; i++) { + const struct dm_node_info *ref_node = obj->paths_refs_list[i]; + if (ref_node == NULL || ref_node->type != DM_NODE_PARAMETER || ((const struct dm_parameter *)ref_node)->data_type != DM_PATH_NAME) { + dmlog_error("empty_referenced_parameter - invalid type: parameter type is expected"); + return -1; + } + + if (dm_node_index_cnt(ref_node->node_id) == 0) { + // no index in the path name, reset the value direclty. + char *path = NULL; + if (dmapi_param_get_by_id(ref_node->node_id, &path) || + tr181_paths_remove(path, deleted_node) || + dmapi_param_set_by_id(ref_node->node_id, path)) { + dmlog_error("failed to remove pathname"); + if (path) { + free(path); + } + return -1; + } + free(path); + continue; + } + + // there is index in the path name, need to search by the deleted node name. + // find the last index node to search, + // eg, if ref_node is the Device.A.{i}.B.C, then the node to search is Device.A.{i}. + dm_node_id_t parent_id = dm_node_i_parent_id(ref_node->node_id); + if (parent_id == INVALID_DM_NODE_ID || !dm_node_has_db(parent_id)) { + dmlog_error("invalid node type: node list with db flag is expected"); + continue; + } + + if (deleted_node_path[0] == '\0') { + // use SQL 'LIKE' statement to search the path enclosed by percent: '%path%' + deleted_node_path[0] = '%'; + int ret = dm_node2name(deleted_node, &deleted_node_path[1], sizeof(dm_path_t) - 2); + if (ret != 0) { + dmlog_error("dm_node2name failed"); + return -1; + } + + int len = strlen(deleted_node_path); + deleted_node_path[len] = '%'; + deleted_node_path[len + 1] = '\0'; + } + + char *key; + asprintf(&key, "%s LIKE \"%s\"", ref_node->name, deleted_node_path); + dm_node_t parent_node; + parent_node.id = parent_id; + parent_node.cnt = 0; + dm_nodelist_h list = dm_nodelist_find(&parent_node, key, 1); + free(key); + + const dm_node_t *pnode = NULL; + nodelist_for_each_node(pnode, list) + { + dm_node_t tmp = *pnode; + tmp.id = ref_node->node_id; + char *path = NULL; + if (dmapi_param_get(&tmp, &path) || + tr181_paths_remove(path, deleted_node) || + dmapi_param_set(&tmp, path)) { + dmlog_error("failed to remove pathname"); + dm_nodelist_free(list); + if (path) { + free(path); + } + return -1; + } + + free(path); + } + + dm_nodelist_free(list); + } + + return 0; +} + +static void del_child_instances(const dm_node_t *node) +{ + const struct dm_object *obj = dm_node_get_object(node->id); + for (int i = 0; i < obj->object_num; i++) { + const struct dm_node_info *info = obj->object_list[i]; + if (info->type == DM_NODE_OBJECT_LIST && dm_node_get_table_name(info)) { + dm_node_t child_node = *node; + child_node.id = info->node_id; + dm_nodelist_h list = dm_nodelist_get_db(&child_node); + const dm_node_t *pnode; + nodelist_for_each_node(pnode, list) { + del_child_instances(pnode); + dmapi_object_del(pnode); + } + } + } +} + +int dmapi_object_del(const dm_node_t *node) +{ + dmlog_info("del node: %s", dm_node_str(node)); + + if (check_node(node) < 0) { + dmlog_error("invalid node"); + return -1; + } + + if (!dm_node_is_objectlist(node->id)) { + dmlog_error("not node list type"); + return -1; + } + + if (!global_ctx.import_mode && !dm_node_is_writable(node->id)) { + dmlog_error("not allowed to delete readonly object %s", dm_node_str(node)); + return -1; + } + + if (!dm_node_has_db(node->id)) { + dmlog_error("not allowed to delete, the multi-object is not stored in db"); + return -1; + } + + if (dm_node_is_fixed_objects(node->id)) { + dmlog_error("not allowed to delete fixed object"); + return -1; + } + + if (qjs_has_get_handler(node->id)) { + // mixed instances, check if the index is in the db. + unsigned int db_max = (unsigned int)dbmgr_get_next_free_index(node); + if (node->index[node->cnt - 1] >= db_max) { + dmlog_error("not allowed to delete, the instance is not stored in db"); + return -1; + } + } + + start_session_on_fly(); + + if (!dmapi_in_session()) { + dmlog_error("not in session"); + return -1; + } + + if (remove_path_references_to_node(node) != 0) { + dmlog_error("empty_referenced_parameter_value failed"); + return -1; + } + + if (global_ctx.session_state != SESSION_STATE_MODIFYING) { + dbmgr_tranx_begin(); + global_ctx.session_state = SESSION_STATE_MODIFYING; + } + + const struct dm_node_info *info = dm_node_get_info(node->id); + + if (info->flag & FLAG_HAS_ORDER) { + dm_node_t order_node; + if (dm_node_find_order_param(node, &order_node) != 0) { + dmlog_error("dm_node_find_order_param failed %s", dm_node_str(node)); + return -1; + } + + char *order; + if (dmapi_param_get(&order_node, &order) != 0) { + dmlog_error("Failed to get parameter value."); + return -1; + } + + compact_order_values(&order_node, atoi(order)); + free(order); + } + + if (!global_ctx.import_mode) { + del_child_instances(node); + } + + int ret = dbmgr_del(node); + if (ret < 0) { + dmlog_error("dbmgr_del failed %s", dm_node_str(node)); + return -1; + } + + if (!global_ctx.import_mode) { + dm_node_t tmp = *node; + tmp.cnt--; + // if (qjs_call_changed_handler(node) < 0) { + // dmlog_error("qjs_call_changed_handler failed %s", dm_node_str(node)); + // return -1; + // } + inode_buf_del_db_inst(node); + dm_appy_add_change(DATA_MODEL_DELETE, node); + } + + return 0; +} + +int dmapi_operate(const dm_node_t *node, const char *json_input, struct json_object **json_output) +{ + return qjs_call_operate_handler(node, json_input, json_output); +} + +dm_nodelist_h dm_nodelist_get_db(const dm_node_t *node) +{ + return dm_nodelist_find(node, NULL, 1); +} + +dm_nodelist_h dm_nodelist_get(const dm_node_t *node) +{ + return dm_nodelist_find(node, NULL, 0); +} + +static int get_dynamic_instance(const dm_node_t *node) +{ + unsigned int len = 0; + JSContext *js_ctx = qjs_ctx(); + + int dynamic_base = 1; + if (dm_node_has_db(node->id)) { + dynamic_base = (unsigned int)dbmgr_get_next_free_index(node); + } + if (!dm_node_is_writable(node->id)) { + // overrule the db index, the index hanler must return the same count of instances as in db. + dynamic_base = 1; + } + + JSValue res = qjs_call_get_instance_handler(node, dynamic_base); + if (JS_IsArray(qjs_ctx(), res)) { + JSValue length = JS_GetPropertyStr(js_ctx, res, "length"); + JS_ToUint32(js_ctx, &len, length); + JS_FreeValue(js_ctx, length); + } + + dm_inst_array_t * arr = dm_inst_array_create(len); + for (int i = 0; i < len; i++) { + inst_entry_t entry = { + dynamic_base + i, + JS_GetPropertyUint32(js_ctx, res, i), + NULL, 0}; + dm_inst_array_append(arr, &entry); + } + inode_buf_add_inst(node, arr, 1, 0); + + return len; +} + +static int indexes_callback(void *context, int cnt, char *data[], char *names[]) +{ + if (cnt < 0 || data[0] == NULL) { + dmlog_debug("indexes_callback, no data"); + return 0; + } + + struct nodelist_struct *info = (struct nodelist_struct *)context; + + if (info->db_index_cnt >= info->index_capacity) { + int new_sz = sizeof(dm_index_t)*info->index_capacity*2; + info->db_indices = realloc(info->db_indices, new_sz); + if (info->db_indices2) { + info->db_indices2 = realloc(info->db_indices2, new_sz); + } + } + + info->db_indices[info->db_index_cnt] = strtoul(data[0], NULL, 10); + if (cnt > 1 && info->db_indices2) { + info->db_indices2[info->db_index_cnt] = strtoul(data[1], NULL, 10); + } + if (info->db_keys) { + JSValue obj = JS_NewObject(qjs_ctx()); + JS_SetPropertyStr(qjs_ctx(), obj, ".index", JS_NewInt32(qjs_ctx(), atoi(data[0]))); + JS_SetPropertyStr(qjs_ctx(), obj, ".db", JS_NewBool(qjs_ctx(), 1)); + + for (int i = 1; i < cnt; i++) { + JS_SetPropertyStr(qjs_ctx(), obj, names[i], JS_NewString(qjs_ctx(), data[i])); + } + info->db_keys[info->db_index_cnt] = obj; + } + + info->db_index_cnt++; + + return 0; +} + +static int get_indexes(const dm_node_t *node, struct nodelist_struct *result, + const char *keys, int only_db) +{ + result->node = *node; + result->dynamic_base = 1; + result->node.cnt = dm_node_index_cnt(result->node.id); + + if (node->cnt < result->node.cnt - 2) { + dmlog_info("get_indexes, invalid node: %s", dm_node_str(node)); + return -1; + } + + if (dm_node_has_db(node->id)) { + if (node->cnt < result->node.cnt - 1) { + result->db_indices = malloc(NODELIST_DEFAULT_CNT * sizeof(dm_index_t)); + result->db_indices2 = malloc(NODELIST_DEFAULT_CNT * sizeof(dm_index_t)); + result->index_capacity = NODELIST_DEFAULT_CNT; + } else { + result->dynamic_base = (unsigned int)dbmgr_get_next_free_index(node); + result->db_indices = malloc(result->dynamic_base * sizeof(dm_index_t)); + result->index_capacity = result->dynamic_base; + } + if (keys == NULL) { + result->db_keys = malloc((result->dynamic_base - 1) * sizeof(JSValue)); + } + dbmgr_query_indexes(node, keys, indexes_callback, result, 0); + if (keys == NULL) { + dm_inst_array_t * arr = dm_inst_array_create(result->db_index_cnt); + for (int i = 0; i < result->db_index_cnt; i++) { + inst_entry_t entry = { + result->db_indices[i], + result->db_keys[i], + NULL, 0}; + dm_inst_array_append(arr, &entry); + } + + inode_buf_add_inst(node, arr, 0, 0); + } + } + + if (!only_db && qjs_has_get_handler(node->id)) { + result->dynamic_cnt = get_dynamic_instance(node); + } + + return 0; +} + +static void make_nodelist_from_inode_buf(const dm_node_t *node, struct nodelist_struct *list, + struct inode_entry *entry, int only_db) +{ + list->node = *node; + list->node.cnt = dm_node_index_cnt(node->id); + + if (!only_db && entry->dyn_insts && entry->dyn_insts->size > 0) { + list->dynamic_base = entry->dyn_insts->data[0].index; + list->dynamic_cnt = entry->dyn_insts->size; + } + + if (entry->insts && entry->insts->size > 0) { + list->db_index_cnt = entry->insts->size; + list->db_indices = malloc(list->db_index_cnt * sizeof(dm_index_t)); + list->db_keys = malloc(list->db_index_cnt * sizeof(JSValue)); + for (int i = 0; i < list->db_index_cnt; i++) { + list->db_indices[i] = entry->insts->data[i].index; + list->db_keys[i] = entry->insts->data[i].value; + } + } +} + +dm_nodelist_h dm_nodelist_find(const dm_node_t *node, const char *keys, int only_db) +{ + // dmlog_debug("dm_nodelist_find %s, key:%s, db: %d", dm_node_str(node), keys, only_db); + if (!dm_node_is_objectlist(node->id)) { + dmlog_error("node is not multi-object! %s", dm_node_str(node)); + return NULL; + } + + int has_db = dm_node_has_db(node->id); + if (only_db && !has_db) { + dmlog_error("dm_nodelist_find, no db available %s", dm_node_str(node)); + return NULL; + } + + int has_get = qjs_has_get_handler(node->id); + if (!dm_node_has_db(node->id) && !has_get) { + dmlog_error("no get instance handler, %s", dm_node_str(node)); + return NULL; + } + + struct nodelist_struct *list = calloc(1, sizeof(struct nodelist_struct)); + if (list == NULL) { + dmlog_error("calloc failed"); + return NULL; + } + + if (!keys) { + // reuse the db instances in buffer. + int retry; + struct inode_entry *entry = inode_buf_find(node, &retry); + if (entry == NULL && retry == 0) { + return NULL; + } + if (entry != NULL && ((has_db && entry->insts) || (!has_db && entry->dyn_insts))) { + if (has_get && !only_db) { + // update the dynamic buffer only + unsigned int now = get_uptime_msecs(); + if (!entry->dyn_insts || (now - entry->dyn_time) >= INODE_BUF_EXPIRE_TIME) { + get_dynamic_instance(node); + } + } + make_nodelist_from_inode_buf(node, list, entry, only_db); + return (dm_nodelist_h)list; + } + } + + get_indexes(node, list, keys, only_db); + return (dm_nodelist_h)list; +} + +const dm_node_t *dm_nodelist_first(dm_nodelist_h list_h) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + if (list == NULL) { + return NULL; + } + + if (list->db_index_cnt > 0) { + list->node.index[list->node.cnt - 1] = list->db_indices[0]; + if (list->db_indices2) { + list->node.index[list->node.cnt - 2] = list->db_indices2[0]; + } + } else if (list->dynamic_cnt > 0) { + list->node.index[list->node.cnt - 1] = list->dynamic_base; + } else { + return NULL; + } + + list->iterator = 0; + return &list->node; +} + +const dm_node_t *dm_nodelist_next(dm_nodelist_h list_h) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + + if (list == NULL) { + return NULL; + } + + list->iterator++; + if (list->iterator < list->db_index_cnt) { + list->node.index[list->node.cnt - 1] = list->db_indices[list->iterator]; + if (list->db_indices2) { + list->node.index[list->node.cnt - 2] = list->db_indices2[list->iterator]; + } + } else if (list->iterator < (list->db_index_cnt + list->dynamic_cnt)) { + list->node.index[list->node.cnt - 1] = list->dynamic_base + list->iterator - list->db_index_cnt; + } else { + return NULL; + } + + return &list->node; +} + +int dm_nodelist_find_first(const dm_node_t *node, + const char *keys, dm_node_t *result, int only_db) +{ + dm_nodelist_h list = dm_nodelist_find(node, keys, only_db); + if (list == NULL) { + return 0; + } + + if (result) { + const dm_node_t *res = dm_nodelist_first(list); + if (res == NULL) { + return 0; + } + *result = *res; + } + + + dm_nodelist_free(list); + return 1; +} + +const char *dm_nodelist_key(dm_nodelist_h list_h, int i, const char *key) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + if (list && list->db_keys && i < list->db_index_cnt) { + return qjs_get_property_str(list->db_keys[i], key); + } + + dmlog_error("dm_nodelist_key, %d, NULL", i); + return NULL; +} + +int dm_nodelist_cnt(dm_nodelist_h list_h) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + if (list == NULL) { + return 0; + } + + return list->db_index_cnt + list->dynamic_cnt; +} + +const dm_node_t *dm_nodelist_node(dm_nodelist_h list_h, int i) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + + if (list == NULL || i >= (list->db_index_cnt + list->dynamic_cnt) || i < 0) { + return NULL; + } + + if (i < list->db_index_cnt) { + list->node.index[list->node.cnt - 1] = list->db_indices[i]; + if (list->db_indices2) { + list->node.index[list->node.cnt - 2] = list->db_indices2[i]; + } + } else { + list->node.index[list->node.cnt - 1] = list->dynamic_base + i - list->db_index_cnt; + } + + return &list->node; +} + +dm_index_t dm_nodelist_index(dm_nodelist_h list_h, int i) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + + if (list == NULL || i >= (list->db_index_cnt + list->dynamic_cnt) || i < 0) { + return INVALID_DM_NODE_ID; + } + + if (i < list->db_index_cnt) { + return list->db_indices[i]; + } else { + return list->dynamic_base + i - list->db_index_cnt; + } +} + +void dm_nodelist_free(dm_nodelist_h list_h) +{ + struct nodelist_struct *list = (struct nodelist_struct *)list_h; + if (list == NULL) { + return; + } + + if (list->db_indices != NULL) { + free(list->db_indices); + } + + if (list->db_indices2 != NULL) { + free(list->db_indices2); + } + + if (list->db_keys != NULL) { + free(list->db_keys); + } + + free(list); +} + +void dmapi_dump_node_buffer(const dm_node_t *node) +{ + inode_buf_dump(node); +} + +int dmapi_handle_ubus_event(dm_node_id_t id, const char *json_event_str, struct json_object **res) +{ + return qjs_call_event_handler(id, json_event_str, res); +} + + +int dmapi_refresh_linker_nodes(const char * service_name) +{ + dm_node_id_t *linker_nodes = dm_linker_nodes; + + while (*linker_nodes != INVALID_DM_NODE_ID) { + dm_node_t *node = dm_node_get_by_id(*linker_nodes); + if (node) { + // todo: to be implemented + } + } +} \ No newline at end of file diff --git a/dm-framework/dm-api/src/core/dm_api.h b/dm-framework/dm-api/src/core/dm_api.h new file mode 100644 index 000000000..7183cd907 --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_api.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DM_API_H +#define DM_API_H + +#include +#include "dm_types.h" +#include "dm_node.h" + +/** Initialize dmapi. + * @return 0 in case of success, or < 0 in case of error + */ +int dmapi_init(void); + +// Transaction action when session is end +enum TRANX_ACTION { + TRANX_NO_ACTION = 0, + TRANX_ROLLBACK, + TRANX_COMMIT, + TRANX_COMMIT_AND_APPLY +}; + +/** Start a session. + * Only one session can be started and active + * While starting the session the lock is taken to avoid race conditions + * @pre dmapi_init should be called successfully + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_session_start(void); + +/** End a session. + * Only one session can be started and active + * @pre dmapi_session_start should be called successfully + * @param action[in] one of the actions defined in TRANX_ACTION + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_session_end(int action); + +/** Commits a transaction in a session. + * @pre dmapi_session_start should be called successfully + * and have modified parameters or objects by calling + * dmapi_xxx_set/add/del + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_session_commit(void); + +/** Revert changes made in current session. + * @pre dmapi_session_start should be called successfully + * and have modified parameters or objects by calling + * dmapi_xxx_set/add/del + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_session_revert(void); + +/** Check if session in the dmapi context is valid and active. + * @pre dmapi_init should be called successfully + * @return 1 if active, or 0 when invalid or not started + */ +int dmapi_in_session(void); + +/** Delete a dmapi communication context. + * @pre dmapi_init should be called successfully + * @return none + */ +void dmapi_quit(void); + +/** Enable or disable feature of "session on the fly" + * With this feature enabled, session will be started automatically when set/add/del APIs are called, + * so you don't have to call dmapi_session_start explicitly before modifying data models. + * but you still need to end the session explicitly by calling dmapi_session_end. + * @param enable: 0: disable, 1: enable + * @pre dmapi_init should be called successfully + * @return none + */ +void dmapi_set_session_on_fly(int enable); + +/** Enable or disable feature of "Auto reference deletion" when deleting objects + * this feature should be not enabled by cwmp client. + * @param enable: 0: disable, 1: enable + * @pre dmapi_init should be called successfully + * @return none + */ +void dmapi_set_dm_auto_del(int enable); + +/** Get parameter value of specific parameter node. + * + * @pre dmapi_init should be called successfully + * + * @param[in] node - pointer to parameter node + * @param[out] value - pointer to pointer to save the result, the value must be freed after use. + * + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_param_get(const dm_node_t *node, char **value); + +/** Set parameter value of specific parameter node. + * + * @pre dmapi_session_start should be called successfully + * + * @param[in] node - pointer to parameter node + * @param[in] param_data - pointer to string parameter value to set + * + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_param_set(const dm_node_t *node, const char *param_data); + +/** Add specific object node. + * + * @pre dmapi_session_start should be called successfully + * + * @param[in] node - pointer to object node, the index of the node will be updated upon success + * + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_object_add(dm_node_t *node); + +/** Delete specific object node. + * + * @pre dmapi_session_start should be called successfully + * + * @param[in] node - pointer to object node + * + * @return 0 in case of success, or a nonzero value in case of error + */ +int dmapi_object_del(const dm_node_t *node); + +/** Operate on the command node. + * + * @param[in] node - pointer to multi-object node as input + * @param[in] args - arguments in json format. + * @param[out] json_input - input of the command as json string format. + * @param[out] output - output of the command as json_object. + * + * @return 0 in case of success, -1 in case of error + */ +int dmapi_operate(const dm_node_t *node, const char *json_input, struct json_object **json_output); + +/** Check if an object instance exist + * @param[in] node - node as input + * @param[in] db_only - if the node should be in db or not + * @return 1 if exist, otherwise return 0 + */ +int dmapi_node_exist(const dm_node_t *node, int db_only); + +typedef void *dm_nodelist_h; +#define DM_INVALID_NODELIST ((dm_nodelist_h)NULL) + +// macro to iterate the node list +#define nodelist_for_each_node(node, list) \ + for ((node) = dm_nodelist_first((list)); (node) != NULL; (node) = dm_nodelist_next((list))) + +/** Get all instances of a multi-object node, node must be a multi-object type. + * @param node[in] pointer to multi-object node as input + * @return handle of instances list in case of success, NULL value in case of error + */ +dm_nodelist_h dm_nodelist_get(const dm_node_t *node); + +// for only db +dm_nodelist_h dm_nodelist_get_db(const dm_node_t *node); + +/** Get all instances of a multi-object node, node must be a multi-object type. + * @param node[in] pointer to multi-object node as input + * @param keys[in] sql condition + * @param only_db[in] true if only get db instance. + * @return handle of instances list in case of success, NULL value in case of error + */ +dm_nodelist_h dm_nodelist_find(const dm_node_t *node, + const char *keys, int only_db); + +/** Wrapper interface for dm_nodelist_find to find only one node as expected. + * If more than one are found, the first one will be the result + * @param node[in] pointer to multi-object node as input + * @param keys[in] sql condition + * @param only_db[in] true if only get db instance. + * @param result[out] as output + * @return 0 if not found, or a nonzero value when found + */ +int dm_nodelist_find_first(const dm_node_t *node, + const char *keys, dm_node_t *result, int only_db); + +/** Free a node list. + * @pre dm_nodelist_get or dm_nodelist_find should be called successfully + * @param list[in] handler of node list as input + * @return none + */ +void dm_nodelist_free(dm_nodelist_h list); + +/** Get first instance of node instance list. + * @pre dm_nodelist_get or dm_nodelist_find should be called successfully + * @param list[in] handler of node list as input + * @return node pointer on success, NULL when node does not exist + */ +const dm_node_t *dm_nodelist_first(dm_nodelist_h list); + +/** Get next instance of node instance list. + * @pre dm_nodelist_get or dm_nodelist_find should be called successfully + * @param list[in] handler of node list as input + * @return node pointer on success, NULL when node does not exist + */ +const dm_node_t *dm_nodelist_next(dm_nodelist_h list); + +/** Get count of instance in the list. + * @pre dm_nodelist_get or dm_nodelist_find should be called successfully + * @param[in] list handler of node list as input + * @return number of instances + */ +int dm_nodelist_cnt(dm_nodelist_h list); + +/** Get each node by index, scope is from [0 - (max_cnt-1)]. + * @pre dm_nodelist_get or dm_nodelist_find should be called successfully + * @param list_h[in] handler of node list as input + * @param i[in] index value as input + * @return node pointer + */ +const dm_node_t *dm_nodelist_node(dm_nodelist_h list_h, int i); + +/** Get node index by sequence number, scope is from [0 - (max_cnt-1)]. + * @pre dm_nodelist_get or dm_nodelist_find should be called successfully + * @param list_h[in] handler of node list as input + * @param i[in] index value as input + * @return index of node + */ +dm_index_t dm_nodelist_index(dm_nodelist_h list_h, int i); +const char *dm_nodelist_key(dm_nodelist_h list_h, int i, const char *key); +void dmapi_dump_node_buffer(const dm_node_t *node); + +int dmapi_handle_ubus_event(dm_node_id_t id, const char *json_event_str, struct json_object **res); + +/** Refresh linker nodes for a given service + * @param service_name The UBus object name of the service (e.g., "bbfdm.core") + * @return 0 on success, -1 on error + */ +int dm_refresh_linker_nodes(const char *service_name); + +#endif /* for dmapi_H */ + +#ifdef __cplusplus +} +#endif diff --git a/dm-framework/dm-api/src/core/dm_apply.c b/dm-framework/dm-api/src/core/dm_apply.c new file mode 100644 index 000000000..272950374 --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_apply.c @@ -0,0 +1,724 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "dm_apply.h" + +#include +#include +#include + +#include "dbmgr.h" +#include "dm_api.h" +#include "dm_list.h" +#include "dm_log.h" +#include "dm_node.h" +#include "dm_uci.h" +#include "qjs.h" +#include "ubus_client.h" + +typedef struct { + const char* key; + int order; +} key_order_pair; + +// datamodel change list during the session +static dm_list_h changed_node_list = NULL; +static dm_list_h apply_uci_list = NULL; + +void add_apply_package(char *package) +{ + if (!package) { + return; + } + if (apply_uci_list == NULL) { + apply_uci_list = dm_list_create(NULL); + } + + // add the uci package name to the apply list + if (!dm_list_contains(apply_uci_list, (const void*)package)) { + dmlog_debug("add uci package to apply: %s", package); + dm_list_append(apply_uci_list, package); + } else { + free(package); + } +} + +void del_apply_package(char *package) +{ + if (!package || !apply_uci_list) { + return; + } + + dmlog_debug("remove uci package to apply: %s", package); + dm_list_remove(apply_uci_list, package); +} + +void dm_apply_reset_changes(void) +{ + if (changed_node_list) { + dm_list_free(changed_node_list); + changed_node_list = NULL; + } + + if (apply_uci_list) { + dm_list_free(apply_uci_list); + apply_uci_list = NULL; + } +} + +static int node_change_cmp(const void *change1, const void *change2) +{ + struct node_change *n1 = (struct node_change *)change1; + struct node_change *n2 = (struct node_change *)change2; + return !(dm_node_compatible(&n1->node, &n2->node) && (n1->redirected == n2->redirected)); +} + +static dm_node_id_t find_parent_with_apply_handler(const struct dm_node_info *node_info) +{ + if (node_info == NULL) { + return INVALID_DM_NODE_ID; + } + if (qjs_has_apply_handler(node_info->node_id)) { + return node_info->node_id; + } + + return find_parent_with_apply_handler(node_info->parent); +} + +int dm_appy_add_change(enum DATAMODEL_ACTION action, const dm_node_t *node) +{ + dmlog_debug("add change %s", dm_node_str(node)); + if (changed_node_list == NULL) { + changed_node_list = dm_list_create(node_change_cmp); + } + + struct node_change *pchange = calloc(1, sizeof(struct node_change)); + pchange->action = action; + pchange->node = *node; + const struct dm_node_info * info = dm_node_get_info(node->id); + const char *uci_map; + const struct dm_parameter *param = NULL; + + // special handling for "Order" parameter + if (info->type == DM_NODE_PARAMETER && info->flag & FLAG_HAS_ORDER) { + // Order parameter, + param = (const struct dm_parameter *)info; + const struct dm_object *pobj = dm_node_get_object(dm_node_i_parent_id(node->id)); + if (param->map.map == NULL && pobj->map.map) { + pchange->node.cnt--; + goto end; + } + } + + if (info->type == DM_NODE_PARAMETER) { + param = (const struct dm_parameter *)info; + uci_map = param->map.map; + } else { + const struct dm_object *obj = (const struct dm_object *)info; + uci_map = obj->map.map; + } + + if (uci_map == NULL && !qjs_has_apply_handler(node->id)) { + // look for parent handler + const dm_node_id_t pid = dm_node_i_parent_id(node->id); + if (pid != INVALID_DM_NODE_ID) { + const struct dm_object *pobj = dm_node_get_object(pid); + if (pobj && pobj->map.map) { + if (param && param->data_type == DM_PATH_NAME && param->data.paths && + dm_node_get_info(param->data.paths[0])->depends_node_id == pid) { + goto end; + } + dmlog_debug("skip apply for %s", dm_node_str(node)); + free(pchange); + return 0; + } + } + dm_node_id_t id = find_parent_with_apply_handler(info->parent); + if (id == INVALID_DM_NODE_ID) { + dmlog_debug("ignored node change for apply %s", dm_node_str(node)); + free(pchange); + return 0; + } + pchange->node.id = id; + pchange->node.cnt = dm_node_index_cnt(id); + } + + dm_node_id_t depend = dm_node_get_apply_depends(pchange->node.id); + if (depend != INVALID_DM_NODE_ID) { + pchange->node.id = depend; + pchange->redirected = 1; + } + +end: + if (dm_list_contains(changed_node_list, (const void*)pchange)) { + free(pchange); + return 0; + } + + if (dm_node_is_objectlist(pchange->node.id)) { + pchange->node.cnt = dm_node_index_cnt(pchange->node.id) - 1; + } + + dm_list_append(changed_node_list, pchange); + dmlog_debug("added node change %s", dm_node_str(&pchange->node)); + return 0; +} + +static char *get_package_name(const char *uci_path) +{ + char *dot_position = strchr(uci_path, '.'); + if (dot_position == NULL) { + dmlog_error("missing dot in the uci_path: %s", uci_path); + return NULL; + } + + int length = dot_position - uci_path; + char *result = (char *)malloc(length + 1); // +1 for the null-terminator + strncpy(result, uci_path, length); + result[length] = '\0'; + return result; +} + +static char *get_package_type(const char *uci_path) +{ + char *dot_position = strchr(uci_path, '.'); + + if (dot_position == NULL) { + dmlog_error("missing dot in the uci_path: %s", uci_path); + return NULL; + } + + size_t length = strlen(dot_position + 1) + 1; + char *substring = malloc(length); + + strncpy(substring, dot_position + 1, length); + substring[length - 1] = '\0'; + return substring; +} + +static dm_nodelist_h find_refer_instances(const dm_node_t *node, const dm_node_t *ref_node) +{ + dm_path_t path; + dm_node2name(ref_node, path, sizeof(dm_path_t)); + const struct dm_object *obj = dm_node_get_object(node->id); + // find the parameter that refers to the "ref_node" + for (int i = 0; i < obj->param_num; i++) { + const struct dm_parameter *param = (const struct dm_parameter*)obj->param_list[i]; + if (param->data_type == DM_PATH_NAME && param->data.paths[0] == ref_node->id) { + const struct dm_node_info *param_info = dm_node_get_info(param->node.node_id); + char *search = NULL; + asprintf(&search, "%s='%s'", param_info->name, path); + dmlog_debug("search key: %s", search); + dm_nodelist_h res = dm_nodelist_find(node, search, 1); + free(search); + return res; + } + } + + return DM_INVALID_NODELIST; +} + +static void apply_param_uci_map(const dm_node_t *node, const dm_node_t *parent, const struct dm_uci_map *map) +{ + dmlog_debug("apply_param_uci_map: %s", dm_node_str(node)); + char *val = NULL; + if (dbmgr_get(node, &val) < 0 || val == NULL) { + return; + } + + char *uci_path = NULL; + char *uci_pkg = NULL; + if (map->map) { + if (parent && strchr(map->map, '.') == NULL) { + char *uci_key = NULL; + dm_node_t key_node; + const struct dm_object *obj = dm_node_get_object(parent->id); + if (dm_node_get_child(parent, "_key", &key_node) < 0) { + dmlog_error("failed to get _key %s", dm_node_str(parent)); + goto end; + } + if (dbmgr_get(&key_node, &uci_key) < 0 || uci_key == NULL) { + dmlog_error("failed to call dbmgr_get %s", dm_node_str(&key_node)); + goto end; + } + + if (obj->map.map == NULL) { + dmlog_error("missing uci map %s", dm_node_str(parent)); + free(uci_key); + goto end; + } + + uci_pkg = get_package_name(obj->map.map); + asprintf(&uci_path, "%s.%s.%s", uci_pkg, uci_key, map->map); + free(uci_key); + } else { + uci_path = strdup(map->map); + uci_pkg = get_package_name(map->map); + } + + if (map->type == DM_UCI_MAP_TYPE_SIMPLE || map->type == DM_UCI_MAP_TYPE_INTERFACE) { + if (dm_node_data_type(node->id) == DM_PATH_NAME) { + // the the uci to the section name of the corresponding pathname + if (val[0] == '\0') { + dm_uci_set(uci_path, val); + } else { + char *key = NULL; + dm_node_t ref_node; + dm_path2node(val, &ref_node); + dbmgr_get_child(&ref_node, "_key", &key); + if (key != NULL) { + dm_uci_set(uci_path, key); + free(key); + } else { + dmlog_error("failed to get key of node %s", val); + } + } + } else { + if (dm_node_is_bool_type(node->id)) { + if (strcmp(val, "1") == 0 || strcmp(val, "true") == 0) + dm_uci_set(uci_path, "1"); + else + dm_uci_set(uci_path, "0"); + } else { + dm_uci_set(uci_path, val); + } + } + } else if (map->type == DM_UCI_MAP_TYPE_DISABLE) { + if (strcmp(val, "1") == 0 || strcmp(val, "true") == 0) + dm_uci_set(uci_path, "0"); + else + dm_uci_set(uci_path, "1"); + } else { + dmlog_debug("not support apply uci type: %s", dm_node_str(node)); + } + free(uci_path); + } + + qjs_call_apply_param_handler(node, val); + + add_apply_package(uci_pkg); + +end: + free(val); + return; +} + +struct dm_inst_key { + const char *key; + dm_node_t node; +}; + +static int dm_inst_list_cmp(const void *item1, const void *item2) +{ + struct dm_inst_key *inst1 = (struct dm_inst_key *)item1; + struct dm_inst_key *inst2 = (struct dm_inst_key *)item2; + if (strcmp((const char*)inst1->key, (const char*)inst2->key) == 0) { + return 0; + } + + return 1; +} + +static void free_dm_inst_list(dm_list_h list) +{ + int cnt = dm_list_cnt(list); + for (int i = 0; i < cnt; i++) { + struct dm_inst_key *inst = dm_list_get(list, i); + free((char *)inst->key); + } + + dm_list_free(list); +} + +static dm_list_h get_obj_key_list(dm_nodelist_h list) +{ + const dm_node_t *n = NULL; + dm_list_h key_list = dm_list_create(dm_inst_list_cmp); + nodelist_for_each_node(n, list) + { + char *key = NULL; + if (dbmgr_get_child(n, "_key", &key) == 0 && key != NULL) { + struct dm_inst_key *ins = malloc(sizeof(struct dm_inst_key)); + ins->key = key; + ins->node = *n; + dm_list_append(key_list, ins); + } else { + dmlog_error("failed to get key for node: %s", dm_node_str(n)); + } + } + + return key_list; +} + +static int apply_obj_uci_map(const dm_node_t *node, const struct dm_uci_map *map) +{ + int ret = 0; + char *pkg_name = get_package_name(map->map); + char *type = get_package_type(map->map); + dm_nodelist_h list = dm_nodelist_get_db(node); + dm_list_h keys = get_obj_key_list(list); + dm_list_h sect_list = dm_list_create(NULL); + + char *uci_key = NULL; + if (map->key != NULL && dm_node_index_cnt(node->id) > 1) { + dm_node_t parent; + dm_node_i_parent(node, &parent); + dbmgr_get_child(&parent, "_key", &uci_key); + } + + json_object *json_list = NULL; + if (dm_uci_get_section_list(pkg_name, type, NULL, 0, &json_list) == 0 && json_list != NULL) { + json_object_object_foreach(json_list, key, val) { + if (!qjs_uci_filter(node, val)) { + continue; + } + const char *key_val = NULL; + if (map->key != NULL) { + json_object *val_obj; + if (json_object_object_get_ex(val, map->key, &val_obj)) { + key_val = json_object_get_string(val_obj); + } + } + if (uci_key && strcmp(uci_key, key_val) != 0) { + continue; + } + + struct dm_inst_key inst; + inst.key = key; + if (!dm_list_contains(keys, &inst)) { + // special handling for user: set "deleted" instead of deleting + if (strcmp(pkg_name, "users") == 0 && strcmp(type, "user") == 0) { + char *tmp; + asprintf(&tmp, "%s.%s.deleted", pkg_name, key); + dm_uci_set(tmp, "1"); + free(tmp); + } else { + dmlog_debug("uci-map: del uci: %s.%s", pkg_name, key); + if (qjs_call_uci_deinit_handler(node, key) == 0) { + dm_uci_del(pkg_name, key); + } else { + dmlog_debug("uci-map: skipped deleting %s", key); + } + } + } else { + dm_list_append(sect_list, strdup(key)); + } + } + json_object_put(json_list); + } + + int key_cnt = dm_list_cnt(keys); + for (int i = 0; i < key_cnt; i++) { + struct dm_inst_key *inst = dm_list_get(keys, i); + if (!dm_list_contains(sect_list, (const void*)inst->key)) { + // add section for the new instance + dmlog_debug("uci-map: add uci: %s.%s", pkg_name, type); + name_val_t opt_val; + int opt_cnt = 0; + if (uci_key != NULL && map->key) { + opt_val.name = map->key; + opt_val.value = uci_key; + opt_cnt = 1; + } + + if (dm_uci_add(pkg_name, type, inst->key, &opt_val, opt_cnt) < 0) { + dmlog_error("failed to add new uci section"); + ret = -1; + } + + qjs_call_uci_init_handler(&inst->node); + } + } + + if (pkg_name) { + add_apply_package(pkg_name); + pkg_name = NULL; + } + + free(type); + if (pkg_name) { + free(pkg_name); + } + + if (uci_key) { + free(uci_key); + } + + dm_nodelist_free(list); + free_dm_inst_list(keys); + dm_list_free(sect_list); + return ret; +} + +static int compare_order(const void* a, const void* b) { + key_order_pair* pair_a = (key_order_pair*)a; + key_order_pair* pair_b = (key_order_pair*)b; + return pair_a->order - pair_b->order; +} + +static void reorder_uci_sections(const char *pkg, key_order_pair *pairs, int cnt) +{ + qsort(pairs, cnt, sizeof(key_order_pair), compare_order); + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "config", pkg); + void *a = blobmsg_open_array(&b, "sections"); + for (int i = 0; i < cnt; ++i) { + blobmsg_add_string(&b, NULL, pairs[i].key); + } + blobmsg_close_array(&b, a); + ubus_client_call("uci", "order", b.head, NULL, NULL); + blob_buf_free(&b); + + add_apply_package((char*)pkg); +} + +// reorder the uci sections according to the "Order" value +static void apply_order(const dm_node_t *node, const char *uci_map) +{ + dmlog_info("apply_order, %s, %s", dm_node_str(node), uci_map); + dm_nodelist_h list = dm_nodelist_get_db(node); + int cnt = dm_nodelist_cnt(list); + if (cnt <= 1) { + dm_nodelist_free(list); + return; + } + + key_order_pair* pairs = calloc(cnt, sizeof(key_order_pair)); + for (int i = 0; i < cnt; i++) { + char* order_str = NULL; + dbmgr_get_child(dm_nodelist_node(list, i), "Order", &order_str); + if (order_str == NULL) { + dmlog_error("apply_order, failed to get Order"); + goto exit; + } + pairs[i].order = atoi(order_str); + free(order_str); + pairs[i].key = dm_nodelist_key(list, i, "_key"); + if (pairs[i].key == NULL) { + dmlog_error("apply_order, unexpected empty key value"); + goto exit; + } + } + + char *pkg = get_package_name(uci_map); + reorder_uci_sections(pkg, pairs, cnt); + +exit: + for (int i = 0; i < cnt; i++) { + if (pairs[i].key) { + JS_FreeCString(qjs_ctx(), pairs[i].key); + } + } + free(pairs); + dm_nodelist_free(list); +} + +static void apply_extended_obj(const dm_node_t *node, const char *path, dm_node_id_t id) +{ + dmlog_debug("apply_extended_obj: %s, %s", dm_node_str(node), path); + dm_node_t ref_node; + if (path == NULL) { + return; + } + if (path[0] == '\0') { + // call the deinit handler for the extended object + dm_node_t n = {id}; + char *uci_key = NULL; + dbmgr_get_child(node, "_key", &uci_key); + if (uci_key) { + qjs_call_uci_deinit_handler(&n, uci_key); + free(uci_key); + } + return; + } + + if (dm_path2node(path, &ref_node) != 0) { + dmlog_error("apply_order, invalid path: %s", path); + return; + } + + const struct dm_object *obj = dm_node_get_object(ref_node.id); + for (int i = 0; i < obj->param_num; i++) { + const struct dm_parameter *param = (const struct dm_parameter *)obj->param_list[i]; + if (param->node.table_name == NULL) { + continue; + } + if (param->map.map == NULL && !qjs_has_apply_handler(node->id)) { + continue; + } + + dm_node_t n = ref_node; + n.id = obj->param_list[i]->node_id; + apply_param_uci_map(&n, node, ¶m->map); + } +} + +void dm_apply_node(struct node_change *change) +{ + const dm_node_t *node = &change->node; + dm_node_t *pparent = NULL; + const struct dm_object *parent_obj = NULL; + dm_node_t parent_node; + if (dm_node_i_parent(node, &parent_node) == 0) { + pparent = &parent_node; + parent_obj = dm_node_get_object(parent_node.id); + } + + if (parent_obj && parent_obj->map.map && parent_obj->node.depends_node_id != INVALID_DM_NODE_ID) { + // apply to the referenced object that it "extends" + dm_node_t refer_node = parent_node; + refer_node.id = parent_obj->node.depends_node_id; + refer_node.cnt--; + dm_nodelist_h res = find_refer_instances(&refer_node, &parent_node); + const dm_node_t *pnode = NULL; + nodelist_for_each_node(pnode, res) { + if (qjs_has_apply_handler(node->id)) { + qjs_call_apply_obj_handler(node, pnode); + } + else if (dm_node_is_parameter(node->id)) { + const struct dm_parameter *param = dm_node_get_parameter(node->id); + apply_param_uci_map(node, pnode, ¶m->map); + } + } + dm_nodelist_free(res); + return; + } + + if (dm_node_is_parameter(node->id)) { + const struct dm_parameter *param = dm_node_get_parameter(node->id); + if (param->data_type == DM_PATH_NAME && param->data.paths) { + if (pparent && dm_node_get_info(param->data.paths[0])->depends_node_id == pparent->id) { + char *val = NULL; + dbmgr_get(node, &val); + apply_extended_obj(pparent, val, param->data.paths[0]); + return; + } + } + if (param->map.map != NULL || qjs_has_apply_handler(node->id)) { + apply_param_uci_map(node, pparent, ¶m->map); + if (param->node.flag & FLAG_HAS_ORDER) { + const struct dm_object *pobj = dm_node_get_object(dm_node_i_parent_id(node->id)); + if (pobj && pobj->map.map) { + dm_node_t pnode; + dm_node_i_parent(node, &pnode); + apply_order(&pnode, pobj->map.map); + } + } + } + } else if (dm_node_is_objectlist(node->id)) { + const struct dm_object *obj = dm_node_get_object(node->id); + if (obj->map.map != NULL && obj->map.type == DM_UCI_MAP_TYPE_SIMPLE ) { + if (obj->node.depends_node_id == INVALID_DM_NODE_ID) { + apply_obj_uci_map(node, &obj->map); + } + } else if (qjs_has_apply_handler(node->id)) { + qjs_call_apply_obj_handler(node, node); + } + } else if (qjs_has_apply_handler(node->id)) { + qjs_call_apply_obj_handler(node, node); + } else { + dmlog_error("dm_apply_node, node is not handled: %s", dm_node_str(node)); + } +} + +static int commit_changes() +{ + int ret = 0; + + if (changed_node_list == NULL) + return 0; + + int i; + int cnt = dm_list_cnt(changed_node_list); + + for (i = 0; i < cnt; i++) { + struct node_change *change = (struct node_change *)dm_list_get(changed_node_list, i); + dmlog_debug("apply node: %s", dm_node_str(&change->node)); + if (change->redirected) { + qjs_call_apply_obj_handler(&change->node, &change->node); + } else { + dm_apply_node(change); + } + dmlog_debug("end of apply node: %s", dm_node_str(&change->node)); + } + return ret; +} + +// reset parameters that are confidential in db. +static void reset_confidentials() +{ + if (changed_node_list == NULL) + return; + + int i; + dbmgr_tranx_begin(); + int cnt = dm_list_cnt(changed_node_list); + for (i = 0; i < cnt; i++) { + struct node_change *change = (struct node_change *)dm_list_get(changed_node_list, i); + if (dm_node_is_confidential(change->node.id)) { + if (dbmgr_set(&change->node, "") < 0) { + dmlog_error("failed to reset confidentail parameter: %s", dm_node_str(&change->node)); + } + } + } + + dbmgr_tranx_commit(); +} + +int dm_apply_reset(void) +{ + dm_apply_reset_changes(); + return 0; +} + +static int reload_service(const char *svc) +{ + struct blob_buf bb = {}; + if (svc == NULL) { + return 0; + } + + blob_buf_init(&bb, 0); + blobmsg_add_string(&bb, "config", svc); + int ret = ubus_client_call("uci", "commit", bb.head, NULL, NULL); + if (ret != 0) { + dmlog_error("failed to reload service %s", svc); + } else { + dmlog_info("reloaded service %s", svc); + } + blob_buf_free(&bb); + return ret; +} + +int dm_apply_do_apply() +{ + int i; + + commit_changes(); + + int cnt = dm_list_cnt(apply_uci_list); + for (i = 0; i < cnt; i++) { + char *pkg_name = dm_list_get(apply_uci_list, i); + dm_uci_commit(pkg_name); + } + + cnt = dm_list_cnt(apply_uci_list); + for (i = 0; i < cnt; i++) { + char *pkg_name = dm_list_get(apply_uci_list, i); + reload_service(pkg_name); + } + + reset_confidentials(); + dm_apply_reset(); + return 0; +} diff --git a/dm-framework/dm-api/src/core/dm_apply.h b/dm-framework/dm-api/src/core/dm_apply.h new file mode 100644 index 000000000..d8f9fe139 --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_apply.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DM_APPLY_H +#define DM_APPLY_H + +#include + +#include "dm_node.h" + +enum DATAMODEL_ACTION { + DATA_MODEL_ADD, + DATA_MODEL_SET, + DATA_MODEL_DELETE +}; + +struct node_change { + dm_node_t node; + enum DATAMODEL_ACTION action; + int redirected; +}; + +int dm_appy_add_change(enum DATAMODEL_ACTION action, const dm_node_t *node); +void dm_apply_reset_changes(void); +int dm_apply_reset(void); +int dm_apply_do_apply(); +void dm_apply_node(struct node_change *change); +void add_apply_package(char *package); +#endif /* DM_APPLY_H */ diff --git a/dm-framework/dm-api/src/core/dm_import.c b/dm-framework/dm-api/src/core/dm_import.c new file mode 100644 index 000000000..6ba56fd92 --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_import.c @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include + +#include "dm_uci.h" +#include "dbmgr.h" +#include "dm.h" +#include "dm_log.h" +#include "dm_node.h" +#include "ubus_client.h" +#include "dm_api.h" +#include "qjs.h" +#include "dm_list.h" + +static char* get_tr181_pathname(const char *pn, const char *uci_path, const char *uci_val) +{ + json_object *values; + if (uci_val == NULL || uci_val[0] == '\0') { + return NULL; + } + + char *path = strdup(uci_path); + char *config = strtok(path, "."); + char *type = strtok(NULL, "."); + if (dm_uci_get_section_list(config, type, NULL, 0, &values) < 0 || values == NULL) { + dmlog_error("get_tr181_pathname, dm_uci_get_section_list failed"); + free(path); + return NULL; + } + + free(path); + + int is_network_interface = 0; + dm_list_h itf_dev_list = NULL; + // special handling for network interface + if (strcmp(uci_path, "network.interface") == 0) { + is_network_interface = 1; + itf_dev_list = dm_list_create(NULL); + } + int index = 1; + // Iterate over all keys in the 'values' object + json_object_object_foreach(values, key, value) { + (void)key; + struct json_object *name_value = NULL; + struct json_object *anonymous = NULL; + if (json_object_object_get_ex(value, ".anonymous", &anonymous) + && json_object_get_boolean(anonymous)) { + json_object_object_get_ex(value, "name", &name_value); + } else { + json_object_object_get_ex(value, ".name", &name_value); + } + if (is_network_interface) { + // special handling for network interface: skip the loopback and dhcpv6 interfaces. + struct json_object *dev_value; + if (json_object_object_get_ex(value, "device", &dev_value)) { + const char *dev_name = json_object_get_string(dev_value); + if (strcmp(dev_name, "lo") == 0) { + continue; + } + if (dm_list_contains(itf_dev_list, (const void*)dev_name)) { + continue; + } + dm_list_append(itf_dev_list, strdup(dev_name)); + } + } + if (strcmp(json_object_get_string(name_value), uci_val) == 0) { + // Found the object with the given name, return its index + char *ret_val = NULL; + asprintf(&ret_val, "%s%d", pn, index); + json_object_put(values); + return ret_val; + } else { + index++; + } + } + if (is_network_interface) { + dm_list_free(itf_dev_list); + } + json_object_put(values); + return NULL; +} +static int import_pathname(const dm_node_t *node, char *uci_value) +{ + const struct dm_parameter * param = dm_node_get_parameter(node->id); + if (param->data.paths) { + dm_node_t ref_node = *node; + ref_node.id = param->data.paths[0]; + const struct dm_object *obj = dm_node_get_object(ref_node.id); + if (obj && obj->map.map) { + dm_path_t pathname; + ref_node.cnt = dm_node_index_cnt(ref_node.id) - 1; + if (dm_node2name(&ref_node, pathname, sizeof(pathname)) < 0) { + dmlog_error("import_pathname, invalid node"); + return -1; + } + // trim the last {i}. + int len = strlen(pathname); + pathname[len - 4] = '\0'; + + char *pn = get_tr181_pathname(pathname, obj->map.map, uci_value); + if (pn) { + int ret = dmapi_param_set(node, pn); + free(pn); + return ret; + } else { + dmlog_error("import_pathname, get_tr181_pathname failed"); + return -1; + } + } + } + + return -1; +} + +static int importParam(const dm_node_t *node, const struct dm_uci_map *map) +{ + char * val = NULL; + if (map->type == DM_UCI_MAP_TYPE_JS) { + JSValue result = qjs_eval_buf(map->map, strlen(map->map), "eval-import-js", JS_EVAL_TYPE_GLOBAL); + if (!JS_IsUndefined(result) && !JS_IsException(result)) { + const char *result_cstr = JS_ToCString(qjs_ctx(), result); + dmapi_param_set(node, result_cstr); + JS_FreeCString(qjs_ctx(), result_cstr); + JS_FreeValue(qjs_ctx(), result); + } else { + dmlog_error("failed to import js: %s", map->map); + } + return 0; + } else { + if (node->cnt == 0 || strchr(map->map, '.') != NULL) { + // uci option with full path + dm_uci_get(map->map, &val); + } else { + // uci option from the js object. + char *buf; + asprintf(&buf, "_arg['%s']", map->map); + JSValue result = qjs_eval_buf(buf, strlen(buf), "eval-uci-js", JS_EVAL_TYPE_GLOBAL); + free(buf); + if (!JS_IsException(result) && !JS_IsUndefined(result)) { + const char *result_cstr = JS_ToCString(qjs_ctx(), result); + val = strdup(result_cstr); + JS_FreeCString(qjs_ctx(), result_cstr); + } + JS_FreeValue(qjs_ctx(), result); + } + } + + const struct dm_parameter * param = dm_node_get_parameter(node->id); + if (val == NULL) { + // uci option not present, use the default value defined in JSON if exist. + if (param->default_uci_val) { + dmapi_param_set(node, param->default_uci_val); + } + return 0; + } + + if (map->type == DM_UCI_MAP_TYPE_DISABLE) { + if (*val == '0') { + dmapi_param_set(node, "true"); + } else { + dmapi_param_set(node, "false"); + } + } else if (param->data_type == DM_DATA_BOOLEAN) { + if (*val == '1' || !strcmp(val, "true") || !strcmp(val, "yes")) { + dmapi_param_set(node, "true"); + } else { + dmapi_param_set(node, "false"); + } + } else if (map->type == DM_UCI_MAP_TYPE_INTERFACE) { + char *pn = get_tr181_pathname("Device.IP.Interface.", "network.interface", val); + if (pn) { + dmapi_param_set(node, pn); + free(pn); + } else { + dmlog_error("import_pathname for network interface failed %s, %s", val, dm_node_str(node)); + } + } else { + if (dm_node_data_type(node->id) == DM_PATH_NAME) { + import_pathname(node, val); + } else { + if (param->data_type == DM_DATA_ENUM && param->data.enum_strings) { + // convert the uci case to the tr181 case, ex 'udp' => 'UDP', 'up' => 'Up' + int i = 0; + while (param->data.enum_strings[i]) { + if (strcasecmp(param->data.enum_strings[i], val) == 0) { + dmapi_param_set(node, param->data.enum_strings[i]); + break; + } + i++; + } + + if (!param->data.enum_strings[i]) { + dmlog_error("uci value %s not found in the enum, %s", val, dm_node_str(node)); + } + } else { + dmapi_param_set(node, val); + } + } + } + + free(val); + return 0; +} + +static int import_dm_obj(const dm_node_t *node, JSValue js_values); + +static int handle_obj_uci_map(const dm_node_t *node, const struct dm_uci_map *map) +{ + int ret; + char *path = strdup(map->map); + char *config = strtok(path, "."); + char *type = strtok(NULL, "."); + json_object *values_obj; + + if (map->key != NULL && dm_node_index_cnt(node->id) > 1) { + dm_node_t parent; + dm_node_i_parent(node, &parent); + const struct dm_object *obj = dm_node_get_object(parent.id); + if (obj->map.map == NULL || obj->map.type != DM_UCI_MAP_TYPE_SIMPLE) { + dmlog_error("handle_obj_uci_map missing parent map uci: %s", dm_node_str(node)); + return -1; + } + char *key_val = NULL; + if (dbmgr_get_child(&parent, "_key", &key_val) < 0 || key_val == NULL) { + dmlog_error("handle_obj_uci_map dbmgr_get_child failed: %s", dm_node_str(node)); + return -1; + } + name_val_t match = {map->key, key_val}; + ret = dm_uci_get_section_list(config, type, &match, 1, &values_obj); + free(key_val); + } else { + ret = dm_uci_get_section_list(config, type, NULL, 0, &values_obj); + } + + free(path); + + if (ret != 0 || values_obj == NULL) { + dmlog_warn("dm_uci_get_section_list failed, uci: %s", map->map); + // uci could be missing, skip the error + return 0; + } + + int obj_index = 0; + json_object_object_foreach(values_obj, key, val) { + (void)key; + if (!qjs_uci_filter(node, val)) { + continue; + } + dm_node_t new_inst = *node; + if (dmapi_object_add(&new_inst) < 0) { + dmlog_error("failed to add new instance"); + ret = -1; + break; + } + + json_object *name_obj; + if (json_object_object_get_ex(val, ".name", &name_obj)) { + dm_node_t key_node = new_inst; + key_node.id = dm_node_get_child_id(new_inst.id, "_key"); + if (dmapi_param_set(&key_node, json_object_get_string(name_obj)) < 0) { + dmlog_error("failed to set param value"); + ret = -1; + } + } else { + dmlog_error("failed to get uci name"); + ret = -1; + break; + } + + JSValue js_val = json_object_to_jsvalue(val); + JS_SetPropertyStr(qjs_ctx(), qjs_global(), "_arg", js_val); + JS_SetPropertyStr(qjs_ctx(), js_val, ".index", JS_NewInt32(qjs_ctx(), obj_index)); + obj_index++; + ret = import_dm_obj(&new_inst, js_val); + if (ret < 0) { + dmlog_error("failed to import node %s", dm_node_str(&new_inst)); + // break; + } + } + + json_object_put(values_obj); + return 0; +} + +extern int update_dm_value(JSContext *ctx, const char *key, JSValue value); + +static int import_dm_obj(const dm_node_t *node, JSValue js_values) { + if (qjs_has_import_handler(node->id)) { + int ret = 0; + dm_path_t node_path; + dm_node2name(node, node_path, sizeof(dm_path_t)); + if (dm_node_is_object(node->id)) { + strcat(node_path, "."); + } + + JSValue input_val = js_values; + JSValue js_val = qjs_call_import_handler(node, input_val); + if (!JS_IsException(js_val) && !JS_IsUndefined(js_val)) { + int len = strlen(node_path); + if (node_path[len-2] == '}') { + node_path[len-4] = '\0'; + } + const char *imported_val = get_js_value_str(js_val); + free_js_cstr(imported_val); + ret = update_dm_value(qjs_ctx(), node_path, js_val); + if (ret < 0) { + dmlog_error("update_dm_value failed"); + } + } + JS_FreeValue(qjs_ctx(), js_val); + return ret; + } + + if (dm_node_is_parameter(node->id)) { + if (dm_node_is_confidential(node->id) || !dm_node_has_db(node->id)) { + return 0; + } + const struct dm_parameter * param = dm_node_get_parameter(node->id); + if (param->map.map == NULL) { + return 0; + } + return importParam(node, ¶m->map); + } + + if (dm_node_is_objectlist(node->id) && !dm_node_is_index_complete(node)) { + const struct dm_object *obj = dm_node_get_object(node->id); + if (obj->map.map != NULL && obj->map.type == DM_UCI_MAP_TYPE_SIMPLE) { + return handle_obj_uci_map(node, &obj->map); + } else { + dmlog_debug("ignored import for object: %s", dm_node_str(node)); + } + + return 0; + } + + if (dm_node_is_object(node->id) || dm_node_is_objectlist(node->id)) { + const struct dm_object * obj = dm_node_get_object(node->id); + for (int i = 0; i < obj->param_num; i++) { + dm_node_t param_node = *node; + param_node.id = obj->param_list[i]->node_id; + if (import_dm_obj(¶m_node, js_values) < 0) { + dmlog_error("import param failed: %s", dm_node_str(¶m_node)); + } + } + + for (int i = 0; i < obj->object_num; i++) { + dm_node_t node_obj = *node; + node_obj.id = obj->object_list[i]->node_id; + if (import_dm_obj(&node_obj, js_values) < 0) { + dmlog_debug("import object failed: %s", dm_node_str(&node_obj)); + } + } + } + + return 0; +} + +int importDM() +{ + dm_node_t root_node = dm_init_node(DM_DEVICE); + return import_dm_obj(&root_node, JS_UNDEFINED); +} diff --git a/dm-framework/dm-api/src/core/dm_linker.c b/dm-framework/dm-api/src/core/dm_linker.c new file mode 100755 index 000000000..51dc8cb5b --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_linker.c @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dm_api.h" +#include "dm_node.h" +#include "dm_log.h" +#include "dm.h" +#include "dbmgr.h" + +#define LOCK_FILE "/var/lock/bbfdm_reference_db.lock" +#define UCI_PACKAGE "bbfdm_reference_db" + +// Forward declarations +static char *calculate_hash(const char *input); +static char *build_wildcard_path(const char *path); +static char *build_linker_string(const char *wildcard_path, const char *key_name, const char *key_value); +static int process_linker_node(struct uci_context *ctx, struct uci_package *pkg, + const char *service_hash, const dm_node_t *node); +static int walk_data_model_tree(struct uci_context *ctx, struct uci_package *pkg, + const char *service_hash, const dm_node_t *node); +static int clear_service_entries(struct uci_context *ctx, struct uci_package *pkg, + const char *service_hash); +static int ensure_uci_sections(struct uci_context *ctx, struct uci_package *pkg); + +/** + * Calculate 8-character hash from input string using SHA-256 + */ +static char *calculate_hash(const char *input) { + if (!input) return NULL; + + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, input, strlen(input)); + SHA256_Final(hash, &sha256); + + char *result = malloc(9); // 8 chars + null terminator + if (!result) return NULL; + + snprintf(result, 9, "%02X%02X%02X%02X", + hash[0], hash[1], hash[2], hash[3]); + + return result; +} + +/** + * Build linker string: "wildcard_path[key_name==key_value]." + */ +static char *build_linker_string(const char *wildcard_path, const char *key_name, const char *key_value) { + if (!wildcard_path || !key_name || !key_value) return NULL; + + size_t len = strlen(wildcard_path) + strlen(key_name) + strlen(key_value) + 10; + char *result = malloc(len); + if (!result) return NULL; + + snprintf(result, len, "%s[%s==%s].", wildcard_path, key_name, key_value); + return result; +} + +/** + * Check if a node ID is in the linker nodes array + */ +static int is_linker_node(dm_node_id_t node_id) { + extern const dm_node_id_t dm_linker_nodes[]; + + for (int i = 0; dm_linker_nodes[i] != INVALID_DM_NODE_ID; i++) { + if (dm_linker_nodes[i] == node_id) { + return 1; + } + } + return 0; +} + +/** + * Process a single linker parameter node + */ +static int process_linker_node(struct uci_context *ctx, struct uci_package *pkg, + const char *service_hash, const dm_node_t *node) { + const struct dm_parameter *param = dm_node_get_parameter(node->id); + if (!param) return -1; + + // Check if this parameter is a linker node + if (!is_linker_node(node->id)) { + return 0; // Not a linker parameter + } + + // Get the parent object path + dm_node_t parent_node; + if (dm_node_i_parent(node, &parent_node) != 0) { + dmlog_error("Failed to get parent node for %s", dm_node_str(node)); + return -1; + } + + // Get parent path + dm_path_t parent_path; + if (dm_node2name(&parent_node, parent_path, sizeof(parent_path)) != 0) { + dmlog_error("Failed to get parent path for %s", dm_node_str(&parent_node)); + return -1; + } + + // Get current path (same as parent for leaf parameters) + dm_path_t current_path; + if (dm_node2name(&parent_node, current_path, sizeof(current_path)) != 0) { + dmlog_error("Failed to get current path for %s", dm_node_str(&parent_node)); + return -1; + } + + // Get parameter value + char *key_value = NULL; + if (dbmgr_get(node, &key_value) != 0 || !key_value) { + dmlog_debug("No value found for linker parameter %s", dm_node_str(node)); + return 0; + } + + // Compose linker string directly from parent_path, keeping instance numbers intact + const char *key_name = param->node.name; + size_t len_linker = strlen(parent_path) + strlen(key_name) + strlen(key_value) + 6; // extra for [==]. + char *linker_string = malloc(len_linker); + if (!linker_string) { + free(key_value); + return -1; + } + snprintf(linker_string, len_linker, "%s[%s==%s].", parent_path, key_name, key_value); + + // Calculate hashes + char *hash_path = calculate_hash(linker_string); + char *hash_value = calculate_hash(current_path); + + if (!hash_path || !hash_value) { + free(linker_string); + free(key_value); + free(hash_path); + free(hash_value); + return -1; + } + + // Update UCI sections + struct uci_section *ref_path_section = uci_lookup_section(ctx, pkg, "reference_path"); + struct uci_section *ref_value_section = uci_lookup_section(ctx, pkg, "reference_value"); + struct uci_section *service_section = uci_lookup_section(ctx, pkg, service_hash); + + if (!ref_path_section || !ref_value_section || !service_section) { + dmlog_error("Failed to find required UCI sections"); + free(linker_string); + free(key_value); + free(hash_path); + free(hash_value); + return -1; + } + + // Set reference_path option + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = "reference_path"; + ptr.option = hash_path; + ptr.value = current_path; + uci_set(ctx, &ptr); + + // Set reference_value option + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = "reference_value"; + ptr.option = hash_value; + ptr.value = (strlen(key_value) > 0) ? key_value : "#"; + uci_set(ctx, &ptr); + + // Add to service section lists + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = service_hash; + ptr.option = "reference_path"; + ptr.value = hash_path; + uci_add_list(ctx, &ptr); + + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = service_hash; + ptr.option = "reference_value"; + ptr.value = hash_value; + uci_add_list(ctx, &ptr); + + dmlog_debug("Processed linker: %s -> %s (path_hash=%s, value_hash=%s)", + linker_string, current_path, hash_path, hash_value); + + // Cleanup + free(linker_string); + free(key_value); + free(hash_path); + free(hash_value); + + return 0; +} + +/** + * Process all linker nodes by iterating through the linker nodes array + */ +static int process_all_linker_nodes(struct uci_context *ctx, struct uci_package *pkg, + const char *service_hash) { + extern const dm_node_id_t dm_linker_nodes[]; + + // Iterate through all linker nodes + for (int i = 0; dm_linker_nodes[i] != INVALID_DM_NODE_ID; i++) { + dm_node_id_t linker_id = dm_linker_nodes[i]; + + // Check if this is a parameter node + if (!dm_node_is_parameter(linker_id)) { + continue; + } + + // Get all instances of multi-instance objects that contain this linker parameter + const struct dm_parameter *param = dm_node_get_parameter(linker_id); + if (!param || !param->node.parent) { + continue; + } + + // Find the object that contains this parameter + dm_node_id_t parent_id = param->node.parent->node_id; + + // Only handle multi-instance objects; skip single-instance objects + if (dm_node_is_objectlist(parent_id)) { + dm_node_t parent_node = {0}; + parent_node.id = parent_id; + + dm_nodelist_h list = dm_nodelist_get_db(&parent_node); + if (list != DM_INVALID_NODELIST) { + const dm_node_t *instance_node; + nodelist_for_each_node(instance_node, list) { + // Create the parameter node for this instance + dm_node_t param_node = *instance_node; + param_node.id = linker_id; + + // Process this linker parameter instance + process_linker_node(ctx, pkg, service_hash, ¶m_node); + } + dm_nodelist_free(list); + } + } + } + + return 0; +} + +/** + * Clear existing entries for this service + */ +static int clear_service_entries(struct uci_context *ctx, struct uci_package *pkg, + const char *service_hash) { + struct uci_section *service_section = uci_lookup_section(ctx, pkg, service_hash); + if (!service_section) { + return 0; // Section doesn't exist yet + } + + // Clear the lists + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = service_hash; + ptr.option = "reference_path"; + uci_delete(ctx, &ptr); + + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = service_hash; + ptr.option = "reference_value"; + uci_delete(ctx, &ptr); + + return 0; +} + +/** + * Ensure required UCI sections exist + */ +static int ensure_uci_sections(struct uci_context *ctx, struct uci_package *pkg) { + struct uci_ptr ptr; + + // Ensure reference_path section exists + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = "reference_path"; + ptr.value = "reference_path"; + if (uci_set(ctx, &ptr) != UCI_OK) { + return -1; + } + + // Ensure reference_value section exists + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = "reference_value"; + ptr.value = "reference_value"; + if (uci_set(ctx, &ptr) != UCI_OK) { + return -1; + } + + return 0; +} + +/** + * Main API function to refresh linker nodes + */ +int dm_refresh_linker_nodes(const char *service_name) { + if (!service_name) { + dmlog_error("Service name is required"); + return -1; + } + + dmlog_info("Refreshing linker nodes for service: %s", service_name); + + // Step 1: Open and lock the reference database + int lock_fd = open(LOCK_FILE, O_CREAT | O_RDWR, 0644); + if (lock_fd < 0) { + dmlog_error("Failed to open lock file: %s", LOCK_FILE); + return -1; + } + + if (flock(lock_fd, LOCK_EX) != 0) { + dmlog_error("Failed to acquire exclusive lock"); + close(lock_fd); + return -1; + } + + // Step 2: Calculate service hash + char *service_hash = calculate_hash(service_name); + if (!service_hash) { + dmlog_error("Failed to calculate service hash"); + close(lock_fd); + return -1; + } + + dmlog_debug("Service hash for '%s': %s", service_name, service_hash); + + // Step 3: Initialize UCI context and point to /var/state for runtime reference DB + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + dmlog_error("Failed to allocate UCI context"); + free(service_hash); + close(lock_fd); + return -1; + } + + // Use /var/state as configuration directory + uci_set_confdir(ctx, "/var/state"); + + // Load or create the UCI package + struct uci_package *pkg = NULL; + if (uci_load(ctx, UCI_PACKAGE, &pkg) != UCI_OK) { + // Package doesn't exist, create it + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = UCI_PACKAGE; + if (uci_set(ctx, &ptr) != UCI_OK) { + dmlog_error("Failed to create UCI package: %s", UCI_PACKAGE); + uci_free_context(ctx); + free(service_hash); + close(lock_fd); + return -1; + } + pkg = ptr.p; + } + + // Step 4: Ensure required sections exist + if (ensure_uci_sections(ctx, pkg) != 0) { + dmlog_error("Failed to ensure UCI sections"); + uci_free_context(ctx); + free(service_hash); + close(lock_fd); + return -1; + } + + // Create service section + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = pkg->e.name; + ptr.section = service_hash; + ptr.value = "service"; + uci_set(ctx, &ptr); + + // Step 5: Clear existing entries for this service + clear_service_entries(ctx, pkg, service_hash); + + // Step 6: Process all linker nodes + if (process_all_linker_nodes(ctx, pkg, service_hash) != 0) { + dmlog_error("Failed to process linker nodes"); + uci_free_context(ctx); + free(service_hash); + close(lock_fd); + return -1; + } + + // Step 7: Commit changes + if (uci_commit(ctx, &pkg, false) != UCI_OK) { + dmlog_error("Failed to commit UCI changes"); + uci_free_context(ctx); + free(service_hash); + close(lock_fd); + return -1; + } + + // Cleanup + uci_free_context(ctx); + free(service_hash); + + // Step 8: Release lock + flock(lock_fd, LOCK_UN); + close(lock_fd); + + dmlog_info("Successfully refreshed linker nodes for service: %s", service_name); + return 0; +} \ No newline at end of file diff --git a/dm-framework/dm-api/src/core/dm_linker.h b/dm-framework/dm-api/src/core/dm_linker.h new file mode 100755 index 000000000..29df562f0 --- /dev/null +++ b/dm-framework/dm-api/src/core/dm_linker.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DM_LINKER_H +#define DM_LINKER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Refresh linker nodes for a given service + * + * This function walks through the entire data model tree and updates the + * UCI reference database with linker parameter information for the specified + * service. It creates a deterministic mapping between object paths and their + * linker parameter values. + * + * The function: + * 1. Takes an exclusive lock on the reference database + * 2. Calculates a service hash from the service name + * 3. Prepares the UCI package with required sections + * 4. Walks through all multi-instance objects looking for linker parameters + * 5. Builds linker strings and generates hashes + * 6. Updates the UCI reference database + * 7. Commits changes and releases the lock + * + * @param service_name The UBus object name of the service (e.g., "bbfdm.core") + * @return 0 on success, -1 on error + */ +int dm_refresh_linker_nodes(const char *service_name); + +#ifdef __cplusplus +} +#endif + +#endif /* DM_LINKER_H */ \ No newline at end of file diff --git a/dm-framework/dm-api/src/core/inode_buf.c b/dm-framework/dm-api/src/core/inode_buf.c new file mode 100644 index 000000000..839a5e62a --- /dev/null +++ b/dm-framework/dm-api/src/core/inode_buf.c @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include + +#include "inode_buf.h" +#include "dm_log.h" +#include "qjs.h" +#include "utils.h" + +#define GROWTH_FACTOR 2 +#define DB_INFO_TIMEOUT 2000 + +extern void dmapi_refresh_instance_buf(const dm_node_t *node); + +static struct inode_entry *inode_g = NULL; + +dm_inst_array_t* dm_inst_array_create(size_t initial_capacity) { + dm_inst_array_t *array = calloc(1, sizeof(dm_inst_array_t)); + if (initial_capacity <= 0) { + initial_capacity = 1; + } + array->data = calloc(initial_capacity, sizeof(inst_entry_t)); + array->size = 0; + array->capacity = initial_capacity; + return array; +} + +static void get_all_i_node(dm_node_id_t id, dm_node_id_t *nodes_id, int *cnt) +{ + if (!dm_node_is_objectlist(id)) { + id = dm_node_i_parent_id(id); + if (id == INVALID_DM_NODE_ID) { + *cnt = 0; + return; + } + } + + nodes_id[0] = id; + *cnt = 1; + dm_node_id_t p = dm_node_i_parent_id(id); + while (p != INVALID_DM_NODE_ID) { + nodes_id[*cnt] = p; + (*cnt)++; + if (*cnt >= MAX_DM_NODE_DEPTH) { + dmlog_info("too long inode"); + // panic + break; + } + p = dm_node_i_parent_id(p); + } +} + +static struct inode_entry *find_inode_entry(struct inode_entry *head, const dm_node_id_t key) +{ + struct inode_entry *current = head; + while (current != NULL) { + if (current->key == key) { + return current; + } + current = current->next; + } + + return NULL; +} + +static inst_entry_t* dm_inst_array_find(dm_inst_array_t *array, unsigned int index) { + if (array == NULL) { + return NULL; + } + for (size_t i = 0; i < array->size; i++) { + if (array->data[i].index == index) { + return &array->data[i]; + } + } + return NULL; +} + +static inst_entry_t *find_inst(struct inode_entry *entry, unsigned int index, int *db) +{ + inst_entry_t *inst = dm_inst_array_find(entry->insts, index); + if (inst == NULL) { + inst = dm_inst_array_find(entry->dyn_insts, index); + if (db && inst) { + *db = 0; + } + } else if (db) { + *db = 1; + } + return inst; +} + +static void free_inode(struct inode_entry *inode); + +void dm_inst_array_free(dm_inst_array_t *array) { + if (array == NULL) { + return; + } + for (size_t i = 0; i < array->size; i++) { + JS_FreeValue(qjs_ctx(), array->data[i].value); + free_inode(array->data[i].inode); + } + + if (array->data){ + free(array->data); + } + + free(array); +} +static void free_insts(struct inode_entry *entry) +{ + dm_inst_array_free(entry->insts); + dm_inst_array_free(entry->dyn_insts); + entry->insts = NULL; + entry->dyn_insts = NULL; +} + +static void free_inode(struct inode_entry *inode) +{ + if (inode == NULL) { + return; + } + + struct inode_entry *current = inode; + struct inode_entry *next; + while (current != NULL) { + next = current->next; + free_insts(current); + free(current); + current = next; + } +} + +int inode_buf_get_js_values(const dm_node_t *node, JSValue values[]) +{ + int cnt = 0; + dm_node_id_t nodes_id[MAX_DM_NODE_DEPTH]; + get_all_i_node(node->id, nodes_id, &cnt); + + struct inode_entry **inode = &inode_g; + + dm_node_t curr_node; + curr_node.cnt = 0; + + int index_i = 0; + for (int i = cnt - 1; i >= 0; i--) { + if (index_i >= node->cnt) { + // not complete index: A.1.B.1.C.{i}. + return index_i; + } + + curr_node.id = nodes_id[i]; + struct inode_entry *entry = find_inode_entry(*inode, nodes_id[i]); + if (entry == NULL) { + dmapi_refresh_instance_buf(&curr_node); + entry = find_inode_entry(*inode, nodes_id[i]); + } + + if (entry == NULL) { + dmlog_error("inode_buf_get_js_values, inode_entry %s not found, %s", dm_node_str(&curr_node), dm_node_str(node)); + return -1; + } + + int db = 0; + inst_entry_t *inst = find_inst(entry, node->index[index_i], &db); + if (inst == NULL) { + dmlog_error("inode_buf_get_js_values, nst_entry not found %s, %d", dm_node_str(node), node->index[index_i]); + return -1; + } + values[index_i++] = inst->value; + inode = &inst->inode; + curr_node.index[curr_node.cnt] = inst->index; + curr_node.cnt++; + + if (db && qjs_has_info_handler(curr_node.id)) { + if (inst->value_time == 0) { + qjs_call_info_handler(&curr_node, inst->value); + inst->value_time = get_uptime_msecs(); + } else { + unsigned int now = get_uptime_msecs(); + if ((now - inst->value_time) >= DB_INFO_TIMEOUT) { + qjs_call_info_handler(&curr_node, inst->value); + inst->value_time = now; + } + } + } + } + return cnt; +} + +struct inode_entry *inode_buf_find(const dm_node_t *node, int *retry) +{ + int cnt = 0; + dm_node_id_t nodes_id[MAX_DM_NODE_DEPTH]; + get_all_i_node(node->id, nodes_id, &cnt); + + struct inode_entry *inode = inode_g; + + int index_i = 0; + for (int i = cnt - 1; i >= 0; i--) { + struct inode_entry *entry = find_inode_entry(inode, nodes_id[i]); + if (entry == NULL) { + if (retry) { + *retry = 1; + } + return NULL; + } + + if (entry->key == node->id) { + return entry; + } + + if (index_i >= node->cnt) { + if (retry) { + *retry = 0; + } + dmlog_error("inode_buf_find, invalid node(missing index) %s", dm_node_str(node)); + return NULL; + } + + inst_entry_t *inst = find_inst(entry, node->index[index_i], NULL); + if (inst == NULL) { + dmlog_error("inode_buf_find, instance not found %s", dm_node_str(node)); + if (retry) { + *retry = 0; + } + return NULL; + } + inode = inst->inode; + index_i++; + } + + return NULL; +} + +static void create_inode_inst(struct inode_entry **inode, const dm_node_id_t key, dm_inst_array_t *insts, int dynamic) +{ + struct inode_entry *entry = calloc(1, sizeof(struct inode_entry)); + entry->key = key; + if (dynamic) { + entry->dyn_insts = insts; + entry->dyn_time = get_uptime_msecs(); + } else { + entry->insts = insts; + } + + entry->next = *inode; + *inode = entry; +} + +static void resize_if_needed(dm_inst_array_t *array) { + if (array->size >= array->capacity) { + array->capacity *= GROWTH_FACTOR; + array->data = realloc(array->data, array->capacity * sizeof(inst_entry_t)); + } +} + +void dm_inst_array_append(dm_inst_array_t *array, inst_entry_t *value) { + resize_if_needed(array); + array->data[array->size++] = *value; +} + +int dm_inst_array_remove(dm_inst_array_t *array, unsigned int index) { + if (!array) { + return 0; + } + + for (size_t i = 0; i < array->size; i++) { + if (array->data[i].index == index) { + JS_FreeValue(qjs_ctx(), array->data[i].value); + free_inode(array->data[i].inode); + for (size_t j = i; j < array->size - 1; j++) { + array->data[j] = array->data[j + 1]; + } + array->size--; + return 1; + } + } + + return 0; +} + +int inode_buf_append_db_inst(const dm_node_t *node, inst_entry_t *inst) +{ + dm_inst_array_t * arr = dm_inst_array_create(1); + dm_inst_array_append(arr, inst); + return inode_buf_add_inst(node, arr, 0, 1); +} + +int inode_buf_del_db_inst(const dm_node_t *node) +{ + int retry; + struct inode_entry *entry = inode_buf_find(node, &retry); + if (!entry) { + if (retry == 0) { + return -1; + } + dm_node_t n = *node; + n.cnt = node->cnt - 1; + dmapi_refresh_instance_buf(&n); + entry = inode_buf_find(node, &retry); + } + + if (!entry) { + dmlog_error("inode_buf_del_db_inst, node not found: %s", dm_node_str(node)); + return -1; + } + + if (dm_inst_array_remove(entry->insts, dm_node_last_index(node))) { + // dmlog_debug("removed instance from buffer: %s", dm_node_str(node)); + return 0; + } + + dmlog_error("inode_buf_del_db_inst, instance not found: %s", dm_node_str(node)); + return -1; +} + +static void update_instances(struct inode_entry *inode, dm_inst_array_t *insts, int dynamic) +{ + if (dynamic) { + dm_inst_array_free(inode->dyn_insts); + inode->dyn_insts = insts; + inode->dyn_time = get_uptime_msecs(); + } else { + dm_inst_array_free(inode->insts); + inode->insts = insts; + } +} + +int inode_buf_add_inst(const dm_node_t *node, dm_inst_array_t *insts, int dynamic, int append) +{ + dm_node_id_t nodes_id[MAX_DM_NODE_DEPTH]; + int cnt; + get_all_i_node(node->id, nodes_id, &cnt); + struct inode_entry **inode = &inode_g; + int index_i = 0; + + dm_node_t curr_node; + curr_node.cnt = 0; + // dmlog_debug("inode_buf_add_inst %s, inst_cnt: %d, dynamic: %d", dm_node_str(node), insts->size, dynamic); + for (int i = cnt - 1; i >= 0; i--) { + curr_node.id = nodes_id[i]; + struct inode_entry *entry = find_inode_entry(*inode, nodes_id[i]); + if (entry == NULL && i != 0) { + dmapi_refresh_instance_buf(&curr_node); + entry = find_inode_entry(*inode, nodes_id[i]); + } + + if (entry == NULL) { + if (i == 0) { + // create the instance + if (!append) { + create_inode_inst(inode, nodes_id[i], insts, dynamic); + } else { + // dmlog_debug("first time to add instance, refresh all"); + dm_inst_array_free(insts); + dmapi_refresh_instance_buf(node); + } + return 0; + } else { + dmlog_error("inode buffer not found: %s", dm_node_str(&curr_node)); + return -1; + } + } else { + if (i == 0) { + if (!append) { + update_instances(entry, insts, dynamic); + } else { + // currently only append one instance. + dm_inst_array_append(entry->insts, &insts->data[0]); + if (insts->data) { + free(insts->data); + } + free(insts); + } + return 0; + } + } + inst_entry_t *inst = find_inst(entry, node->index[index_i], NULL); + if (inst == NULL) { + dmlog_error("inode_buf_add_inst inst_entry not found"); + return -1; + } + index_i++; + inode = &inst->inode; + curr_node.index[curr_node.cnt] = inst->index; + curr_node.cnt++; + } + + dmlog_error("failed to add instance buffer for %s", dm_node_str(node)); + return -1; +} + +int inode_buf_update_param_value(const dm_node_t *node, const char *value) +{ + JSValue args[MAX_DM_NODE_DEPTH]; + int ret = inode_buf_get_js_values(node, args); + if (ret != node->cnt) { + dmlog_error("inode_buf_update_param_value failed, node:%s, ret: %d", dm_node_str(node), ret); + return -1; + } + + const struct dm_node_info *info = dm_node_get_info(node->id); + JS_SetPropertyStr(qjs_ctx(), args[ret - 1], info->name, JS_NewString(qjs_ctx(), value)); + return 0; +} + +void inode_buf_free_all() +{ + free_inode(inode_g); + inode_g = NULL; +} + +static void print_inst_array(dm_inst_array_t *arr, int ident) +{ + if (arr == NULL) { + dmlog_info("no instances"); + return; + } + for (int i = 0; i < arr->size; i++) { + const char *str = get_js_value_str(arr->data[i].value); + dmlog_info("idx:%d", arr->data[i].index); + dmlog_info("val: \n%s", str); + free_js_cstr(str); + } +} + +static void print_inode_insts(struct inode_entry *inode, int level) +{ + dmlog_info("db instances:"); + print_inst_array(inode->insts, level); + dmlog_info("dynamic instances (created time:%d)", inode->dyn_time); + print_inst_array(inode->dyn_insts, level); +} + +void inode_buf_dump(const dm_node_t *node) +{ + dmlog_info("node buffer: %s", dm_node_str(node)); + struct inode_entry *entry = inode_buf_find(node, NULL); + if (!entry) { + dmlog_info("node buffer not found: %s", dm_node_str(node)); + return; + } + + if (dm_node_is_index_complete(node)) { + int db; + inst_entry_t *inst = find_inst(entry, node->index[node->cnt-1], &db); + if (!inst) { + dmlog_info("node buffer (inst) not found: %s", dm_node_str(node)); + return; + } else { + dmlog_info("instance buffer (db:%d):", db); + dmlog_info("index: %d", inst->index); + const char *str = get_js_value_str(inst->value); + dmlog_info("value: %s", str); + free_js_cstr(str); + } + } else { + dmlog_info("inode buffer:"); + print_inode_insts(entry, 0); + } +} \ No newline at end of file diff --git a/dm-framework/dm-api/src/core/inode_buf.h b/dm-framework/dm-api/src/core/inode_buf.h new file mode 100644 index 000000000..1d36e8931 --- /dev/null +++ b/dm-framework/dm-api/src/core/inode_buf.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef I_NODE_BUF_H +#define I_NODE_BUF_H + +#include +#include + +#include "dm_node.h" + +/* +Buffer for Multi-object instance. +An example of buffer is as following: + +inode_entry * head -+ + | + v +inode_entry [Device.IP.Interface.{i}.] -> [Device.Ethernet.Link.{i}.] + | | + v v +inst_entry [instance 1] -> [instance 2] ... + | + v +inode_entry [Device.IP.Interface.{i}.IPv4Address.{i}.] -> [Device.IP.Interface.{i}.IPv46ddress.{i}.] + | + v +inst_entry [instance 1] -> [instance 2] +*/ + +struct inode_entry; + +typedef struct { + unsigned int index; + JSValue value; // key value for the instance + struct inode_entry *inode; // head of child inode buf (in recursive) + unsigned int value_time; +} inst_entry_t; + +typedef struct { + inst_entry_t *data; + size_t size; + size_t capacity; +} dm_inst_array_t; + +dm_inst_array_t* dm_inst_array_create(size_t initial_capacity); +void dm_inst_array_append(dm_inst_array_t *array, inst_entry_t *value); + + +struct inode_entry { + dm_node_id_t key; + dm_inst_array_t *insts; + dm_inst_array_t *dyn_insts; + struct inode_entry *next; + unsigned int dyn_time; +}; + +/** Buffer new db instances for the multi-instance object. + * @param[in] node node of instance + * @param[in] insts instance array + * @param[in] cnt length of instance array + * @param[in] dynamic if the instances are dynamic or in db + * @return 0 in case of success, or a nonzero value in case of error + */ +int inode_buf_add_inst(const dm_node_t *node, dm_inst_array_t *insts, int dynamic, int append); + +/** Get instance values for the node + * @param[in] node node of instance + * @param[in] values values to store the result + * @return number of values found in case of success, or -1 in case of error + */ +int inode_buf_get_js_values(const dm_node_t *node, JSValue values[]); + +/** find inode buffer according to node + * @param[in] node node of instance + * @param[in] retry can be retried or not + * @return pointer of inode_entry if found, otherwise NULL + */ +struct inode_entry *inode_buf_find(const dm_node_t *node, int *retry); + +/** Free all the buffer + * @return none + */ +void inode_buf_free_all(void); + +int inode_buf_append_db_inst(const dm_node_t *node, inst_entry_t *inst); +int inode_buf_del_db_inst(const dm_node_t *node); +int inode_buf_update_param_value(const dm_node_t *node, const char *value); + +void inode_buf_dump(const dm_node_t *node); +#endif diff --git a/dm-framework/dm-api/src/include/dm_log.h b/dm-framework/dm-api/src/include/dm_log.h new file mode 100644 index 000000000..6b7658626 --- /dev/null +++ b/dm-framework/dm-api/src/include/dm_log.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DMLOG_H +#define DMLOG_H +void dmlog_init(const char *name, int log_level); +void dmlog_info(const char *fmt, ...); +void dmlog_warn(const char *fmt, ...); +void dmlog_error(const char *fmt, ...); +void dmlog_debug(const char *fmt, ...); +void dmlog_close(); +#endif /* DMLOG_H */ diff --git a/dm-framework/dm-api/src/quickjs/._qjs.c b/dm-framework/dm-api/src/quickjs/._qjs.c new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/quickjs/._qjs_dm_api.c b/dm-framework/dm-api/src/quickjs/._qjs_dm_api.c new file mode 100755 index 0000000000000000000000000000000000000000..afceea39b197e7a26212cfef0bd1636e2f84b9a9 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vvIyKK;b>5zirgfA%8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O*h2u+*#u!QkPFGkELJE=EzU13N={Ws y%P-1S$jmEA%`3^w&r8h7sZ_{GO)F7I%1O-22KI%ax`s4`>VLRbWEkZB{|5l(D=7^C literal 0 HcmV?d00001 diff --git a/dm-framework/dm-api/src/quickjs/qjs.c b/dm-framework/dm-api/src/quickjs/qjs.c new file mode 100644 index 000000000..255fd4db1 --- /dev/null +++ b/dm-framework/dm-api/src/quickjs/qjs.c @@ -0,0 +1,1127 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "qjs.h" + +#include +#include +#include +#include +#include +#include + +#include "dbmgr.h" +#include "dm.h" +#include "dm_log.h" +#include "dm_node.h" +#include "dm_uci.h" +#include "inode_buf.h" +#include "qjs_api.h" + +static JSRuntime *runtime_g; +static JSContext *ctx_g; +static JSValue global_obj; +static JSValue dm_handlers_obj; + +struct node_handlers { + JSValue import; + JSValue apply; + JSValue get; + JSValue set; + JSValue info; + JSValue changed; +}; + +struct cmd_handler_info { + JSValue cmd; +}; + +void **js_handlers_g = NULL; + +extern JSValue get_object(const dm_node_t *node, int only_db); + +const char *get_js_value_str(JSValue value) +{ + JSValue val = JS_JSONStringify(ctx_g, value, JS_UNDEFINED, JS_UNDEFINED); + const char *str = JS_ToCString(ctx_g, val); + JS_FreeValue(ctx_g, val); + return str; +} + +const char *qjs_get_property_str(JSValue obj, const char *prop_name) +{ + if (JS_IsUndefined(obj)) { + dmlog_error("qjs_get_property_str, undefined obj"); + return NULL; + } + JSValue value = JS_GetPropertyStr(ctx_g, obj, prop_name); + if (JS_IsUndefined(value)) { + dmlog_error("qjs_get_property_str, undefined property: %s", prop_name); + return NULL; + } + + const char *res = JS_ToCString(ctx_g, value); + JS_FreeValue(ctx_g, value); + return res; +} + +void qjs_log_exception() +{ + JSValue exception_val = JS_GetException(ctx_g); + const char *error_msg = JS_ToCString(ctx_g, exception_val); + dmlog_error("JS exception: %s", error_msg); + JS_FreeCString(ctx_g, error_msg); + if (JS_IsError(ctx_g, exception_val)) { + JSValue val = JS_GetPropertyStr(ctx_g, exception_val, "stack"); + if (!JS_IsUndefined(val)) { + const char *stack = JS_ToCString(ctx_g, val); + dmlog_error("stack: %s", stack); + JS_FreeCString(ctx_g, stack); + } + JS_FreeValue(ctx_g, val); + } + JS_FreeValue(ctx_g, exception_val); + +} + +JSValue qjs_eval_buf(const void *buf, int buf_len, const char *filename, int eval_flags) +{ + JSValue val; + + if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) { + /* for the modules, we compile then run to be able to set + import.meta */ + val = JS_Eval(ctx_g, buf, buf_len, filename, + eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(val)) { + js_module_set_import_meta(ctx_g, val, 1, 1); + val = JS_EvalFunction(ctx_g, val); + } + } else { + val = JS_Eval(ctx_g, buf, buf_len, filename, eval_flags); + } + + if (JS_IsException(val)) { + qjs_log_exception(); + dmlog_error("js: %s", buf); + } + + return val; +} + +static JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContext(rt); + if (!ctx) + return NULL; + /* system modules */ + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + return ctx; +} + +void qjs_register_c_api(const char *name, JSCFunction func, int param_len) +{ + int ret = JS_SetPropertyStr(ctx_g, global_obj, name, + JS_NewCFunction(ctx_g, func, name, param_len)); + if (ret == -1) { + dmlog_error("qjs_register_c_api failed %d %s", ret, name); + } +} + +static JSValue get_js_handler(const struct dm_node_info *node, const char *name) +{ + dm_path_t handler_name; + snprintf(handler_name, sizeof(handler_name), "%s%s", name, node->pathname); + JSValue func = JS_GetPropertyStr(ctx_g, dm_handlers_obj, handler_name); + if (JS_IsException(func) || JS_IsUndefined(func)) { + + return JS_UNDEFINED; + } + + return func; +} + +static int init_obj_js_handlers() +{ + js_handlers_g = (void **)calloc(NODE_ID_MAX, sizeof(void*)); + for (unsigned int i = 0; i < NODE_ID_MAX; i++) { + if (dm_node_is_object(i) || dm_node_is_objectlist(i) || dm_node_is_parameter(i)) { + struct node_handlers *handler = (struct node_handlers *)calloc(1, sizeof(struct node_handlers)); + js_handlers_g[i] = (void*)handler; + handler->get = get_js_handler(dm_node_get_info(i), "get"); + handler->set = get_js_handler(dm_node_get_info(i), "set"); + handler->changed = get_js_handler(dm_node_get_info(i), "changed"); + handler->info = get_js_handler(dm_node_get_info(i), "info"); + dm_node_id_t depends = dm_node_get_apply_depends(i); + if (depends != INVALID_DM_NODE_ID) { + handler->apply = get_js_handler(dm_node_get_info(depends), "apply"); + } else { + handler->apply = get_js_handler(dm_node_get_info(i), "apply"); + } + handler->import = get_js_handler(dm_node_get_info(i), "import"); + + } else if (dm_node_is_command(i)) { + struct cmd_handler_info *cmd_handler = (struct cmd_handler_info *)calloc(1, sizeof(struct cmd_handler_info)); + js_handlers_g[i] = (void*)cmd_handler; + cmd_handler->cmd = get_js_handler(dm_node_get_info(i), "operate"); + } + } + return 0; +} + +static void init_qjs_c_api() +{ + qjs_log_api_init(); + qjs_dm_api_init(); + qjs_ubus_api_init(); +} + +static int import_js_handlers() +{ + const char *str = + "import * as dm_handlers from '/usr/lib/quickjs/dm_handlers/exports.js';\n" + "globalThis.dm_handlers = dm_handlers;\n"; + JSValue val = qjs_eval_buf(str, strlen(str), "", JS_EVAL_TYPE_MODULE); + if (JS_IsException(val)) { + dmlog_error("failed to load get handlers"); + return -1; + } + + JS_FreeValue(ctx_g, val); + dm_handlers_obj = JS_GetPropertyStr(ctx_g, global_obj, "dm_handlers"); + if (JS_IsException(dm_handlers_obj) || JS_IsUndefined(dm_handlers_obj)) { + dmlog_error("failed to find handlers"); + return -1; + } + + init_obj_js_handlers(); + return 0; +} + +int qjs_init() +{ + runtime_g = JS_NewRuntime(); + if (runtime_g == NULL) { + dmlog_error("JS_NewRuntime failure"); + return -1; + } + + js_std_set_worker_new_context_func(JS_NewCustomContext); + js_std_init_handlers(runtime_g); + + ctx_g = JS_NewCustomContext(runtime_g); + if (ctx_g == NULL) { + JS_FreeRuntime(runtime_g); + dmlog_error("JS_NewContext failure"); + return -1; + } + JS_SetModuleLoaderFunc(runtime_g, NULL, js_module_loader, NULL); + + js_init_module_std(ctx_g, "std"); + js_init_module_os(ctx_g, "os"); + + js_std_add_helpers(ctx_g, 0, NULL); + + global_obj = JS_GetGlobalObject(ctx_g); + init_qjs_c_api(); + + return import_js_handlers(); +} + +int qjs_has_get_handler(dm_node_id_t id) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[id]; + if (info && !JS_IsUndefined(info->get)) { + return 1; + } + + if (dm_node_is_objectlist(id)) { + const struct dm_object *obj = dm_node_get_object(id); + if (obj->js_val) { + return 1; + } + } + + if (!dm_node_is_parameter(id)) { + return 0; + } + + // special handling for _key parameter + if (dm_node_is_internal(id)) { + return 1; + } + + const struct dm_parameter *param = dm_node_get_parameter(id); + if (param->js_val) { + return 1; + } + + if (!dm_node_has_db(id) && param->map.map) { + return 1; + } + + return 0; +} + +int qjs_has_set_handler(dm_node_id_t id) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[id]; + if (info && !JS_IsUndefined(info->set)) { + return 1; + } + + return 0; +} + +int qjs_has_import_handler(dm_node_id_t id) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[id]; + if (info && !JS_IsUndefined(info->import)) { + return 1; + } + + return 0; +} + +int qjs_has_apply_handler(dm_node_id_t id) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[id]; + if (info && !JS_IsUndefined(info->apply)) { + return 1; + } + + return 0; +} + +int qjs_has_changed_handler(dm_node_id_t id) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[id]; + if (info && !JS_IsUndefined(info->changed)) { + return 1; + } + + return 0; +} + +int qjs_has_info_handler(dm_node_id_t id) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[id]; + if (info && !JS_IsUndefined(info->info)) { + return 1; + } + + return 0; +} + +static JSValue get_nested_property(JSValueConst obj, const char *prop) { + JSValue val, nested_val; + char *token, *str, *prop_copy; + + prop_copy = strdup(prop); + str = prop_copy; + + /* Get first level property "a" from "a.b" */ + token = strtok(str, "."); + val = JS_GetPropertyStr(ctx_g, obj, token); + + if (JS_IsException(val)) { + free(prop_copy); + return JS_UNDEFINED; + } + + /* Get nested properties for "b" from "a.b" */ + while ((token = strtok(NULL, ".")) != NULL) { + nested_val = JS_GetPropertyStr(ctx_g, val, token); + JS_FreeValue(ctx_g, val); + + if (JS_IsException(nested_val)) { + free(prop_copy); + return JS_UNDEFINED; + } + + val = nested_val; + } + + free(prop_copy); + return val; +} + +// return 0 on success, -1 otherwise +int qjs_call_get_handler(const dm_node_t *node, char **res) +{ + JSValue val; + int free_val = 1; + const struct dm_parameter *param = dm_node_get_parameter(node->id); + if (param->js_val == NULL && !qjs_has_get_handler(node->id)) { + dmlog_error("missing get handler for %s", dm_node_str(node)); + return -1; + } + + JSValue values[MAX_DM_NODE_DEPTH]; + int cnt = inode_buf_get_js_values(node, values); + if (cnt != node->cnt) { + dmlog_error("inode_buf_get_js_values failed %s, %d", dm_node_str(node), cnt); + return -1; + } + + if (param->js_val) { + if (param->js_val[0] == '\0') { + val = values[cnt - 1]; + free_val = 0; // no free + } else { + val = get_nested_property(values[cnt - 1], param->js_val); + if (JS_IsException(val)) { + dmlog_error("JS exception in get_nested_property %s", dm_node_str(node)); + qjs_log_exception(); + } + } + } else { + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (info && !JS_IsUndefined(info->get)) { + val = JS_Call(ctx_g, info->get, global_obj, cnt, values); + } else if (param->map.map) { + return dm_uci_get(param->map.map, res); + } else { + dmlog_error("missing get handler for %s", dm_node_str(node)); + return -1; + } + } + + if (JS_IsException(val)) { + dmlog_error("JS exception happened in get handler %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + // use default value + if (param->default_val) { + *res = strdup(param->default_val); + } else { + if (dm_node_is_text_type(node->id)) { + *res = strdup(""); + } else { + if (dm_node_is_unsigned_type(node->id)) { + *res = strdup("0"); + } else { + *res = strdup("-1"); + } + } + } + return 0; + } + + const char *str = JS_ToCString(ctx_g, val); + *res = strdup(str); + JS_FreeCString(ctx_g, str); + + if (free_val) { + JS_FreeValue(ctx_g, val); + } + return 0; +} + +// return 1 if called, otherwise return 0; +int qjs_call_set_handler(const dm_node_t *node, const char *value) +{ + int arg_cnt = 1; + JSValue args[MAX_DM_NODE_DEPTH + 1]; + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (!info || JS_IsUndefined(info->set)) { + dmlog_error("qjs_call_set_handler, handler not found: %s", dm_node_str(node)); + return -1; + } + JSValue js_handler = info->set; + + if (node->cnt > 0) { + int cnt = inode_buf_get_js_values(node, &args[1]); + if (cnt < 0) { + dmlog_error("qjs_call_set_handler, inode_buf_get_js_values failed %s", dm_node_str(node)); + return -1; + } else { + arg_cnt += cnt; + } + } + + args[0] = JS_NewString(ctx_g, value); + + JSValue val = JS_Call(ctx_g, js_handler, global_obj, arg_cnt, args); + JS_FreeValue(ctx_g, args[0]); + + if (JS_IsException(val)) { + dmlog_error("JS exception happened in set handler %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + // to continue set db + return 1; + } + + if (!JS_IsNumber(val)) { + dmlog_error("set handler, integer value is expected, %s", dm_node_str(node)); + return -1; + } + int ret; + JS_ToInt32(ctx_g, &ret, val); + JS_FreeValue(ctx_g, val); + return ret; +} + +JSValue get_js_dt(enum DM_DATA_TYPE dt, const char *value) +{ + switch (dt) + { + case DM_DATA_BOOLEAN: + { + int bol_val; + if (strcmp(value, "1") == 0 || strcmp(value, "true") == 0) { + bol_val = 1; + } else { + bol_val = 0; + } + return JS_NewBool(ctx_g, bol_val); + } + case DM_DATA_INT: + return JS_NewInt32(ctx_g, atoi(value)); + case DM_DATA_LONG: + return JS_NewInt32(ctx_g, atol(value)); + case DM_DATA_UINT: + return JS_NewUint32(ctx_g, atoi(value)); + case DM_DATA_ULONG: + return JS_NewUint32(ctx_g, atol(value)); + default: + break; + } + + return JS_NewString(ctx_g, value); +} + +// return 1 if called, otherwise return 0; +JSValue qjs_call_import_handler(const dm_node_t *node, JSValue values) +{ + int arg_cnt = 0; + JSValue args[1]; + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (!info || JS_IsUndefined(info->import)) { + dmlog_error("qjs_call_import_handler, handler not found: %s", dm_node_str(node)); + return JS_UNDEFINED; + } + + JSValue js_handler = info->import; + + if (!JS_IsUndefined(values)) { + args[0] = values; + arg_cnt = 1; + } + + JSValue val = JS_Call(ctx_g, js_handler, global_obj, arg_cnt, args); + if (JS_IsException(val)) { + dmlog_error("JS exception happened in import handler %s", dm_node_str(node)); + qjs_log_exception(); + } + + return val; +} + +// return 1 if called, otherwise return 0; +int qjs_call_apply_param_handler(const dm_node_t *node, const char *value) +{ + int arg_cnt = 1; + JSValue args[MAX_DM_NODE_DEPTH + 1]; + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (!info || JS_IsUndefined(info->apply)) { + // dmlog_error("qjs_call_apply_param_handler, handler not found: %s", dm_node_str(node)); + return 0; + } + JSValue js_handler = info->apply; + + if (node->cnt > 0) { + int cnt = inode_buf_get_js_values(node, &args[1]); + if (cnt < 0) { + dmlog_error("qjs_call_apply_param_handler, inode_buf_get_js_values failed %s", dm_node_str(node)); + return -1; + } else { + arg_cnt += cnt; + } + } + + args[0] = get_js_dt(dm_node_data_type(node->id), value); + + JSValue val = JS_Call(ctx_g, js_handler, global_obj, arg_cnt, args); + JS_FreeValue(ctx_g, args[0]); + + if (JS_IsException(val)) { + dmlog_error("JS exception happened in apply handler %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + JS_FreeValue(ctx_g, val); + return 0; +} + +// return 1 if called, otherwise return 0; +int qjs_call_apply_obj_handler(const dm_node_t *node, const dm_node_t *ext_node) +{ + int arg_cnt = 1; + JSValue args[MAX_DM_NODE_DEPTH + 1]; + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (!info || JS_IsUndefined(info->apply)) { + dmlog_error("qjs_call_apply_param_handler, handler not found: %s", dm_node_str(node)); + return -1; + } + JSValue js_handler = info->apply; + args[0] = get_object(node, 1); + + if (ext_node->cnt > 0) { + int cnt = inode_buf_get_js_values(ext_node, &args[1]); + if (cnt < 0) { + dmlog_error("qjs_call_apply_param_handler, inode_buf_get_js_values failed %s", dm_node_str(ext_node)); + return -1; + } else { + arg_cnt = cnt + 1; + } + } + + JSValue val = JS_Call(ctx_g, js_handler, global_obj, arg_cnt, args); + JS_FreeValue(ctx_g, args[0]); + if (JS_IsException(val)) { + dmlog_error("JS exception happened in apply handler %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + JS_FreeValue(ctx_g, val); + return 0; +} + +// create the json_object from the element of js array. +static struct json_object *create_json_output(const dm_node_t *node, JSValue value) +{ + uint32_t len, i; + JSPropertyEnum *tab; + if (JS_GetOwnPropertyNames(ctx_g, &tab, &len, value, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { + dmlog_error("_dm_update, JS_GetOwnPropertyNames failed"); + return NULL; + } + + struct json_object *json_arr = json_object_new_array(); + + for (i = 0; i < len; i++) { + const char *key_name = JS_AtomToCString(ctx_g, tab[i].atom); + const struct command_arg *arg = dm_node_get_command_output_arg(node->id, key_name); + if (arg == NULL) { + dmlog_error("failed to find output name %s", key_name); + json_object_put(json_arr); + json_arr = NULL; + break; + } + + struct json_object *obj = json_object_new_object(); + JSValue output_value = JS_GetProperty(ctx_g, value, tab[i].atom); + const char *value_str = JS_ToCString(ctx_g, output_value); + json_object_object_add(obj, "path", json_object_new_string(key_name)); + json_object_object_add(obj, "data", json_object_new_string(value_str)); + if (arg->type == DM_DATA_INT || arg->type == DM_DATA_UINT || + arg->type == DM_DATA_LONG || arg->type == DM_DATA_ULONG) { + json_object_object_add(obj, "type", json_object_new_string(get_param_xsd_type(arg->type))); + } else if (arg->type == DM_DATA_BOOLEAN) { + json_object_object_add(obj, "type", json_object_new_string("xsd:boolean")); + } else { + // treat all other types as string + json_object_object_add(obj, "type", json_object_new_string("xsd:string")); + } + + JS_FreeCString(ctx_g, value_str); + JS_FreeValue(ctx_g, output_value); + json_object_array_add(json_arr, obj); + } + js_free(ctx_g, tab); + + return json_arr; +} + +int qjs_call_operate_handler(const dm_node_t *node, const char *json_input, struct json_object **json_output) +{ + struct cmd_handler_info * info = (struct cmd_handler_info*)js_handlers_g[node->id]; + + if (!info || JS_IsUndefined(info->cmd)) { + dmlog_error("missing operate handler for %s", dm_node_str(node)); + return -1; + } + + JSValue val = JS_EXCEPTION; + int arg_cnt = 1; + JSValue args[MAX_DM_NODE_DEPTH + 1]; + // first arg for the JSON input + if (json_input) { + args[0] = JS_ParseJSON(ctx_g, json_input, strlen(json_input), "json"); + if (JS_IsException(args[0])) { + dmlog_error("JS_ParseJSON failed %s", json_input); + qjs_log_exception(); + return -1; + } + } else { + args[0] = JS_UNDEFINED; + } + + if (node->cnt > 0) { + int ins_cnt = inode_buf_get_js_values(node, &args[1]); + if (ins_cnt < 0) { + dmlog_error("inode_buf_get_js_values failed %s", dm_node_str(node)); + if (json_input) { + JS_FreeValue(ctx_g, args[0]); + } + return -1; + } else { + arg_cnt += ins_cnt; + } + } + + val = JS_Call(ctx_g, info->cmd, global_obj, arg_cnt, args); + if (json_input) { + JS_FreeValue(ctx_g, args[0]); + } + + if (JS_IsException(val)) { + dmlog_error("exception happened for operate handler %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + // no news is good news + return 0; + } + + if (json_output && JS_IsObject(val)) { + *json_output = create_json_output(node, val); + } + + JS_FreeValue(ctx_g, val); + return 0; +} + +JSValue qjs_call_get_instance_handler(const dm_node_t *node, int start_index) +{ + const struct dm_object *obj = dm_node_get_object(node->id); + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if ((!info || JS_IsUndefined(info->get)) && !obj->js_val) { + dmlog_error("missing index handler for %s", dm_node_str(node)); + return JS_UNDEFINED; + } + + int argc = 0; + JSValue argv[MAX_DM_NODE_DEPTH]; + JSValue val; + if (dm_node_index_cnt(node->id) > 1) { + argc = inode_buf_get_js_values(node, &argv[0]); + if (argc < 0) { + dmlog_error("qjs_call_get_instance_handler, inode_buf_get_js_values failed %s", dm_node_str(node)); + return JS_UNDEFINED; + } + } + if (obj->js_val) { + if (argc < 1) { + dmlog_error("qjs_call_get_instance_handler, unexpected js buf size: %d", argc); + return JS_UNDEFINED; + } + if (obj->js_val[0] == '\0') { + val = argv[argc - 1]; + } else { + val = get_nested_property(argv[argc - 1], obj->js_val); + } + } else { + val = JS_Call(ctx_g, info->get, global_obj, argc, argv); + } + + if (JS_IsException(val)) { + dmlog_error("exception happened get instance handler %s", dm_node_str(node)); + qjs_log_exception(); + return JS_UNDEFINED; + } + + if (JS_IsUndefined(val)) { + return JS_UNDEFINED; + } + + if (!JS_IsArray(ctx_g, val)) { + dmlog_error("qjs_call_get_instance_handler, unexpected non-array returned %s", dm_node_str(node)); + JS_FreeValue(ctx_g, val); + return JS_UNDEFINED; + } + + // set the 'index' property for each element to be use by get handler + uint32_t len; + JSValue length = JS_GetPropertyStr(ctx_g, val, "length"); + JS_ToUint32(ctx_g, &len, length); + JS_FreeValue(ctx_g, length); + + for (unsigned int i = 0; i < len; i++) { + JSValue arr_val = JS_GetPropertyUint32(ctx_g, val, i); + if (JS_IsObject(arr_val)) { + JS_SetPropertyStr(ctx_g, arr_val, ".index", JS_NewUint32(ctx_g, start_index + i)); + } + + JS_FreeValue(ctx_g, arr_val); + } + + return val; +} + +int qjs_call_changed_handler(const dm_node_t *node) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (!info || JS_IsUndefined(info->changed)) { + return 0; + } + JSValue js_handler = info->changed; + + JSValue argv[MAX_DM_NODE_DEPTH]; + JSValue val; + if (dm_node_index_cnt(node->id) > 0) { + int argc = inode_buf_get_js_values(node, &argv[0]); + if (argc < 0) { + dmlog_error("qjs_call_changed_handler, inode_buf_get_js_values failed %s", dm_node_str(node)); + return -1; + } + val = JS_Call(ctx_g, js_handler, global_obj, argc, argv); + } else { + val = JS_Call(ctx_g, js_handler, global_obj, 0, NULL); + } + + if (JS_IsException(val)) { + dmlog_error("exception happened for changed_handler: %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + JS_FreeValue(ctx_g, val); + return 0; + } + + if (!JS_IsNumber(val)) { + dmlog_error("changed handler, integer value is expected, %s", dm_node_str(node)); + JS_FreeValue(ctx_g, val); + return -1; + } + + int ret; + JS_ToInt32(ctx_g, &ret, val); + JS_FreeValue(ctx_g, val); + + return ret; +} + +int qjs_call_info_handler(const dm_node_t *node, JSValue obj) +{ + struct node_handlers * info = (struct node_handlers*)js_handlers_g[node->id]; + if (!info || JS_IsUndefined(info->info)) { + return 0; + } + JSValue js_handler = info->info; + + JSValue argv[2]; + const char *path = dm_node_str(node); + argv[0] = JS_NewString(ctx_g, path); + argv[1] = obj; + JSValue val = JS_Call(ctx_g, js_handler, global_obj, 2, argv); + JS_FreeValue(ctx_g, argv[0]); + + if (JS_IsException(val)) { + dmlog_error("exception happened for info handler: %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + JS_FreeValue(ctx_g, val); + return 0; + } + + if (!JS_IsNumber(val)) { + dmlog_error("info handler, integer value is expected, %s", dm_node_str(node)); + JS_FreeValue(ctx_g, val); + return -1; + } + + int ret; + JS_ToInt32(ctx_g, &ret, val); + JS_FreeValue(ctx_g, val); + return ret; +} + +int qjs_has_key_handler(dm_node_id_t id) +{ + JSValue js_handler = get_js_handler(dm_node_get_info(id), "key"); + if (JS_IsUndefined(js_handler)) { + return 0; + } + return 1; +} + +int qjs_call_key_handler(const dm_node_t *node, char **res) +{ + JSValue js_handler = get_js_handler(dm_node_get_info(node->id), "key"); + if (JS_IsUndefined(js_handler)) { + return 0; + } + + JSValue argv[MAX_DM_NODE_DEPTH]; + JSValue val; + int argc = inode_buf_get_js_values(node, &argv[0]); + if (argc < 0) { + dmlog_error("qjs_call_key_handler, inode_buf_get_js_values failed %s", dm_node_str(node)); + return -1; + } + val = JS_Call(ctx_g, js_handler, global_obj, argc, argv); + if (JS_IsException(val)) { + dmlog_error("exception happened for qjs_call_key_handler: %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + dmlog_error("qjs_call_key_handler returned undefined"); + JS_FreeValue(ctx_g, val); + return -1; + } + + const char *str = JS_ToCString(ctx_g, val); + *res = strdup(str); + JS_FreeCString(ctx_g, str); + JS_FreeValue(ctx_g, val); + + return 0; +} + +int qjs_call_uci_init_handler(const dm_node_t *node) +{ + JSValue js_handler = get_js_handler(dm_node_get_info(node->id), "init"); + if (JS_IsUndefined(js_handler)) { + return 0; + } + + JSValue argv[MAX_DM_NODE_DEPTH]; + JSValue val; + int argc = inode_buf_get_js_values(node, &argv[0]); + if (argc < 0) { + dmlog_error("qjs_call_uci_init_handler, inode_buf_get_js_values failed %s", dm_node_str(node)); + return -1; + } + + val = JS_Call(ctx_g, js_handler, global_obj, argc, argv); + if (JS_IsException(val)) { + dmlog_error("exception happened for qjs_call_uci_init_handler: %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + return 0; + } + + JS_FreeValue(ctx_g, val); + return 0; +} + +int qjs_call_uci_deinit_handler(const dm_node_t *node, const char *uci_section) +{ + JSValue js_handler = get_js_handler(dm_node_get_info(node->id), "deinit"); + if (JS_IsUndefined(js_handler)) { + return 0; + } + + JSValue argv[1]; + JSValue val; + + argv[0] = JS_NewString(ctx_g, uci_section); + val = JS_Call(ctx_g, js_handler, global_obj, 1, argv); + if (JS_IsException(val)) { + dmlog_error("exception happened for qjs_call_uci_deinit_handler: %s", dm_node_str(node)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + return 0; + } + + int ret; + JS_ToInt32(ctx_g, &ret, val); + + JS_FreeValue(ctx_g, val); + return ret; +} + +JSContext *qjs_ctx() +{ + return ctx_g; +} + +JSValue qjs_global() +{ + return global_obj; +} + +int qjs_array_len(JSValue arr) +{ + int len; + JSValue length = JS_GetPropertyStr(ctx_g, arr, "length"); + JS_ToInt32(ctx_g, &len, length); + JS_FreeValue(ctx_g, length); + return len; +} + +void free_js_cstr(const char *str) +{ + JS_FreeCString(ctx_g, str); +} + +JSValue json_object_to_jsvalue(json_object *json_obj) +{ + if (json_obj == NULL) { + return JS_UNDEFINED; + } + + json_type type = json_object_get_type(json_obj); + + switch (type) { + case json_type_boolean: + return JS_NewBool(ctx_g, json_object_get_boolean(json_obj)); + + case json_type_double: + return JS_NewFloat64(ctx_g, json_object_get_double(json_obj)); + + case json_type_int: + return JS_NewInt64(ctx_g, json_object_get_int64(json_obj)); + + case json_type_string: + return JS_NewString(ctx_g, json_object_get_string(json_obj)); + + case json_type_object: { + JSValue js_obj = JS_NewObject(ctx_g); + json_object_object_foreach(json_obj, key, val) { + JS_SetPropertyStr(ctx_g, js_obj, key, json_object_to_jsvalue(val)); + } + return js_obj; + } + + case json_type_array: { + int array_len = json_object_array_length(json_obj); + JSValue js_array = JS_NewArray(ctx_g); + + for (int i = 0; i < array_len; i++) { + json_object *element = json_object_array_get_idx(json_obj, i); + JS_SetPropertyUint32(ctx_g, js_array, i, json_object_to_jsvalue(element)); + } + + return js_array; + } + + case json_type_null: + default: + return JS_NULL; + } +} + +int qjs_uci_filter(const dm_node_t *node, json_object *obj) +{ + JSValue js_handler = get_js_handler(dm_node_get_info(node->id), "filter"); + if (JS_IsUndefined(js_handler)) { + return 1; + } + + JSValue argv[MAX_DM_NODE_DEPTH + 1]; + JSValue val; + + int argc = inode_buf_get_js_values(node, &argv[1]); + if (argc < 0) { + dmlog_error("qjs_uci_filter, inode_buf_get_js_values failed %s", dm_node_str(node)); + return 0; + } + + argv[0] = json_object_to_jsvalue(obj); + val = JS_Call(ctx_g, js_handler, global_obj, argc + 1, argv); + if (JS_IsException(val)) { + dmlog_error("exception happened for qjs_uci_filter: %s", dm_node_str(node)); + qjs_log_exception(); + JS_FreeValue(ctx_g, argv[0]); + return 0; + } + + JS_FreeValue(ctx_g, argv[0]); + + if (JS_IsUndefined(val)) { + dmlog_error("qjs_uci_filter returned undefined"); + JS_FreeValue(ctx_g, val); + return 0; + } + + int res = JS_ToBool(ctx_g, val); + JS_FreeValue(ctx_g, val); + return res; +} + +int qjs_call_event_handler(dm_node_id_t node_id, const char *ubus_json_msg, struct json_object **res) +{ + JSValue js_handler = get_js_handler(dm_node_get_info(node_id), "event"); + if (JS_IsUndefined(js_handler)) { + dmlog_error("qjs_call_event_handler, handler not defined %s", dm_node_id_str(node_id)); + return 0; + } + + JSValue event_msg; + // first arg for the JSON input + if (ubus_json_msg) { + event_msg = JS_ParseJSON(ctx_g, ubus_json_msg, strlen(ubus_json_msg), "json"); + if (JS_IsException(event_msg)) { + dmlog_error("JS_ParseJSON failed %s", ubus_json_msg); + qjs_log_exception(); + return -1; + } + } else { + event_msg = JS_UNDEFINED; + } + + JSValue val = JS_Call(ctx_g, js_handler, global_obj, 1, &event_msg); + if (!JS_IsUndefined(event_msg)) { + JS_FreeValue(ctx_g, event_msg); + } + + if (JS_IsException(val)) { + dmlog_error("exception happened for event handler %s", dm_node_id_str(node_id)); + qjs_log_exception(); + return -1; + } + + if (JS_IsUndefined(val)) { + dmlog_error("undefined is returned from event handler %s", dm_node_id_str(node_id)); + return -1; + } + + if (res && JS_IsObject(val)) { + const char *js_str = get_js_value_str(val); + if (js_str) { + *res = json_tokener_parse(js_str); + JS_FreeCString(ctx_g, js_str); + } else { + dmlog_error("failed to get json str %s", dm_node_id_str(node_id)); + } + } + + JS_FreeValue(ctx_g, val); + return 0; +} diff --git a/dm-framework/dm-api/src/quickjs/qjs.h b/dm-framework/dm-api/src/quickjs/qjs.h new file mode 100644 index 000000000..7e245b4c4 --- /dev/null +++ b/dm-framework/dm-api/src/quickjs/qjs.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef QJS_H +#define QJS_H + +#include +#include +#include + +#include "dm_node.h" + +int qjs_init(void); + +void qjs_register_c_api(const char *name, JSCFunction func, int param_len); +int qjs_has_get_handler(dm_node_id_t id); +int qjs_has_set_handler(dm_node_id_t id); +int qjs_call_set_handler(const dm_node_t *node, const char *value); +int qjs_has_import_handler(dm_node_id_t id); +int qjs_has_apply_handler(dm_node_id_t id); +int qjs_has_changed_handler(dm_node_id_t id); +int qjs_has_info_handler(dm_node_id_t id); +int qjs_has_key_handler(dm_node_id_t id); + +int qjs_call_get_handler(const dm_node_t *node, char **res); +int qjs_call_apply_param_handler(const dm_node_t *node, const char *value); +int qjs_call_apply_obj_handler(const dm_node_t *node, const dm_node_t *ext_node); +JSValue qjs_call_import_handler(const dm_node_t *node, JSValue values); +int qjs_call_operate_handler(const dm_node_t *node, const char *json_input, json_object **json_output); +JSValue qjs_call_get_instance_handler(const dm_node_t *node, int start_index); +int qjs_call_changed_handler(const dm_node_t *node); +int qjs_call_info_handler(const dm_node_t *node, JSValue obj); +int qjs_call_key_handler(const dm_node_t *node, char **res); +JSContext *qjs_ctx(); +JSValue qjs_global(); +int qjs_array_len(JSValue arr); +const char *get_js_value_str(JSValue value); +const char *qjs_get_property_str(JSValue obj, const char *prop_name); +void free_js_cstr(const char *str); +void qjs_log_exception(); +JSValue qjs_eval_buf(const void *buf, int buf_len, const char *filename, int eval_flags); +JSValue json_object_to_jsvalue(json_object *json_obj); +int qjs_uci_filter(const dm_node_t *node, json_object *obj); +int qjs_call_event_handler(dm_node_id_t node_id, const char *ubus_json_msg, struct json_object **res); +int qjs_call_uci_init_handler(const dm_node_t *node); +int qjs_call_uci_deinit_handler(const dm_node_t *node, const char *uci_section); +#endif \ No newline at end of file diff --git a/dm-framework/dm-api/src/quickjs/qjs_api.h b/dm-framework/dm-api/src/quickjs/qjs_api.h new file mode 100644 index 000000000..79eee5e50 --- /dev/null +++ b/dm-framework/dm-api/src/quickjs/qjs_api.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef QJS_API_H +#define QJS_API_H + +int qjs_dm_api_init(void); +int qjs_ubus_api_init(void); +int qjs_log_api_init(void); +#endif \ No newline at end of file diff --git a/dm-framework/dm-api/src/quickjs/qjs_dm_api.c b/dm-framework/dm-api/src/quickjs/qjs_dm_api.c new file mode 100644 index 000000000..a5ead29db --- /dev/null +++ b/dm-framework/dm-api/src/quickjs/qjs_dm_api.c @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include + +#include "dbmgr.h" +#include "dm_api.h" +#include "dm_apply.h" +#include "dm_log.h" +#include "dm_node.h" +#include "dm_types.h" +#include "qjs.h" +#include "qjs_api.h" +#include "inode_buf.h" + +JSValue get_object(const dm_node_t *node, int only_db) +{ + int i; + if (dm_node_is_parameter(node->id)) { + char *value = NULL; + if (only_db) { + if (dbmgr_get(node, &value) < 0 || value == NULL) { + dmlog_error("dbmgr_get failed to get param: %s", dm_node_str(node)); + return JS_UNDEFINED; + } + } else { + if (dmapi_param_get(node, &value) < 0 || value == NULL) { + dmlog_error("dmapi_param_get failed to get param: %s", dm_node_str(node)); + return JS_UNDEFINED; + } + } + + JSValue param; + enum DM_DATA_TYPE type = dm_node_data_type(node->id); + if (dm_node_is_param_list(node->id)) { + param = JS_NewString(qjs_ctx(), value); + } else if (type == DM_DATA_BOOLEAN) { + if (strcmp(value, "1") == 0 || strcmp(value, "true") == 0) { + param = JS_NewBool(qjs_ctx(), 1); + } else { + param = JS_NewBool(qjs_ctx(), 0); + } + } else if (type == DM_DATA_INT) { + param = JS_NewInt32(qjs_ctx(), atoi(value)); + } else if (type == DM_DATA_UINT || type == DM_DATA_ULONG) { + param = JS_NewUint32(qjs_ctx(), strtoul (value, NULL, 0)); + } else { + param = JS_NewString(qjs_ctx(), value); + } + + free(value); + return param; + } else if (dm_node_is_object(node->id) || (dm_node_is_objectlist(node->id) && dm_node_is_index_complete(node))) { + const struct dm_object *obj = dm_node_get_object(node->id); + JSValue obj_val = JS_NewObject(qjs_ctx()); + + for (i = 0; i < obj->param_num; i++) { + dm_node_t param_node = *node; + param_node.id = obj->param_list[i]->node_id; + if (only_db && !dm_node_has_db(param_node.id)) { + continue; + } + JSValue param_val = get_object(¶m_node, only_db); + if (JS_IsException(param_val)) { + JS_FreeValue(qjs_ctx(), obj_val); + return JS_UNDEFINED; + } else { + JS_SetPropertyStr(qjs_ctx(), obj_val, obj->param_list[i]->name, param_val); + } + } + + if (dm_node_is_objectlist(node->id)) { + JS_SetPropertyStr(qjs_ctx(), obj_val, ".index", JS_NewUint32(qjs_ctx(), node->index[node->cnt - 1])); + } + + for (i = 0; i < obj->object_num; i++) { + dm_node_t obj_node = *node; + obj_node.id = obj->object_list[i]->node_id; + if (dm_node_is_objectlist(obj_node.id) + && only_db && !dm_node_has_db(obj_node.id)) { + continue; + } + JSValue child_obj = get_object(&obj_node, only_db); + if (JS_IsUndefined(child_obj)) { + JS_FreeValue(qjs_ctx(), obj_val); + return JS_UNDEFINED; + } else { + JS_SetPropertyStr(qjs_ctx(), obj_val, obj->object_list[i]->name, child_obj); + } + } + + return obj_val; + } else if (dm_node_is_objectlist(node->id)) { + dm_nodelist_h list = dm_nodelist_find(node, NULL, only_db); + JSValue arr = JS_NewArray(qjs_ctx()); + // JS_SetPropertyStr(qjs_ctx(), res, node->name, arr); + for (i = 0; i < dm_nodelist_cnt(list); i++) { + const dm_node_t *n = dm_nodelist_node(list, i); + JSValue ins_val = get_object(n, only_db); + if (JS_IsUndefined(ins_val)) { + JS_FreeValue(qjs_ctx(), arr); + return JS_UNDEFINED; + } else { + // add index into the objecct + // JS_SetPropertyStr(qjs_ctx(), ins_val, "index", JS_NewInt32(qjs_ctx(), n->index[n->cnt - 1])); + JS_DefinePropertyValueUint32(qjs_ctx(), arr, i, ins_val, JS_PROP_C_W_E); + } + } + dm_nodelist_free(list); + return arr; + } else { + dmlog_error("get_object, unknown node: %s", dm_node_str(node)); + return JS_UNDEFINED; + } +} + +static JSValue _dm_get(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int only_db = 1; + dm_node_t node; + if (argc < 1) { + dmlog_info("_dm_get, missing argument"); + return JS_UNDEFINED; + } + + if (JS_IsString(argv[0])) { + const char *path = JS_ToCString(ctx, argv[0]); + if (dm_path2node(path, &node) < 0) { + dmlog_error("_dm_get, invalid pathname: %s", path); + JS_FreeCString(ctx, path); + return JS_UNDEFINED; + } + JS_FreeCString(ctx, path); + + if (argc >= 2) { + only_db = JS_ToBool(ctx, argv[1]); + } + } else if (JS_IsNumber(argv[0])) { + JS_ToUint32(ctx, &node.id, argv[0]); + if (argc >= 2) { + if (JS_IsNumber(argv[1])) { + node.cnt = 1; + JS_ToUint32(ctx, &node.index[0], argv[1]); + } else if (JS_IsArray(ctx, argv[1])) { + JSValue length = JS_GetPropertyStr(ctx, argv[1], "length"); + JS_ToInt32(ctx, &node.cnt , length); + JS_FreeValue(ctx, length); + for (unsigned int i = 0; i < node.cnt; i++) { + JSValue index_val = JS_GetPropertyUint32(ctx, argv[1], i); + JS_ToUint32(ctx, &node.index[i], index_val); + JS_FreeValue(ctx, index_val); + } + } else { + dmlog_error("_dm_get, invalid index"); + return JS_UNDEFINED; + } + } else { + node.cnt = 0; + } + } else { + dmlog_error("_dm_get, invalid parameter"); + return JS_UNDEFINED; + } + + JSValue val = get_object(&node, only_db); + if (JS_IsException(val)) { + return JS_UNDEFINED; + } + + return val; +} + + +static JSValue _set_param_value(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int only_db) +{ + int ret = -1; + dm_node_t node; + if (argc < 2) { + dmlog_info("_dm_set, missing argument"); + return JS_NewInt32(ctx, ret); + } + + const char *value = NULL; + + if (JS_IsString(argv[0])) { + const char *path = JS_ToCString(ctx, argv[0]); + if (dm_path2node(path, &node) < 0) { + dmlog_error("_dm_set, invalid pathname: %s", path); + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); + } + JS_FreeCString(ctx, path); + value = JS_ToCString(ctx, argv[1]); + } else if (JS_IsNumber(argv[0])) { + JS_ToUint32(ctx, &node.id, argv[0]); + if (dm_node_index_cnt(node.id) > 0) { + if (JS_IsNumber(argv[1])) { + node.cnt = 1; + JS_ToUint32(ctx, &node.index[0], argv[1]); + } else if (JS_IsArray(ctx, argv[1])) { + JSValue length = JS_GetPropertyStr(ctx, argv[1], "length"); + JS_ToInt32(ctx, &node.cnt , length); + JS_FreeValue(ctx, length); + for (unsigned int i = 0; i < node.cnt; i++) { + JSValue index_val = JS_GetPropertyUint32(ctx, argv[1], i); + JS_ToUint32(ctx, &node.index[i], index_val); + JS_FreeValue(ctx, index_val); + } + } else { + dmlog_error("_dm_get, invalid index"); + return JS_NewInt32(ctx, ret); + } + value = JS_ToCString(ctx, argv[2]); + } else { + node.cnt = 0; + value = JS_ToCString(ctx, argv[1]); + } + } else { + dmlog_error("_dm_get, invalid parameter"); + return JS_NewInt32(ctx, ret); + } + + if (value != NULL) { + if (only_db) { + dbmgr_tranx_begin(); + ret = dbmgr_set(&node, value); + if (dm_node_index_cnt(node.id) > 0) { + ret |= inode_buf_update_param_value(&node, value); + } + dbmgr_tranx_commit(); + } else { + dmapi_param_set(&node, value); + } + JS_FreeCString(ctx, value); + } else { + dmlog_error("_dm_set, missing value"); + return JS_NewInt32(ctx, -1); + } + + return JS_NewInt32(ctx, ret); +} + +static JSValue _db_set(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return _set_param_value(ctx, this_val, argc, argv, 1); +} + +static JSValue _dm_set(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return _set_param_value(ctx, this_val, argc, argv, 0); +} + +static JSValue _dm_node_id(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + dm_node_t node; + if (argc < 1 || !JS_IsString(argv[0])) { + dmlog_info("_dm_node_id, invalid argument"); + return JS_UNDEFINED; + } + + const char *path = JS_ToCString(ctx, argv[0]); + if (dm_path2node(path, &node) < 0) { + dmlog_info("_dm_node_id, invalid pathname: %s", path); + return JS_NewInt32(ctx, -1); + } + JS_FreeCString(ctx, path); + return JS_NewUint32(ctx, node.id); +} + +static JSValue _dm_node(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + dm_node_t node; + if (argc < 1 || !JS_IsString(argv[0])) { + dmlog_info("_dm_node, invalid argument"); + return JS_UNDEFINED; + } + JSValue arr = JS_NewArray(qjs_ctx()); + + const char *path = JS_ToCString(ctx, argv[0]); + if (dm_path2node(path, &node) < 0) { + dmlog_info("_dm_node, invalid pathname: %s", path); + JS_DefinePropertyValueUint32(qjs_ctx(), arr, 0, JS_NewInt32(ctx, -1), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(qjs_ctx(), arr, 1, JS_NewArray(qjs_ctx()), JS_PROP_C_W_E); + } else { + JS_DefinePropertyValueUint32(qjs_ctx(), arr, 0, JS_NewUint32(ctx, node.id), JS_PROP_C_W_E); + JSValue index_arr = JS_NewArray(qjs_ctx()); + for (int i = 0; i < node.cnt; i++) { + JS_DefinePropertyValueUint32(qjs_ctx(), index_arr, i, JS_NewUint32(ctx, node.index[i]), JS_PROP_C_W_E); + } + + JS_DefinePropertyValueUint32(qjs_ctx(), arr, 1, index_arr, JS_PROP_C_W_E); + } + + JS_FreeCString(ctx, path); + return arr; +} + +static JSValue _dm_instances(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int only_db = 1; + dm_node_t node; + const char *keys = NULL; + if (argc < 1) { + dmlog_info("_dm_node, invalid argument"); + return JS_UNDEFINED; + } + + JSValue arr = JS_NewArray(qjs_ctx()); + const char *path = JS_ToCString(ctx, argv[0]); + if (dm_path2node(path, &node) < 0) { + dmlog_info("_dm_node, invalid pathname: %s", path); + JS_FreeCString(ctx, path); + return arr; + } + JS_FreeCString(ctx, path); + + if (argc >= 2 && JS_IsString(argv[1])) { + keys = JS_ToCString(ctx, argv[1]); + } + + if (argc >= 3) { + only_db = JS_ToBool(ctx, argv[2]); + } + + dm_nodelist_h list = dm_nodelist_find(&node, keys, only_db); + if (keys != NULL) { + JS_FreeCString(ctx, keys); + } + + const dm_node_t *pnode = NULL; + int i = 0; + nodelist_for_each_node(pnode, list) + { + dm_path_t path; + dm_node2name(pnode, path, sizeof(path)); + JS_DefinePropertyValueUint32(qjs_ctx(), arr, i++, JS_NewString(ctx, path), JS_PROP_C_W_E); + } + + dm_nodelist_free(list); + return arr; +} + +int update_dm_value(JSContext *ctx, const char *key, JSValue value) +{ + if (JS_IsString(value) || JS_IsNumber(value) || JS_IsBool(value)) { + dm_node_t node; + if (dm_path2node(key, &node) < 0) { + dmlog_error("update_dm_value, invalid pathname: %s", key); + return -1; + } + if (!dm_node_is_parameter(node.id) || !dm_node_is_index_complete(&node)) { + dmlog_error("update_dm_value, unexpected parameter: %s", key); + return -1; + } + const char *value_str = JS_ToCString(ctx, value); + if (dm_node_is_bool_type(node.id)) { + if (*value_str == '1' || !strcmp(value_str, "true")) { + dmapi_param_set(&node, "true"); + } else { + dmapi_param_set(&node, "false"); + } + } else { + dmapi_param_set(&node, value_str); + } + JS_FreeCString(ctx, value_str); + } else if (JS_IsArray(ctx, value)) { + dm_node_t node; + if (dm_path2node(key, &node) < 0) { + dmlog_error("update_dm_value, invalid pathname: %s", key); + return -1; + } + // multi-instance node is expected. + if (!dm_node_is_objectlist(node.id)) { + dmlog_error("update_dm_value, unexpected multi-instance object: %s", key); + return -1; + } + + uint32_t len; + JSValue length = JS_GetPropertyStr(ctx, value, "length"); + JS_ToUint32(ctx, &len, length); + JS_FreeValue(ctx, length); + + for (unsigned int i = 0; i < len; i++) { + if (dmapi_object_add(&node) < 0) { + dmlog_error("update_dm_value, failed to add instance %s", key); + return -1; + } + + dm_path_t path; + dm_node2name(&node, path, sizeof(path)); + strcat(path, "."); + // new path as the new key + JSValue inst_val = JS_GetPropertyUint32(ctx, value, i); + if (update_dm_value(ctx, path, inst_val) < 0) { + dmlog_error("update_dm_value, failed to update instance %s", path); + JS_FreeValue(ctx, inst_val); + return -1; + } + JS_FreeValue(ctx, inst_val); + } + } else if (JS_IsObject(value)) { + uint32_t len, i; + JSPropertyEnum *tab; + if (JS_GetOwnPropertyNames(ctx, &tab, &len, value, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { + dmlog_error("_dm_update, JS_GetOwnPropertyNames failed"); + return -1; + } + + for (i = 0; i < len; i++) { + JSValue child_val = JS_GetProperty(ctx, value, tab[i].atom); + const char *child_key = JS_AtomToCString(ctx, tab[i].atom); + char *new_key = NULL; + asprintf(&new_key, "%s%s", key, child_key); + update_dm_value(ctx, new_key, child_val); + free(new_key); + JS_FreeValue(ctx, child_val); + } + js_free(ctx, tab); + } else if (JS_IsUndefined(value)) { + dmlog_debug("update_dm_value, skip update value for %s", key); + } + + return 0; +} + +static JSValue _dm_update(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 2) { + dmlog_info("_dm_update, invalid argument"); + return JS_UNDEFINED; + } + + const char *path = JS_ToCString(ctx, argv[0]); + int ret = update_dm_value(ctx, path, argv[1]); + return JS_NewInt32(ctx, ret); +} + +static JSValue _dm_apply(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + dm_node_t node; + if (argc < 1) { + dmlog_info("_dm_apply, missing argument"); + return JS_UNDEFINED; + } + + if (JS_IsString(argv[0])) { + const char *path = JS_ToCString(ctx, argv[0]); + if (dm_path2node(path, &node) < 0) { + dmlog_error("_dm_apply, invalid pathname: %s", path); + JS_FreeCString(ctx, path); + return JS_UNDEFINED; + } + JS_FreeCString(ctx, path); + } else if (JS_IsNumber(argv[0])) { + JS_ToUint32(ctx, &node.id, argv[0]); + if (argc >= 2) { + if (JS_IsNumber(argv[1])) { + node.cnt = 1; + JS_ToUint32(ctx, &node.index[0], argv[1]); + } else if (JS_IsArray(ctx, argv[1])) { + JSValue length = JS_GetPropertyStr(ctx, argv[1], "length"); + JS_ToInt32(ctx, &node.cnt , length); + JS_FreeValue(ctx, length); + for (unsigned int i = 0; i < node.cnt; i++) { + JSValue index_val = JS_GetPropertyUint32(ctx, argv[1], i); + JS_ToUint32(ctx, &node.index[i], index_val); + JS_FreeValue(ctx, index_val); + } + } else { + dmlog_error("_dm_apply, invalid index"); + return JS_UNDEFINED; + } + } else { + node.cnt = 0; + } + } else { + dmlog_error("_dm_apply, invalid parameter"); + return JS_UNDEFINED; + } + + struct node_change change; + change.node = node; + change.redirected = 0; + change.action = DATA_MODEL_SET; + dm_apply_node(&change); + return JS_NewInt32(ctx, 0); +} + +int qjs_dm_api_init() +{ + qjs_register_c_api("_dm_get", _dm_get, 1); + qjs_register_c_api("_dm_set", _dm_set, 2); + qjs_register_c_api("_db_set", _db_set, 2); + qjs_register_c_api("_dm_node_id", _dm_node_id, 1); + qjs_register_c_api("_dm_node", _dm_node, 1); + qjs_register_c_api("_dm_instances", _dm_instances, 1); + qjs_register_c_api("_dm_update", _dm_update, 1); + qjs_register_c_api("_dm_apply", _dm_apply, 1); + return 0; +} diff --git a/dm-framework/dm-api/src/quickjs/qjs_log.c b/dm-framework/dm-api/src/quickjs/qjs_log.c new file mode 100644 index 000000000..a0635e383 --- /dev/null +++ b/dm-framework/dm-api/src/quickjs/qjs_log.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include + +#include "dm_log.h" +#include "qjs.h" +#include "qjs_api.h" + +enum LOG_TYPE { + QJS_LOG_INFO, + QJS_LOG_ERROR, + QJS_LOG_DEBUG +}; + +static JSValue _log(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, enum LOG_TYPE type) +{ + // keep it simple at the moment: syslog for each parameter independently. + for (int i = 0; i < argc; i++) { + JSValue json = JS_JSONStringify(ctx, argv[i], JS_UNDEFINED, JS_UNDEFINED); + const char *msg = JS_ToCString(ctx, json); + if (type == QJS_LOG_INFO) { + dmlog_info(msg); + } else if (type == QJS_LOG_ERROR) { + dmlog_error(msg); + } else { + dmlog_debug(msg); + } + + JS_FreeCString(ctx, msg); + JS_FreeValue(ctx, json); + } + + return JS_UNDEFINED; +} + +static JSValue _log_info(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return _log(ctx, this_val, argc, argv, QJS_LOG_INFO); +} + +static JSValue _log_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return _log(ctx, this_val, argc, argv, QJS_LOG_ERROR); +} + +static JSValue _log_debug(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return _log(ctx, this_val, argc, argv, QJS_LOG_DEBUG); +} + +int qjs_log_api_init() +{ + qjs_register_c_api("_log_info", _log_info, 1); + qjs_register_c_api("_log_error", _log_error, 1); + qjs_register_c_api("_log_debug", _log_debug, 1); + return 0; +} \ No newline at end of file diff --git a/dm-framework/dm-api/src/quickjs/qjs_ubus_api.c b/dm-framework/dm-api/src/quickjs/qjs_ubus_api.c new file mode 100644 index 000000000..04949319b --- /dev/null +++ b/dm-framework/dm-api/src/quickjs/qjs_ubus_api.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "dm_log.h" +#include "qjs.h" +#include "qjs_api.h" +#include "ubus_client.h" +#include "dm_apply.h" + +struct ubus_event_data { + struct uloop_timeout tm; + struct ubus_event_handler ev; + struct blob_attr *msg; + int res; +}; + +struct invoke_cb_arg { + JSContext *ctx; + JSValue res; +}; + +static void ubus_invoke_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct invoke_cb_arg *arg = (struct invoke_cb_arg *)req->priv; + if (!msg) + return; + + char *json_str = blobmsg_format_json(msg, true); + if (json_str == NULL) { + dmlog_error("blobmsg_format_json failed"); + return; + } + // dmlog_debug("ubus_invoke_cb: %s", json_str); + arg->res = JS_ParseJSON(arg->ctx, json_str, strlen(json_str), "ubus_invoke_cb"); + if (JS_IsException(arg->res)) { + dmlog_error("JS_ParseJSON failed %s", json_str); + } + free(json_str); +} + +static JSValue _ubus_call(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int ret = -1; + const char *path = JS_ToCString(ctx, argv[0]); + const char *method = JS_ToCString(ctx, argv[1]); + struct invoke_cb_arg arg; + arg.ctx = ctx; + arg.res = JS_UNDEFINED; + + // dmlog_debug("ubus %s: %s, argc: %d", path, method, argc); + + if (argc > 2) { + struct blob_buf bb = {}; + if (!JS_IsObject(argv[2])) { + dmlog_error("_ubus_call, object is expected as ubus arguments"); + goto end; + } + JSValue json = JS_JSONStringify(ctx, argv[2], JS_UNDEFINED, JS_UNDEFINED); + if (JS_IsException(json)) { + dmlog_error("JS_ParseJSON failed"); + goto end; + } + blob_buf_init(&bb, 0); + const char *str = JS_ToCString(ctx, json); + if (!blobmsg_add_json_from_string(&bb, str)) { + dmlog_error("blobmsg_add_json_from_string failed"); + JS_FreeCString(ctx, str); + JS_FreeValue(ctx, json); + goto end; + } + + ret = ubus_client_call(path, method, bb.head, ubus_invoke_cb, &arg); + blob_buf_free(&bb); + + if (strcmp(path, "uci") == 0 && + (strcmp(method, "set") == 0 || strcmp(method, "add") == 0 || strcmp(method, "delete") == 0)) { + dmlog_debug("ubus uci %s: args: %s", method, str); + if (ret == 0) { + JSValue val = JS_GetPropertyStr(ctx, argv[2], "config"); + if (JS_IsString(val)) { + const char *config = JS_ToCString(ctx, val); + add_apply_package(strdup(config)); + JS_FreeCString(ctx, config); + } + JS_FreeValue(ctx, val); + } + } + + JS_FreeCString(ctx, str); + JS_FreeValue(ctx, json); + } else { + ret = ubus_client_call(path, method, NULL, ubus_invoke_cb, &arg); + } + + if (ret != 0 || JS_IsException(arg.res)) { + ret = -1; + } +end: + JS_FreeCString(ctx, path); + JS_FreeCString(ctx, method); + + JSValue arr = JS_NewArray(ctx); + JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, ret)); + JS_SetPropertyUint32(ctx, arr, 1, arg.res); + return arr; +} + +static void ubus_listen_timeout(struct uloop_timeout *timeout) +{ + uloop_end(); +} + +static bool compare_blob_msg(struct blob_attr *src_attr, struct blob_attr *dst_attr) +{ + if (!src_attr || !dst_attr) + return false; + + int src_type = blob_id(src_attr); + int dst_type = blob_id(dst_attr); + if (src_type != dst_type) + return false; + + void *src_val = blobmsg_data(src_attr); + void *dst_val = blobmsg_data(dst_attr); + + switch (src_type) { + case BLOBMSG_TYPE_STRING: + if (src_val == NULL && dst_val == NULL) + return true; + + if (src_val && dst_val && strcmp((char *)src_val, (char*)dst_val) == 0) + return true; + break; + default: + break; + } + + return false; +} + +static bool validate_blob_message(struct blob_attr *src, struct blob_attr *dst) +{ + if (!src || !dst) + return false; + + size_t src_len = (size_t)blobmsg_data_len(src); + size_t dst_len = (size_t)blobmsg_data_len(dst); + + if (dst_len < src_len) + return false; + + bool res = true; + struct blob_attr *src_attr, *dst_attr; + + __blob_for_each_attr(src_attr, blobmsg_data(src), src_len) { + bool matched = false; + __blob_for_each_attr(dst_attr, blobmsg_data(dst), dst_len) { + if (strcmp(blobmsg_name(src_attr), blobmsg_name(dst_attr)) != 0) { + continue; + } + + matched = compare_blob_msg(src_attr, dst_attr); + break; + } + if (matched == false) { + res = false; + break; + } + } + + return res; +} + +static void ubus_receive_event(struct ubus_context *ctx, struct ubus_event_handler *ev, + const char *type, struct blob_attr *msg) +{ + struct ubus_event_data *data; + if (!msg || !ev) + return; + + data = container_of(ev, struct ubus_event_data, ev); + // skip the check if the content of event to check (data->msg) is empty. + if (data->msg == NULL || validate_blob_message(data->msg, msg) == true) { + data->res = 0; + uloop_end(); + } + + return; +} + +static JSValue _ubus_wait_event(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + if (argc < 2) { + dmlog_info("_ubus_wait_event, missing argument"); + return JS_NewInt32(ctx, -1); + } + + unsigned int timeout = 100; + struct blob_attr *msg = NULL; + JS_ToUint32(ctx, &timeout, argv[1]); + struct blob_buf b; + memset(&b, 0, sizeof(b)); + struct ubus_context *ubus_ctx = ubus_connect(NULL); + if (!ubus_ctx) { + return JS_NewInt32(ctx, -1); + } + + const char *event = JS_ToCString(ctx, argv[0]); + blob_buf_init(&b, 0); + if (argc > 2) { + JSValue json_val = JS_JSONStringify(ctx, argv[2], JS_UNDEFINED, JS_UNDEFINED); + const char *json_str = JS_ToCString(ctx, json_val); + blobmsg_add_json_from_string(&b, json_str); + JS_FreeCString(ctx, json_str); + msg = b.head; + } + + struct ubus_event_data data = { + .tm.cb = ubus_listen_timeout, + .ev.cb = ubus_receive_event, + .msg = msg, + .res = -1, + }; + + uloop_init(); + ubus_add_uloop(ubus_ctx); + + if (ubus_register_event_handler(ubus_ctx, &data.ev, event) == 0) { + uloop_timeout_set(&data.tm, timeout * 1000); + uloop_run(); + uloop_done(); + ubus_unregister_event_handler(ubus_ctx, &data.ev); + } else { + dmlog_error("_ubus_wait_event, failed to register event"); + } + + JS_FreeCString(ctx, event); + blob_buf_free(&b); + ubus_free(ubus_ctx); + return JS_NewInt32(ctx, data.res); +} + +int qjs_ubus_api_init() +{ + ubus_client_init(); + qjs_register_c_api("_ubus_call", _ubus_call, 2); + qjs_register_c_api("_ubus_wait_event", _ubus_wait_event, 2); + + return 0; +} diff --git a/dm-framework/dm-api/src/utils/dm_list.c b/dm-framework/dm-api/src/utils/dm_list.c new file mode 100644 index 000000000..0e8861e21 --- /dev/null +++ b/dm-framework/dm-api/src/utils/dm_list.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + + +#include +#include + +#include "dm_list.h" +#include "dm_log.h" + +#define DEFAULT_LIST_BUF_SIZE 128 +#define MAX_LIST_BUF_SIZE 1024 * 256 + +struct dm_list { + unsigned int cnt; + unsigned int buf_size; + void **mem_ptr; + list_item_cmp cmp; +}; + + +// default compare for string. +static int list_str_cmp(const void *item1, const void *item2) +{ + if (strcmp((const char*)item1, (const char*)item2) == 0) { + return 0; + } + + return 1; +} + +dm_list_h dm_list_create(list_item_cmp cmp_cb) +{ + struct dm_list *list = calloc(1, sizeof(struct dm_list)); + + if (list == NULL) + return NULL; + + if (cmp_cb == NULL) { + list->cmp = list_str_cmp; + } else { + list->cmp = cmp_cb; + } + + return (dm_list_h)list; +} + +int dm_list_append(dm_list_h list, void *item) +{ + if (list == NULL) + return -1; + + if (list->buf_size == 0) { + list->buf_size = DEFAULT_LIST_BUF_SIZE; + list->mem_ptr = (void **)calloc(DEFAULT_LIST_BUF_SIZE, sizeof(void *)); + if (list->mem_ptr == NULL) + return -1; + } else if (list->buf_size <= list->cnt) { + // Check if the memory required to allocate is not out of bounds. + if (list->buf_size > MAX_LIST_BUF_SIZE / 2) + return -1; + else { + // double buffer + void *new_mem = realloc((void *)list->mem_ptr, 2 * (list->buf_size) * sizeof(void *)); + + if (new_mem == NULL) + return -1; + list->buf_size *= 2; + list->mem_ptr = (void **)new_mem; + } + } + + list->mem_ptr[list->cnt] = item; + list->cnt++; + + return 0; +} + +int dm_list_cnt(dm_list_h list) +{ + if (list == NULL) + return 0; + + return list->cnt; +} + +void *dm_list_get(dm_list_h list, int i) +{ + if (list == NULL || i >= list->cnt) + return NULL; + + return list->mem_ptr[i]; +} + +int dm_list_contains(dm_list_h list, const void *item) +{ + int cnt = dm_list_cnt(list); + for (int i = 0; i < cnt; i++) { + void *val = dm_list_get(list, i); + if (val && list->cmp(val, item) == 0) { + return 1; + } + } + + return 0; +} + +int dm_list_remove(dm_list_h list, void *item) +{ + int cnt = dm_list_cnt(list); + for (int i = 0; i < cnt; i++) { + void *val = dm_list_get(list, i); + if (val && list->cmp(val, item) == 0) { + free(val); + list->mem_ptr[i] = NULL; + return 0; + } + } + + return -1; +} + +void dm_list_free(dm_list_h list) +{ + if (list == NULL) + return; + + int i; + + for (i = 0; i < list->cnt; i++) + if (list->mem_ptr[i]) + free(list->mem_ptr[i]); + + if (list->mem_ptr) + free(list->mem_ptr); + + free(list); +} + +void dm_list_dump(dm_list_h list) +{ + int cnt = dm_list_cnt(list); + for (int i = 0; i < cnt; i++) { + char *val = dm_list_get(list, i); + if (val) { + dmlog_debug("dm-list val: %s", val); + } + } +} diff --git a/dm-framework/dm-api/src/utils/dm_list.h b/dm-framework/dm-api/src/utils/dm_list.h new file mode 100644 index 000000000..22ec5518c --- /dev/null +++ b/dm-framework/dm-api/src/utils/dm_list.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef DM_LIST_H +#define DM_LIST_H + +struct dm_list; +typedef struct dm_list *dm_list_h; + +// return 0 if equal, otherwise 1 +typedef int (*list_item_cmp)(const void *item1, const void *item2); + +/** + * Create a list +* @param cmp_cb item compare callback (optional if dm_list_contains will not be called) + * @return a valid handle if successful, otherwise return NULL + */ +dm_list_h dm_list_create(list_item_cmp cmp_cb); + +/** + * Append an item to the list + * @param list handle create by dm_list_create + * @item pointer of item, the memory must be allocated by user using malloc. + * memory of item will be not free'd until dm_list_free or dm_list_del(not implemented yet) is called. + * @return 0 if successful, otherwise return -1 + */ +int dm_list_append(dm_list_h list, void *item); + +/** + * Get count of items in the list + * @param list handle create by dm_list_create + * @return number of items + */ +int dm_list_cnt(dm_list_h list); + +/** + * Get item in the list by index + * @param list handle create by dm_list_create + * @param i, index of item to get, valid scope is [0, cnt-1] + * @return pointer of item if index is valid, otherwise return NULL + */ +void *dm_list_get(dm_list_h list, int i); + +/** + * check if a item is already in the list + * @param list handle create by dm_list_create + * @param item, item to check + * @return 1 if exist, otherwise return 0 + */ +int dm_list_contains(dm_list_h list, const void *item); + +/** + * remove a item from the list + * @param list handle create by dm_list_create + * @param item, item to remove + * @return 0 if successful, otherwise return -1 + */ +int dm_list_remove(dm_list_h list, void *item); + +/** + * Free the list, including all items in the list + * @param list handle create by dm_list_create + * @return none + */ +void dm_list_free(dm_list_h list); + +// only works when the value is string type +void dm_list_dump(dm_list_h list); +#endif diff --git a/dm-framework/dm-api/src/utils/dm_log.c b/dm-framework/dm-api/src/utils/dm_log.c new file mode 100644 index 000000000..2930c9d45 --- /dev/null +++ b/dm-framework/dm-api/src/utils/dm_log.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include +#include + +void dmlog_init(const char *name, int log_level) +{ + // todo : implement the filter level + openlog(name, LOG_NOWAIT | LOG_ODELAY, LOG_LOCAL0); +} + +void dmlog_info(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsyslog(LOG_INFO, fmt, ap); + va_end(ap); +} +void dmlog_warn(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsyslog(LOG_WARNING, fmt, ap); + va_end(ap); +} +void dmlog_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); +} +void dmlog_debug(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); +} +void dmlog_close() +{ + closelog(); +} diff --git a/dm-framework/dm-api/src/utils/dm_uci.c b/dm-framework/dm-api/src/utils/dm_uci.c new file mode 100644 index 000000000..64a73848b --- /dev/null +++ b/dm-framework/dm-api/src/utils/dm_uci.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include +#include +#include + +#include "dm_uci.h" +#include "dm_log.h" +#include "ubus_client.h" + +enum { + UBUS_UCI_GET_DATA, + __UBUS_MAX +}; + +static const struct blobmsg_policy ubus_policy_get_value[__UBUS_MAX] = { + [UBUS_UCI_GET_DATA] = { .name = "value", .type = BLOBMSG_TYPE_UNSPEC }, +}; + +static const struct blobmsg_policy ubus_policy_get_values[__UBUS_MAX] = { + [UBUS_UCI_GET_DATA] = { .name = "values", .type = BLOBMSG_TYPE_TABLE }, +}; + +int dm_uci_set(const char *uci_path, const char *value) +{ + int ret = 0; + dmlog_debug("dm_uci_set %s = %s", uci_path, value); + + char *path = strdup(uci_path); + char *config = strtok(path, "."); + char *section = strtok(NULL, "."); + char *option = strtok(NULL, "."); + + // Check if section and option are not NULL + if (section == NULL || option == NULL) { + free(path); + dmlog_error("dm_uci_set missing arguments"); + return -1; + } + + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + // Add config and section to the message + blobmsg_add_string(&b, "config", config); + blobmsg_add_string(&b, "section", section); + + // Prepare values table + struct blob_attr *values = blobmsg_open_table(&b, "values"); + + // Add option and value to the table + blobmsg_add_string(&b, option, value); + + // Close the table + blobmsg_close_table(&b, values); + + // Invoke the UBUS call + if (ubus_client_call("uci", "set", b.head, NULL, NULL) != 0) { + dmlog_error("dm_uci_set failed"); + ret = -1; + } + blob_buf_free(&b); + free(path); + return ret; +} + +int dm_uci_add(const char *config, const char *type, const char *section_name, name_val_t *opt_values, int value_cnt) +{ + int ret = 0; + dmlog_debug("dm_uci_add %s.%s=%s", config, section_name ? section_name : "", type); + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + + // Add config and section to the message + blobmsg_add_string(&b, "config", config); + blobmsg_add_string(&b, "type", type); + blobmsg_add_string(&b, "name", section_name); + + if (value_cnt > 0) { + struct blob_attr *values = blobmsg_open_table(&b, "values"); + for (int i = 0; i < value_cnt; i++) { + blobmsg_add_string(&b, opt_values[i].name, opt_values[i].value); + } + blobmsg_close_table(&b, values); + } + + // Invoke the UBUS call + if (ubus_client_call("uci", "add", b.head, NULL, NULL) != 0) { + dmlog_error("dm_uci_add failed"); + ret = -1; + } + blob_buf_free(&b); + return ret; +} + +int dm_uci_del(const char *config, const char *section) +{ + int ret = 0; + dmlog_debug("dm_uci_del %s.%s", config, section); + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + + // Add config and section to the message + blobmsg_add_string(&b, "config", config); + blobmsg_add_string(&b, "section", section); + + // Invoke the UBUS call + if (ubus_client_call("uci", "delete", b.head, NULL, NULL) != 0) { + dmlog_error("dm_uci_add failed"); + ret = -1; + } + blob_buf_free(&b); + return ret; +} + + +// Callback function to handle the response +static void get_value_callback(struct ubus_request *req, int type, struct blob_attr *msg) { + struct blob_attr *tb[__UBUS_MAX]; + char **value = (char **) req->priv; + char *buffer = NULL; + + blobmsg_parse(ubus_policy_get_value, __UBUS_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[UBUS_UCI_GET_DATA]) { + dmlog_error("get_value_callback, value is not found"); + return; + } + + struct blob_attr *attr = tb[UBUS_UCI_GET_DATA]; + switch (blobmsg_type(attr)) { + case BLOBMSG_TYPE_STRING: + *value = strdup(blobmsg_get_string(attr)); + break; + case BLOBMSG_TYPE_INT32: + asprintf(&buffer, "%d", blobmsg_get_u32(attr)); + *value = buffer; + break; + case BLOBMSG_TYPE_INT64: + asprintf(&buffer, "%lld", blobmsg_get_u64(attr)); + *value = buffer; + break; + case BLOBMSG_TYPE_ARRAY: { + struct blob_attr *cur; + int rem; + buffer = strdup(""); + blobmsg_for_each_attr(cur, attr, rem) { + char *temp; + if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING) { + asprintf(&temp, "%s%s,", buffer, blobmsg_get_string(cur)); + } else if (blobmsg_type(cur) == BLOBMSG_TYPE_INT32) { + asprintf(&temp, "%s%d,", buffer, blobmsg_get_u32(cur)); + } else if (blobmsg_type(cur) == BLOBMSG_TYPE_INT64) { + asprintf(&temp, "%s%lld,", buffer, blobmsg_get_u64(cur)); + } + free(buffer); + buffer = temp; + } + // Remove the last comma, only if the array is not empty + if (strlen(buffer) > 0) { + buffer[strlen(buffer) - 1] = '\0'; + } + *value = buffer; + break; + } + default: + break; + } +} + +int dm_uci_get(const char *uci_path, char **value) +{ + int ret = 0; + char *path = strdup(uci_path); + char *confdir = NULL; + char *config = NULL; + char *section = NULL; + char *option = NULL; + + char *rest; // The rest of the path after confdir + + // Check if the path starts with a '/' + if(path[0] == '/') { + // Find the last slash, which is the delimiter between confdir and the rest + char *last_slash = strrchr(path, '/'); + if (last_slash != NULL) { + // Null-terminate the string at the last slash to separate confdir + *last_slash = '\0'; + confdir = path; + // The rest starts after the last slash + rest = last_slash + 1; + } else { + dmlog_error("dm_uci_get, invalid uci path: %s", path); + free(path); + return -1; + } + } else { + // If the path doesn't start with a '/', confdir is missing + rest = path; + } + + // Now we split the rest of the path + char *ptr = strtok(rest, "."); + if (ptr != NULL) { + config = ptr; + ptr = strtok(NULL, "."); + } + + if (ptr != NULL) { + section = ptr; + ptr = strtok(NULL, "."); + } + + if (ptr != NULL) { + option = ptr; + } + + if (config == NULL || section == NULL || option == NULL) { + dmlog_error("dm_uci_get, missing arguments: %s", uci_path); + free(path); + return -1; + } + + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + // If confdir is not NULL, add it to the message + if (confdir != NULL) { + blobmsg_add_string(&b, "confdir", confdir); + } + blobmsg_add_string(&b, "config", config); + blobmsg_add_string(&b, "section", section); + blobmsg_add_string(&b, "option", option); + + // Invoke the UBUS call + if (ubus_client_call("uci", "get", b.head, get_value_callback, (void *) value) != 0) { + dmlog_error("dm_uci_get failed"); + ret = -1; + } + blob_buf_free(&b); + free(path); + return ret; +} + +static void get_sect_callback(struct ubus_request *req, int type, struct blob_attr *msg) { + struct blob_attr *tb[__UBUS_MAX]; + json_object **res = (json_object **) req->priv; + + blobmsg_parse(ubus_policy_get_values, __UBUS_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[UBUS_UCI_GET_DATA]) { + dmlog_error("get_sect_callback, values is not found"); + return; + } + + char *json_str = blobmsg_format_json(tb[UBUS_UCI_GET_DATA], true); + if (!json_str) { + dmlog_error("get_sect_callback, blobmsg_format_json failed"); + return; + } + + *res = json_tokener_parse(json_str); + free(json_str); +} + +int dm_uci_get_section_list(const char *config, const char *type, name_val_t *match, int match_cnt, json_object **res) +{ + int ret = 0; + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "config", config); + blobmsg_add_string(&b, "type", type); + + if (match_cnt > 0) { + struct blob_attr *matches = blobmsg_open_table(&b, "match"); + for (int i = 0; i < match_cnt; i++) { + blobmsg_add_string(&b, match[i].name, match[i].value); + } + blobmsg_close_table(&b, matches); + } + + if (ubus_client_call("uci", "get", b.head, get_sect_callback, (void*)res) != 0) { + dmlog_error("dm_uci_get_section_list, ubus_client_call: %s", config); + ret = -1; + } + + blob_buf_free(&b); + return ret; +} + +int dm_uci_commit(const char *config) +{ + int ret = 0; + dmlog_debug("dm_uci_commit %s", config); + struct blob_buf b; + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + + // Add config and section to the message + blobmsg_add_string(&b, "config", config); + + // Invoke the UBUS call + if (ubus_client_call("uci", "commit", b.head, NULL, NULL) != 0) { + dmlog_error("dm_uci_commit failed"); + ret = -1; + } + blob_buf_free(&b); + return ret; +} diff --git a/dm-framework/dm-api/src/utils/dm_uci.h b/dm-framework/dm-api/src/utils/dm_uci.h new file mode 100644 index 000000000..244b06be0 --- /dev/null +++ b/dm-framework/dm-api/src/utils/dm_uci.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef _DM_UCI_H_ +#define _DM_UCI_H_ + +#include +#include +#include + +typedef struct { + const char *name; + const char *value; +} name_val_t; + +int dm_uci_get(const char *uci_path, char **value); +int dm_uci_set(const char *uci_path, const char *value); +int dm_uci_add(const char *config, const char *type, const char *section_name, name_val_t *opt_values, int value_cnt); +int dm_uci_del(const char *pkg, const char *section); +int dm_uci_get_section_list(const char *config, const char *type, name_val_t *match, int match_cnt, json_object **res); +int dm_uci_commit(const char *config); +#endif \ No newline at end of file diff --git a/dm-framework/dm-api/src/utils/ubus_client.c b/dm-framework/dm-api/src/utils/ubus_client.c new file mode 100644 index 000000000..f2ba5f208 --- /dev/null +++ b/dm-framework/dm-api/src/utils/ubus_client.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#include "ubus_client.h" +#include "dm_log.h" + +#define DEFAULT_UBUS_TIMEOUT 10000 // in ms + +static struct ubus_context *ubus_ctx_g = NULL; + +void ubus_client_init() +{ + ubus_ctx_g = ubus_connect(NULL); +} + +int ubus_client_call(const char *path, const char *method, struct blob_attr *msg, + ubus_data_handler_t cb, void *priv) +{ + uint32_t id; + if (ubus_lookup_id(ubus_ctx_g, path, &id)) { + // dmlog_error("ubus_lookup_id failed, object:%s", path); + return -1; + } + + int ret = ubus_invoke(ubus_ctx_g, id, method, msg, cb, priv, DEFAULT_UBUS_TIMEOUT); + if (ret != 0) { + dmlog_error("ubus_invoke failed: object:%s, method:%s, %d", path, method, ret); + if (msg) { + char *json_str = blobmsg_format_json(msg, true); + dmlog_error("ubus_invoke mesg: %s", json_str); + free(json_str); + } + return -1; + } + + return 0; +} diff --git a/dm-framework/dm-api/src/utils/ubus_client.h b/dm-framework/dm-api/src/utils/ubus_client.h new file mode 100644 index 000000000..a2dd9b055 --- /dev/null +++ b/dm-framework/dm-api/src/utils/ubus_client.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef UBUS_CLIENT_H +#define UBUS_CLIENT_H +#include +#include +#include + +void ubus_client_init(); +int ubus_client_call(const char *path, const char *method, struct blob_attr *msg, + ubus_data_handler_t cb, void *priv); +#endif \ No newline at end of file diff --git a/dm-framework/dm-api/src/utils/utils.c b/dm-framework/dm-api/src/utils/utils.c new file mode 100644 index 000000000..76ff63a43 --- /dev/null +++ b/dm-framework/dm-api/src/utils/utils.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "dm_log.h" + +int copy_file(const char *source, const char *dest) +{ + int source_fd = open(source, O_RDONLY, 0); + + if (source_fd < 0) { + dmlog_error("%s, failed to open %s", __FUNCTION__, source); + return -1; + } + + struct stat stat_source; + + if (fstat(source_fd, &stat_source) < 0) { + close(source_fd); + dmlog_error("%s, failed to fstat %s", __FUNCTION__, source); + return -1; + } + + int dest_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644); + + if (dest_fd < 0) { + close(source_fd); + dmlog_error("%s, failed to open %s", __FUNCTION__, dest); + return -1; + } + + ssize_t ret = sendfile(dest_fd, source_fd, 0, stat_source.st_size); + + if (ret != stat_source.st_size) { + close(source_fd); + close(dest_fd); + remove(dest); + dmlog_error("%s, failed to sendfile from %s to %s", __FUNCTION__, source, dest); + return -1; + } + + if (0 != fsync(dest_fd)) { + dmlog_error("%s, fsync fails", __FUNCTION__); + close(source_fd); + close(dest_fd); + remove(dest); + return -1; + } + + sync(); + + close(source_fd); + close(dest_fd); + return 0; +} + +unsigned int get_uptime_msecs(void) +{ + time_t t; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + t = ((time_t)(ts.tv_sec * 1000) + (time_t)(ts.tv_nsec / 1000000)); + return (uint32_t)t; +} \ No newline at end of file diff --git a/dm-framework/dm-api/src/utils/utils.h b/dm-framework/dm-api/src/utils/utils.h new file mode 100644 index 000000000..60da9cb08 --- /dev/null +++ b/dm-framework/dm-api/src/utils/utils.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Genexis B.V. All rights reserved. + * + * This Software and its content are protected by the Dutch Copyright Act + * ('Auteurswet'). All and any copying and distribution of the software + * and its content without authorization by Genexis B.V. is + * prohibited. The prohibition includes every form of reproduction and + * distribution. + * + */ + +#ifndef UTILS_H +#define UTILS_H + +int copy_file(const char *source, const char *dest); +unsigned int get_uptime_msecs(void); +#endif \ No newline at end of file diff --git a/quickjs/Makefile b/quickjs/Makefile index ea4f7217a..09afc806f 100644 --- a/quickjs/Makefile +++ b/quickjs/Makefile @@ -15,6 +15,8 @@ PKG_BUILD_PARALLEL:=1 include $(INCLUDE_DIR)/package.mk +TARGET_CFLAGS += -fPIC + define Package/quickjs SECTION:=lang CATEGORY:=Languages