diff --git a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql new file mode 100644 index 0000000000..a182bc8117 --- /dev/null +++ b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql @@ -0,0 +1,200 @@ +-- +-- Copyright © 2016-2021 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE OR REPLACE PROCEDURE update_profile_bootstrap() + LANGUAGE plpgsql AS +$$ + +BEGIN + + UPDATE device_profile + SET profile_data = jsonb_set( + profile_data, + '{transportConfiguration}', + get_bootstrap( + profile_data::jsonb #> '{transportConfiguration}', + subquery.publickey_bs, + subquery.publickey_lw), + true) + FROM ( + SELECT id, + encode( + decode(profile_data::json #> '{transportConfiguration,bootstrap,bootstrapServer}' ->> + 'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_bs, + encode( + decode(profile_data::json #> '{transportConfiguration,bootstrap,lwm2mServer}' ->> + 'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_lw + FROM device_profile + WHERE transport_type = 'LWM2M' + ) AS subquery + WHERE device_profile.id = subquery.id + AND subquery.publickey_bs IS NOT NULL + AND subquery.publickey_lw IS NOT NULL; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_bootstrap(transport_configuration_in jsonb, publickey_bs text, + publickey_lw text) RETURNS jsonb AS +$$ + +DECLARE + bootstrap_new jsonb; + bootstrap_in jsonb; + +BEGIN + bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}'; + bootstrap_new := json_build_array( + json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId', + 'securityMode', bootstrap_in::json #> '{bootstrapServer}' ->> 'securityMode', + 'binding', bootstrap_in::json #> '{servers}' ->> 'binding', + 'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', + 'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', + 'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod', + 'host', bootstrap_in::json #> '{bootstrapServer}' ->> 'host', + 'port', bootstrap_in::json #> '{bootstrapServer}' -> 'port', + 'serverPublicKey', publickey_bs, + 'bootstrapServerIs', true, + 'clientHoldOffTime', bootstrap_in::json #> '{bootstrapServer}' -> 'clientHoldOffTime', + 'bootstrapServerAccountTimeout', + bootstrap_in::json #> '{bootstrapServer}' -> 'bootstrapServerAccountTimeout' + ), + json_build_object('shortServerId', bootstrap_in::json #> '{lwm2mServer}' -> 'serverId', + 'securityMode', bootstrap_in::json #> '{lwm2mServer}' ->> 'securityMode', + 'binding', bootstrap_in::json #> '{servers}' ->> 'binding', + 'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', + 'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', + 'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod', + 'host', bootstrap_in::json #> '{lwm2mServer}' ->> 'host', + 'port', bootstrap_in::json #> '{lwm2mServer}' -> 'port', + 'serverPublicKey', publickey_lw, + 'bootstrapServerIs', false, + 'clientHoldOffTime', bootstrap_in::json #> '{lwm2mServer}' -> 'clientHoldOffTime', + 'bootstrapServerAccountTimeout', + bootstrap_in::json #> '{lwm2mServer}' -> 'bootstrapServerAccountTimeout' + ) + ); + RETURN jsonb_set( + transport_configuration_in, + '{bootstrap}', + bootstrap_new, + true) || '{"bootstrapServerUpdateEnable": true}'; + +END ; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE PROCEDURE update_device_credentials_to_base64_and_bootstrap() + LANGUAGE plpgsql AS +$$ + +BEGIN + +UPDATE device_credentials +SET credentials_value = get_device_and_bootstrap(credentials_value::text) +WHERE credentials_type = 'LWM2M_CREDENTIALS'; +END; +$$; + +CREATE OR REPLACE FUNCTION get_device_and_bootstrap(IN credentials_value text, OUT credentials_value_new text) + LANGUAGE plpgsql AS +$$ +DECLARE +client_secret_key text; + client_public_key_or_id text; + client_key_value_object jsonb; + client_bootstrap_server_value_object jsonb; + client_bootstrap_server_object jsonb; + client_bootstrap_object jsonb; + +BEGIN + credentials_value_new := credentials_value; + IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'RPK' AND + NULLIF((credentials_value::jsonb #> '{client}' ->> 'key' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN + client_public_key_or_id := encode(decode(credentials_value::jsonb #> '{client}' ->> 'key', 'hex')::bytea, 'base64'); + client_key_value_object := json_build_object( + 'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint', + 'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode', + 'key', client_public_key_or_id); + credentials_value_new := + credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; +END IF; + IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'X509' AND + NULLIF((credentials_value::jsonb #> '{client}' ->> 'cert' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN + client_public_key_or_id := + encode(decode(credentials_value::jsonb #> '{client}' ->> 'cert', 'hex')::bytea, 'base64'); + client_key_value_object := json_build_object( + 'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint', + 'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode', + 'cert', client_public_key_or_id); + credentials_value_new := + credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; +END IF; + + IF credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'RPK' OR + credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'X509' THEN + IF NULLIF((credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, + 'false') = 'true' AND + NULLIF( + (credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text, + 'false') = 'true' THEN + client_secret_key := + encode(decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey', 'hex')::bytea, + 'base64'); + client_public_key_or_id := encode( + decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea, + 'base64'); + client_bootstrap_server_value_object := jsonb_build_object( + 'securityMode', credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode', + 'clientPublicKeyOrId', client_public_key_or_id, + 'clientSecretKey', client_secret_key + ); + client_bootstrap_server_object := jsonb_build_object('lwm2mServer', client_bootstrap_server_value_object::jsonb); + client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; + credentials_value_new := + jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; +END IF; +END IF; + + IF credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'RPK' OR + credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'X509' THEN + IF NULLIF( + (credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, + 'false') = 'true' AND + NULLIF( + (credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text, + 'false') = 'true' THEN + client_secret_key := + encode( + decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey', 'hex')::bytea, + 'base64'); + client_public_key_or_id := encode( + decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea, + 'base64'); + client_bootstrap_server_value_object := jsonb_build_object( + 'securityMode', credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode', + 'clientPublicKeyOrId', client_public_key_or_id, + 'clientSecretKey', client_secret_key + ); + client_bootstrap_server_object := + jsonb_build_object('bootstrapServer', client_bootstrap_server_value_object::jsonb); + client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; + credentials_value_new := + jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; +END IF; +END IF; + +END; +$$; \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index a6ebaa4c99..c077d5aef5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -126,7 +126,6 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.EdgeLicenseService; import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; -import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; @@ -261,9 +260,6 @@ public abstract class BaseController { @Autowired protected TbDeviceProfileCache deviceProfileCache; - @Autowired - protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository; - @Autowired(required = false) protected EdgeService edgeService; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index c1c8743101..5aacfbb5ce 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -219,7 +219,6 @@ public class DeviceProfileController extends BaseController { isSoftwareChanged = true; } } - DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index 35131de2f7..424e7d04ff 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -20,6 +20,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -29,10 +30,10 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.lwm2m.LwM2MService; import java.util.Map; @@ -41,15 +42,17 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CU @Slf4j @RestController -@TbCoreComponent +@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'") @RequestMapping("/api") public class Lwm2mController extends BaseController { @Autowired private DeviceController deviceController; - public static final String IS_BOOTSTRAP_SERVER = "isBootstrapServer"; + @Autowired + private LwM2MService lwM2MService; + public static final String IS_BOOTSTRAP_SERVER = "isBootstrapServer"; @ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)", notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " + @@ -58,14 +61,14 @@ public class Lwm2mController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET) @ResponseBody - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo( - @ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION) - @PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException { - try { - return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer); - } catch (Exception e) { - throw handleException(e); - } + public LwM2MServerSecurityConfigDefault getLwm2mBootstrapSecurityInfo( + @ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION) + @PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException { + try { + return lwM2MService.getServerSecurityInfo(bootstrapServer); + } catch (Exception e) { + throw handleException(e); + } } @ApiOperation(hidden = true, value = "Save device with credentials (Deprecated)") diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index 353451df3b..f10b44d313 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -185,9 +185,9 @@ public class DeviceBulkImportService extends AbstractBulkImportService { setValues(client, fields, Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE, BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_IDENTITY, BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT)); - LwM2MClientCredentials lwM2MClientCredentials = JacksonUtil.treeToValue(client, LwM2MClientCredentials.class); + LwM2MClientCredential lwM2MClientCredential = JacksonUtil.treeToValue(client, LwM2MClientCredential.class); // so that only fields needed for specific type of lwM2MClientCredentials were saved in json - lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredentials)); + lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredential)); ObjectNode bootstrapServer = JacksonUtil.newObjectNode(); setValues(bootstrapServer, fields, Set.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 27e8375dc8..eef29026ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -475,6 +475,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.info("Updating schema ..."); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, conn); + log.info("Updating server`s public key from HexDec to Base64 in profile for LWM2M..."); + conn.createStatement().execute("call update_profile_bootstrap();"); + log.info("Server`s public key from HexDec to Base64 in profile for LWM2M updated."); + log.info("Updating client`s public key and secret key from HexDec to Base64 for LWM2M..."); + conn.createStatement().execute("call update_device_credentials_to_base64_and_bootstrap();"); + log.info("Client`s public key and secret key from HexDec to Base64 for LWM2M updated."); log.info("Updating schema settings..."); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;"); log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 1456b3f975..9dcb4f05bc 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -57,6 +57,12 @@ public class DefaultCacheCleanupService implements CacheCleanupService { clearCacheByName("tenantProfiles"); clearCacheByName("relations"); break; + case "3.3.2": + log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ..."); + clearCacheByName("devices"); + clearCacheByName("deviceProfiles"); + clearCacheByName("tenantProfiles"); + break; default: //Do nothing, since cache cleanup is optional. } diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java deleted file mode 100644 index fcf68bf13a..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.lwm2m; - - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; -import org.thingsboard.server.common.transport.config.ssl.SslCredentials; -import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; -import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; -import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; - -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") -public class LwM2MServerSecurityInfoRepository { - - private final LwM2MTransportServerConfig serverConfig; - private final LwM2MTransportBootstrapConfig bootstrapConfig; - - public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); - result.setBootstrapServerIs(bootstrapServer); - return result; - } - - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) { - ServerSecurityConfig bsServ = new ServerSecurityConfig(); - bsServ.setServerId(serverConfig.getId()); - bsServ.setHost(serverConfig.getHost()); - bsServ.setPort(serverConfig.getPort()); - bsServ.setSecurityHost(serverConfig.getSecureHost()); - bsServ.setSecurityPort(serverConfig.getSecurePort()); - bsServ.setServerPublicKey(getPublicKey(serverConfig)); - return bsServ; - } - - private String getPublicKey(LwM2MSecureServerConfig config) { - try { - SslCredentials sslCredentials = config.getSslCredentials(); - if (sslCredentials != null) { - return Hex.encodeHexString(sslCredentials.getPublicKey().getEncoded()); - } - } catch (Exception e) { - log.trace("Failed to fetch public key from key store!", e); - - } - return ""; - } - -} - diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java new file mode 100644 index 0000000000..f5724e9ec0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.lwm2m; + +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault; + +public interface LwM2MService { + + LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java new file mode 100644 index 0000000000..b5ec5cc654 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.lwm2m; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; +import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; + +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'") +public class LwM2MServiceImpl implements LwM2MService { + + private final LwM2MTransportServerConfig serverConfig; + private final Optional bootstrapConfig; + + @Override + public LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer) { + LwM2MSecureServerConfig bsServerConfig = bootstrapServer ? bootstrapConfig.orElse(null) : serverConfig; + if (bsServerConfig!= null) { + LwM2MServerSecurityConfigDefault result = getServerSecurityConfig(bsServerConfig); + result.setBootstrapServerIs(bootstrapServer); + return result; + } + else { + return null; + } + } + + private LwM2MServerSecurityConfigDefault getServerSecurityConfig(LwM2MSecureServerConfig bsServerConfig) { + LwM2MServerSecurityConfigDefault bsServ = new LwM2MServerSecurityConfigDefault(); + bsServ.setShortServerId(bsServerConfig.getId()); + bsServ.setHost(bsServerConfig.getHost()); + bsServ.setPort(bsServerConfig.getPort()); + bsServ.setSecurityHost(bsServerConfig.getSecureHost()); + bsServ.setSecurityPort(bsServerConfig.getSecurePort()); + byte[] publicKeyBase64 = getPublicKey(bsServerConfig); + if (publicKeyBase64 == null) { + bsServ.setServerPublicKey(""); + } else { + bsServ.setServerPublicKey(Base64.encodeBase64String(publicKeyBase64)); + } + return bsServ; + } + + private byte[] getPublicKey(LwM2MSecureServerConfig config) { + try { + SslCredentials sslCredentials = config.getSslCredentials(); + if (sslCredentials != null) { + return sslCredentials.getPublicKey().getEncoded(); + } + } catch (Exception e) { + log.trace("Failed to fetch public key from key store!", e); + } + return null; + } +} + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 124462cd5e..a502afb297 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -165,7 +165,7 @@ ui: # Help parameters help: # Base url for UI help assets - base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.2}" + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}" database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records @@ -759,7 +759,7 @@ transport: # Only Certificate_x509: skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: - enable: "${LWM2M_ENABLED_BS:true}" + enabled: "${LWM2M_ENABLED_BS:true}" id: "${LWM2M_SERVER_ID_BS:111}" bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_BS_BIND_PORT:5687}" @@ -818,6 +818,9 @@ transport: downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}" ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}" clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}" + log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}" + psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}" + paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}" # Use redis for Security and Registration stores redis.enabled: "${LWM2M_REDIS_ENABLED:false}" network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 8de16413c3..a97a66cc13 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -30,7 +30,7 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; "org.thingsboard.server.transport.*.claim.sql.*Test", "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.*.credentials.sql.*Test", - "org.thingsboard.server.transport.lwm2m.sql.*Test" + "org.thingsboard.server.transport.lwm2m.*.sql.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java index eccba64ddc..091900488b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java @@ -146,7 +146,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); - String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java index 14d72297a4..671741c7d0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java @@ -145,7 +145,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); - String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index a70d20686e..5d4c32641d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -19,13 +19,10 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.client.object.Security; -import org.eclipse.leshan.core.util.Hex; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.util.SocketUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.Device; @@ -33,13 +30,13 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecServerCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; @@ -61,38 +58,18 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.KeySpec; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import static org.eclipse.leshan.client.object.Security.noSec; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; -import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; @DaoSqlTest public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { - protected final String TRANSPORT_CONFIGURATION = "{\n" + + protected String transportConfiguration = "{\n" + " \"type\": \"LWM2M\",\n" + " \"observeAttr\": {\n" + " \"keyName\": {\n" + @@ -106,178 +83,70 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest " ],\n" + " \"attributeLwm2m\": {}\n" + " },\n" + - " \"bootstrap\": {\n" + - " \"servers\": {\n" + - " \"binding\": \"U\",\n" + - " \"shortId\": 123,\n" + - " \"lifetime\": 300,\n" + - " \"notifIfDisabled\": true,\n" + - " \"defaultMinPeriod\": 1\n" + + " \"bootstrapServerUpdateEnable\": true,\n" + + " \"bootstrap\": [\n" + + " {\n" + + " \"host\": \"0.0.0.0\",\n" + + " \"port\": 5687,\n" + + " \"binding\": \"U\",\n" + + " \"lifetime\": 300,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"shortServerId\": 111,\n" + + " \"notifIfDisabled\": true,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"defaultMinPeriod\": 1,\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + " },\n" + - " \"lwm2mServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5686,\n" + - " \"serverId\": 123,\n" + - " \"serverPublicKey\": \"\",\n" + - " \"bootstrapServerIs\": false,\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + - " },\n" + - " \"bootstrapServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5687,\n" + - " \"serverId\": 111,\n" + - " \"securityMode\": \"NO_SEC\",\n" + - " \"serverPublicKey\": \"\",\n" + - " \"bootstrapServerIs\": true,\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + + " {\n" + + " \"host\": \"0.0.0.0\",\n" + + " \"port\": 5685,\n" + + " \"binding\": \"U\",\n" + + " \"lifetime\": 300,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"shortServerId\": 123,\n" + + " \"notifIfDisabled\": true,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"defaultMinPeriod\": 1,\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + " }\n" + - " },\n" + + " ],\n" + " \"clientLwM2mSettings\": {\n" + - " \"clientOnlyObserveAfterConnect\": 1,\n" + + " \"edrxCycle\": null,\n" + + " \"powerMode\": \"DRX\",\n" + + " \"fwUpdateResource\": null,\n" + " \"fwUpdateStrategy\": 1,\n" + - " \"swUpdateStrategy\": 1\n" + + " \"psmActivityTimer\": null,\n" + + " \"swUpdateResource\": null,\n" + + " \"swUpdateStrategy\": 1,\n" + + " \"pagingTransmissionWindow\": null,\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + " }\n" + "}"; protected DeviceProfile deviceProfile; protected ScheduledExecutorService executor; protected TbTestWebSocketClient wsClient; - - protected final PublicKey clientPublicKey; // client public key used for RPK - protected final PrivateKey clientPrivateKey; // client private key used for RPK - protected final PublicKey serverPublicKey; // server public key used for RPK - protected final PrivateKey serverPrivateKey; // server private key used for RPK - - // client private key used for X509 - protected final PrivateKey clientPrivateKeyFromCert; - // server private key used for X509 - protected final PrivateKey serverPrivateKeyFromCert; - // client certificate signed by rootCA with a good CN (CN start by leshan_integration_test) - protected final X509Certificate clientX509Cert; - // client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test) - protected final X509Certificate clientX509CertWithBadCN; - // client certificate self-signed with a good CN (CN start by leshan_integration_test) - protected final X509Certificate clientX509CertSelfSigned; - // client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test) - protected final X509Certificate clientX509CertNotTrusted; - // server certificate signed by rootCA - protected final X509Certificate serverX509Cert; - // self-signed server certificate - protected final X509Certificate serverX509CertSelfSigned; - // rootCA used by the server - protected final X509Certificate rootCAX509Cert; - // certificates trustedby the server (should contain rootCA) - protected final Certificate[] trustedCertificates = new Certificate[1]; - - protected static final int SECURE_PORT = 5686; - protected static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); - protected static final String ENDPOINT = "deviceAEndpoint"; - protected static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; - - protected static final int PORT = 5685; - protected static final Security SECURITY = noSec("coap://localhost:" + PORT, 123); - protected static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); - - private final LwM2MBootstrapCredentials defaultBootstrapCredentials; + protected LwM2MTestClient client; + private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials; + private String[] resources; + protected String endpoint; public AbstractLwM2MIntegrationTest() { -// create client credentials - try { - // Get point values - byte[] publicX = Hex - .decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray()); - byte[] publicY = Hex - .decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray()); - byte[] privateS = Hex - .decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray()); - - // Get Elliptic Curve Parameter spec for secp256r1 - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - - // Create key specs - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - - // Get keys - clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - - // Get certificates from key store - char[] clientKeyStorePwd = "client".toCharArray(); - KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) { - clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd); - } - - clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd); - clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client"); - clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn"); - clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed"); - clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted"); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } - - // create server credentials - try { - // Get point values - byte[] publicX = Hex - .decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray()); - byte[] publicY = Hex - .decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray()); - byte[] privateS = Hex - .decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray()); - - // Get Elliptic Curve Parameter spec for secp256r1 - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - - // Create key specs - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - - // Get keys - serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - - // Get certificates from key store - char[] serverKeyStorePwd = "server".toCharArray(); - KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) { - serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd); - } - - serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd); - rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA"); - serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server"); - serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed"); - trustedCertificates[0] = rootCAX509Cert; - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } - - defaultBootstrapCredentials = new LwM2MBootstrapCredentials(); - - NoSecServerCredentials serverCredentials = new NoSecServerCredentials(); - - defaultBootstrapCredentials.setBootstrapServer(serverCredentials); - defaultBootstrapCredentials.setLwm2mServer(serverCredentials); + this.defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials(); + NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); + this.defaultBootstrapCredentials.setBootstrapServer(serverCredentials); + this.defaultBootstrapCredentials.setLwm2mServer(serverCredentials); } - @Before - public void beforeTest() throws Exception { + public void init () throws Exception{ executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled")); loginTenantAdmin(); - String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; - for (String resourceName : resources) { + for (String resourceName : this.resources) { TbResource lwModel = new TbResource(); lwModel.setResourceType(ResourceType.LWM2M_MODEL); lwModel.setTitle(resourceName); @@ -292,9 +161,57 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest wsClient = buildAndConnectWebSocketClient(); } + @Before + public void beforeTest() throws Exception { + this.init(); + } + + @After + public void after() { + wsClient.close(); + clientDestroy(); + executor.shutdownNow(); + } + + public void basicTestConnectionObserveTelemetry(Security security, + LwM2MClientCredential credentials, + NetworkConfig coapConfig, + String endpoint) throws Exception { + createDeviceProfile(transportConfiguration); + Device device = createDevice(credentials); + + SingleEntityFilter sef = new SingleEntityFilter(); + sef.setSingleEntity(device.getId()); + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + + wsClient.send(mapper.writeValueAsString(wrapper)); + wsClient.waitForReply(); + + wsClient.registerWaitForUpdate(); + this.endpoint = endpoint; + createNewClient(security, coapConfig, false); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + } + protected void createDeviceProfile(String transportConfiguration) throws Exception { deviceProfile = new DeviceProfile(); - deviceProfile.setName("LwM2M"); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTenantId(tenantId); @@ -312,7 +229,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest Assert.assertNotNull(deviceProfile); } - protected Device createDevice(LwM2MClientCredentials clientCredentials) throws Exception { + protected Device createDevice(LwM2MClientCredential clientCredentials) throws Exception { Device device = new Device(); device.setName("Device A"); device.setDeviceProfileId(deviceProfile.getId()); @@ -326,7 +243,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); - credentials.setClient(clientCredentials); credentials.setBootstrap(defaultBootstrapCredentials); @@ -335,94 +251,30 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest return device; } - - protected OtaPackageInfo createFirmware() throws Exception { - String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; - - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); - firmwareInfo.setDeviceProfileId(deviceProfile.getId()); - firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle("My firmware"); - firmwareInfo.setVersion("v1.0"); - - OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); - - MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); - - return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); + public NoSecClientCredential createNoSecClientCredentials(String endpoint) { + NoSecClientCredential clientCredentials = new NoSecClientCredential(); + clientCredentials.setEndpoint(endpoint); + return clientCredentials; } - protected OtaPackageInfo createSoftware() throws Exception { - String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; - - OtaPackageInfo swInfo = new OtaPackageInfo(); - swInfo.setDeviceProfileId(deviceProfile.getId()); - swInfo.setType(SOFTWARE); - swInfo.setTitle("My sw"); - swInfo.setVersion("v1.0"); - - OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", swInfo, OtaPackageInfo.class); - - MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); - - return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); + public void setResources(String[] resources) { + this.resources = resources; } - protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { - MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); - postRequest.file(content); - setJwtToken(postRequest); - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; } - @After - public void after() { - executor.shutdownNow(); - wsClient.close(); + public void createNewClient(Security security, NetworkConfig coapConfig, boolean isRpc) throws Exception { + clientDestroy(); + client = new LwM2MTestClient(this.executor, this.endpoint); + int clientPort = SocketUtils.findAvailableTcpPort(); + client.init(security, coapConfig, clientPort, isRpc); } - public void basicTestConnectionObserveTelemetry(Security security, - LwM2MClientCredentials credentials, - NetworkConfig coapConfig, - String endpoint) throws Exception { - LwM2MTestClient client = null; - try { - createDeviceProfile(TRANSPORT_CONFIGURATION); - Device device = createDevice(credentials); - - SingleEntityFilter sef = new SingleEntityFilter(); - sef.setSingleEntity(device.getId()); - LatestValueCmd latestCmd = new LatestValueCmd(); - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); - EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - wsClient.waitForReply(); - - wsClient.registerWaitForUpdate(); - client = new LwM2MTestClient(executor, endpoint); - - client.init(security, coapConfig); - String msg = wsClient.waitForUpdate(); - - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); - Assert.assertEquals(1, update.getCmdId()); - List eData = update.getUpdate(); - Assert.assertNotNull(eData); - Assert.assertEquals(1, eData.size()); - Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); - Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); - var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); - Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); - } finally { - if (client != null) { - client.destroy(); - } + private void clientDestroy() { + if (client != null) { + client.destroy(); } } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java new file mode 100644 index 0000000000..232332e554 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m; + +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; + +import static org.eclipse.leshan.client.object.Security.noSec; + +public class Lwm2mTestHelper { + + // Server + public static final int SECURE_PORT = 5686; + public static final int SECURE_PORT_BS = 5688; + public static final String HOST = "localhost"; + public static final String HOST_BS = "localhost"; + public static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); + public static final String ENDPOINT_SECURITY = "deviceAEndpoint"; + public static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; + + public static final int PORT = 5685; + public static final int PORT_BS = 5687; + public static final int SHORT_SERVER_ID = 123; + public static final int SHORT_SERVER_ID_BS = 111; + + public static final Security SECURITY = noSec("coap://localhost:" + PORT, SHORT_SERVER_ID); + public static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); + + // Models + public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"}; + public static final int BINARY_APP_DATA_CONTAINER = 19; + public static final int TEMPERATURE_SENSOR = 3303; + + // Ids in Client + public static final int objectId_0 = 0; + public static final int objectInstanceId_0 = 0; + public static final int objectInstanceId_1 = 1; + public static final int objectInstanceId_2 = 2; + public static final int objectInstanceId_12 = 12; + public static final int resourceId_0 = 0; + public static final int resourceId_1 = 1; + public static final int resourceId_2 = 2; + public static final int resourceId_3 = 3; + public static final int resourceId_4 = 4; + public static final int resourceId_8 = 8; + public static final int resourceId_9 = 9; + public static final int resourceId_11 = 11; + public static final int resourceId_14 = 14; + public static final int resourceId_15= 15; + public static final int resourceInstanceId_2 = 2; + + public static final String resourceIdName_3_9 = "batteryLevel"; + public static final String resourceIdName_3_14 = "UtfOffset"; + public static final String resourceIdName_19_0_0 = "dataRead"; + public static final String resourceIdName_19_1_0 = "dataWrite"; + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java new file mode 100644 index 0000000000..67d1dc5749 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.client; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +public class LwM2MLocationParams { + + private Float latitude; + private Float longitude; + private Float scaleFactor = 1.0F; + private final String locationPos = "50.4501:30.5234"; + private final Float locationScaleFactor = 1.0F; + + + protected void getPos() { + this.latitude = null; + this.longitude = null; + int c = locationPos.indexOf(':'); + this.latitude = Float.valueOf(locationPos.substring(0, c)); + this.longitude = Float.valueOf(locationPos.substring(c + 1)); + } + + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 178b9f473e..1c62b2b79d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -31,7 +31,6 @@ import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.servers.ServerIdentity; -import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.model.InvalidDDFFileException; @@ -39,12 +38,13 @@ import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.junit.Assert; import java.io.IOException; @@ -53,11 +53,20 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; +import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; +import static org.eclipse.leshan.core.LwM2mId.LOCATION; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; + @Slf4j @Data @@ -66,24 +75,36 @@ public class LwM2MTestClient { private final ScheduledExecutorService executor; private final String endpoint; private LeshanClient client; + + private Server lwm2mServer; + private SimpleLwM2MDevice lwM2MDevice; private FwLwM2MDevice fwLwM2MDevice; private SwLwM2MDevice swLwM2MDevice; + private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer; + private LwM2MLocationParams locationParams; + private LwM2mTemperatureSensor lwM2MTemperatureSensor; - public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException { + public void init(Security security, NetworkConfig coapConfig, int port, boolean isRpc) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", client); - String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; List models = new ArrayList<>(); for (String resourceName : resources) { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); } + LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); initializer.setInstancesForObject(SECURITY, security); - initializer.setInstancesForObject(SERVER, new Server(123, 300)); - initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); + initializer.setInstancesForObject(SERVER, lwm2mServer = new Server(123, 300)); + initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice()); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice()); - initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); + initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); + initializer.setInstancesForObject(BINARY_APP_DATA_CONTAINER, lwM2MBinaryAppDataContainer = new LwM2mBinaryAppDataContainer(executor, objectInstanceId_0), + new LwM2mBinaryAppDataContainer(executor, objectInstanceId_1)); + locationParams = new LwM2MLocationParams(); + locationParams.getPos(); + initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, objectInstanceId_0)); + initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2MTemperatureSensor = new LwM2mTemperatureSensor(executor, objectInstanceId_0), new LwM2mTemperatureSensor(executor, objectInstanceId_12)); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setRecommendedCipherSuitesOnly(true); @@ -127,15 +148,16 @@ public class LwM2MTestClient { LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); - builder.setLocalAddress("0.0.0.0", 11000); + builder.setLocalAddress("0.0.0.0", port); builder.setObjects(initializer.createAll()); builder.setCoapConfig(coapConfig); builder.setDtlsConfig(dtlsConfig); builder.setRegistrationEngineFactory(engineFactory); builder.setEndpointFactory(endpointFactory); builder.setSharedExecutor(executor); - builder.setDecoder(new DefaultLwM2mNodeDecoder(true)); - builder.setEncoder(new DefaultLwM2mNodeEncoder(true)); + builder.setDecoder(new DefaultLwM2mDecoder(false)); + + builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false)); client = builder.build(); LwM2mClientObserver observer = new LwM2mClientObserver() { @@ -227,18 +249,39 @@ public class LwM2MTestClient { } }; this.client.addObserver(observer); - - client.start(); + if (!isRpc) { + client.start(); + } } public void destroy() { - client.destroy(true); + if (client != null) { + client.destroy(true); + } + if (lwm2mServer != null) { + lwm2mServer = null; + } + if (lwM2MDevice != null) { + lwM2MDevice.destroy(); + } if (fwLwM2MDevice != null) { fwLwM2MDevice.destroy(); } if (swLwM2MDevice != null) { swLwM2MDevice.destroy(); } + if (lwM2MBinaryAppDataContainer != null) { + lwM2MBinaryAppDataContainer.destroy(); + } + if (lwM2MTemperatureSensor != null) { + lwM2MTemperatureSensor.destroy(); + } + } + + public void start() { + if (client != null) { + client.start(); + } } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java new file mode 100644 index 0000000000..b7ac889649 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -0,0 +1,231 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +import javax.security.auth.Destroyable; +import java.sql.Time; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + + +@Slf4j +public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable { + + /** + * id = 0 + * Multiple + * base64 + */ + + /** + * Example1: + * InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka + * W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo= + * "serviceId":"Meter", + * "serviceData":{ + * "currentReading":"46.3", + * "signalStrength":16, + * "dailyActivityTime":5706 + */ + + /** + * Example2: + * InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX + * 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K + * "serviceId":"WaterMeter", + * "cmd":"SET_TEMPERATURE_READ_PERIOD", + * "paras":{ + * "value":4 + * }, + */ +// private String data = "InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFkaW5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo="; +// private byte[] data; + Map data; + private Integer priority = 0; + private Time timestamp; + private String description; + private String dataFormat; + private Integer appID = -1; + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5); + + public LwM2mBinaryAppDataContainer() { + } + + public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + executorService.scheduleWithFixedDelay(() -> +// fireResourcesChange(0, 2), 5000, 5000, TimeUnit.MILLISECONDS); + fireResourcesChange(0, 2), 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public ReadResponse read(ServerIdentity identity, int resourceId) { +// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + try { + switch (resourceId) { + case 0: +// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE); +// log.warn("Response [{}]", response); + return response; + + case 1: + return ReadResponse.success(resourceId, getPriority()); + case 2: + return ReadResponse.success(resourceId, getTimestamp()); + case 3: + return ReadResponse.success(resourceId, getDescription()); + case 4: + return ReadResponse.success(resourceId, getDataFormat()); + case 5: + return ReadResponse.success(resourceId, getAppID()); + default: + return super.read(identity, resourceId); + } + } catch (Exception e) { + return ReadResponse.badRequest(e.getMessage()); + } + } + + @Override + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 0: + if (setData(value, replace)) { + return WriteResponse.success(); + } else { + WriteResponse.badRequest("Invalidate value ..."); + } + case 1: + setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); + fireResourcesChange(resourceId); + return WriteResponse.success(); + case 2: + setTimestamp(((Date) value.getValue()).getTime()); + fireResourcesChange(resourceId); + return WriteResponse.success(); + case 3: + setDescription((String) value.getValue()); + fireResourcesChange(resourceId); + return WriteResponse.success(); + case 4: + setDataFormat((String) value.getValue()); + fireResourcesChange(resourceId); + return WriteResponse.success(); + case 5: + setAppID((Integer) value.getValue()); + fireResourcesChange(resourceId); + return WriteResponse.success(); + default: + return super.write(identity, replace, resourceId, value); + } + } + + private Integer getAppID() { + return this.appID; + } + + private void setAppID(Integer appId) { + this.appID = appId; + } + + private void setDataFormat(String value) { + this.dataFormat = value; + } + + private String getDataFormat() { +// return this.dataFormat == null ? "base64" : this.dataFormat; + return this.dataFormat == null ? "OPAQUE" : this.dataFormat; + } + + private void setDescription(String value) { + this.description = value; + } + + private String getDescription() { + return this.description == null ? "meter reading" : this.description; + } + + private void setTimestamp(long time) { + this.timestamp = new Time(time); + } + + private Time getTimestamp() { + return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); + } + +// fireResourcesChange(resourceId); + private boolean setData(LwM2mResource value, boolean replace) { + try { + if (value instanceof LwM2mMultipleResource) { + if (replace || this.data == null) { + this.data = new HashMap<>(); + } + value.getInstances().values().forEach(v -> { + this.data.put(v.getId(), (byte[]) v.getValue()); + }); + return true; + } else { + return false; + } + } catch (Exception e) { + return false; + } + } + + private Map getData() { +// this.data.put(23, new byte[]{0,0, 2,3}); + return data; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } + + private int getPriority() { + return this.priority; + } + + private void setPriority(int value) { + this.priority = value; + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java new file mode 100644 index 0000000000..079cd9fdac --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java @@ -0,0 +1,150 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.response.ReadResponse; + +import javax.security.auth.Destroyable; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable { + + private float latitude; + private float longitude; + private float scaleFactor; + private Date timestamp; + protected static final Random RANDOM = new Random(); + private static final List supportedResources = Arrays.asList(0, 1, 5); + + public LwM2mLocation() { + this(null, null, 1.0f); + } + + public LwM2mLocation(Float latitude, Float longitude, float scaleFactor) { + + if (latitude != null) { + this.latitude = latitude + 90f; + } else { + this.latitude = RANDOM.nextInt(180); + } + if (longitude != null) { + this.longitude = longitude + 180f; + } else { + this.longitude = RANDOM.nextInt(360); + } + this.scaleFactor = scaleFactor; + timestamp = new Date(); + } + + public LwM2mLocation(Float latitude, Float longitude, float scaleFactor, ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + if (latitude != null) { + this.latitude = latitude + 90f; + } else { + this.latitude = RANDOM.nextInt(180); + } + if (longitude != null) { + this.longitude = longitude + 180f; + } else { + this.longitude = RANDOM.nextInt(360); + } + this.scaleFactor = scaleFactor; + timestamp = new Date(); + executorService.scheduleWithFixedDelay(() -> + fireResourcesChange(0, 1), 10000, 10000, TimeUnit.MILLISECONDS); + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public ReadResponse read(ServerIdentity identity, int resourceId) { + log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 0: + return ReadResponse.success(resourceId, getLatitude()); + case 1: + return ReadResponse.success(resourceId, getLongitude()); + case 5: + return ReadResponse.success(resourceId, getTimestamp()); + default: + return super.read(identity, resourceId); + } + } + + public void moveLocation(String nextMove) { + switch (nextMove.charAt(0)) { + case 'w': + moveLatitude(1.0f); +// log.info("Move to North [{}]/[{}]", getLatitude(), getLongitude()); + break; + case 'a': + moveLongitude(-1.0f); +// log.info("Move to East [{}]/[{}]", getLatitude(), getLongitude()); + break; + case 's': + moveLatitude(-1.0f); +// log.info("Move to South [{}]/[{}]", getLatitude(), getLongitude()); + break; + case 'd': + moveLongitude(1.0f); +// log.info("Move to West [{}]/[{}]", getLatitude(), getLongitude()); + break; + } + } + + private void moveLatitude(float delta) { + latitude = latitude + delta * scaleFactor; + timestamp = new Date(); + fireResourcesChange(0, 5); + } + + private void moveLongitude(float delta) { + longitude = longitude + delta * scaleFactor; + timestamp = new Date(); + fireResourcesChange(1, 5); + } + + public float getLatitude() { + return latitude - 90.0f; + } + + public float getLongitude() { + return longitude - 180.f; + } + + public Date getTimestamp() { + return timestamp; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java new file mode 100644 index 0000000000..2e52cfc812 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -0,0 +1,131 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; + +import javax.security.auth.Destroyable; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable { + + private static final String UNIT_CELSIUS = "cel"; + private double currentTemp = 20d; + private double minMeasuredValue = currentTemp; + private double maxMeasuredValue = currentTemp; + protected static final Random RANDOM = new Random(); + private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); + + public LwM2mTemperatureSensor() { + + } + + public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS); + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public synchronized ReadResponse read(ServerIdentity identity, int resourceId) { + log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 5601: + return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); + case 5602: + return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); + case 5700: + return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + case 5701: + return ReadResponse.success(resourceId, UNIT_CELSIUS); + default: + return super.read(identity, resourceId); + } + } + + @Override + public synchronized ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { + log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 5605: + resetMinMaxMeasuredValues(); + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceId, params); + } + } + + private double getTwoDigitValue(double value) { + BigDecimal toBeTruncated = BigDecimal.valueOf(value); + return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + private void adjustTemperature() { + float delta = (RANDOM.nextInt(20) - 10) / 10f; + currentTemp += delta; + Integer changedResource = adjustMinMaxMeasuredValue(currentTemp); + if (changedResource != null) { + fireResourcesChange(5700, changedResource); + } else { + fireResourcesChange(5700); + } + } + + private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) { + if (newTemperature > maxMeasuredValue) { + maxMeasuredValue = newTemperature; + return 5602; + } else if (newTemperature < minMeasuredValue) { + minMeasuredValue = newTemperature; + return 5601; + } else { + return null; + } + } + + private void resetMinMaxMeasuredValues() { + minMeasuredValue = currentTemp; + maxMeasuredValue = currentTemp; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } + + + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index 9ede9da80e..8d5c46a1a0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -40,9 +40,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final Random RANDOM = new Random(); - private static final List supportedResources = Arrays.asList(0, 1, 2, 3 -// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21 - ); + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); @Override public ReadResponse read(ServerIdentity identity, int resourceId) { @@ -117,7 +115,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } private String getManufacturer() { - return "Leshan Demo Device"; + return "Thingsboard Demo Lwm2mDevice"; } private String getModelNumber() { @@ -125,11 +123,11 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } private String getSerialNumber() { - return "LT-500-000-0001"; + return "Thingsboard-500-000-0001"; } private String getFirmwareVersion() { - return "1.0.0"; + return "1.0.2"; } private long getErrorCode() { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java index b06a3ebbd3..4b1e02a68d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.client; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.servers.ServerIdentity; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java new file mode 100644 index 0000000000..e78d74bdcd --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.ota; + +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; + +@DaoSqlTest +public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + private final String[] resources = new String[]{"3.xml", "5.xml", "9.xml"}; + + public AbstractOtaLwM2MIntegrationTest() { + setResources(this.resources); + } + + protected OtaPackageInfo createFirmware() throws Exception { + String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; + + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); + firmwareInfo.setDeviceProfileId(deviceProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("My firmware"); + firmwareInfo.setVersion("v1.0"); + + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + + MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); + + return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); + } + + protected OtaPackageInfo createSoftware() throws Exception { + String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; + + OtaPackageInfo swInfo = new OtaPackageInfo(); + swInfo.setDeviceProfileId(deviceProfile.getId()); + swInfo.setType(SOFTWARE); + swInfo.setTitle("My sw"); + swInfo.setVersion("v1.0"); + + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", swInfo, OtaPackageInfo.class); + + MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); + + return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); + } + + protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); + postRequest.file(content); + setJwtToken(postRequest); + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java similarity index 53% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/NoSecLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 168e3cbdec..903750663a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.sql; +package org.thingsboard.server.transport.lwm2m.ota.sql; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; -import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; -import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; +import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest; import java.util.Arrays; import java.util.Collections; @@ -48,12 +46,15 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEU import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; @Slf4j -public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { +public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { public static final int TIMEOUT = 30; private final String OTA_TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + " \"observeAttr\": {\n" + " \"keyName\": {\n" + " \"/5_1.0/0/3\": \"state\",\n" + @@ -91,126 +92,109 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { " ],\n" + " \"attributeLwm2m\": {}\n" + " },\n" + - " \"bootstrap\": {\n" + - " \"servers\": {\n" + - " \"binding\": \"UQ\",\n" + - " \"shortId\": 123,\n" + - " \"lifetime\": 300,\n" + - " \"notifIfDisabled\": true,\n" + - " \"defaultMinPeriod\": 1\n" + + " \"bootstrapServerUpdateEnable\": true,\n" + + " \"bootstrap\": [\n" + + " {\n" + + " \"host\": \"0.0.0.0\",\n" + + " \"port\": 5687,\n" + + " \"binding\": \"U\",\n" + + " \"lifetime\": 300,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"shortServerId\": 111,\n" + + " \"notifIfDisabled\": true,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"defaultMinPeriod\": 1,\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + " },\n" + - " \"lwm2mServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5685,\n" + - " \"serverId\": 123,\n" + - " \"securityMode\": \"NO_SEC\",\n" + - " \"serverPublicKey\": \"\",\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + - " },\n" + - " \"bootstrapServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5687,\n" + - " \"serverId\": 111,\n" + - " \"securityMode\": \"NO_SEC\",\n" + - " \"serverPublicKey\": \"\",\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + + " {\n" + + " \"host\": \"0.0.0.0\",\n" + + " \"port\": 5685,\n" + + " \"binding\": \"U\",\n" + + " \"lifetime\": 300,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"shortServerId\": 123,\n" + + " \"notifIfDisabled\": true,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"defaultMinPeriod\": 1,\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + " }\n" + - " },\n" + + " ],\n" + " \"clientLwM2mSettings\": {\n" + + " \"edrxCycle\": null,\n" + + " \"powerMode\": \"DRX\",\n" + + " \"fwUpdateResource\": null,\n" + " \"fwUpdateStrategy\": 1,\n" + + " \"psmActivityTimer\": null,\n" + + " \"swUpdateResource\": null,\n" + " \"swUpdateStrategy\": 1,\n" + - " \"clientOnlyObserveAfterConnect\": 1,\n" + - " \"powerMode\": \"PSM\",\n" + - " \"fwUpdateResource\": \"\",\n" + - " \"swUpdateResource\": \"\",\n" + - " \"compositeOperationsSupport\": false\n" + - " },\n" + - " \"type\": \"LWM2M\"\n" + + " \"pagingTransmissionWindow\": null,\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + "}"; - LwM2MTestClient client = null; - - @After - public void tearDown() { - if (client != null) { - client.destroy(); - } - } - - @Test - public void testConnectAndObserveTelemetry() throws Exception { - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(ENDPOINT); - super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT); - } - @Test - public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { - LwM2MTestClient client = null; - try { - createDeviceProfile(TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(ENDPOINT); - Device device = createDevice(clientCredentials); + public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception { + String endpoint = "WithoutFirmwareInfoDevice"; + setEndpoint(endpoint); + createDeviceProfile(transportConfiguration); + NoSecClientCredential credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + createNewClient(SECURITY, COAP_CONFIG, false); - client = new LwM2MTestClient(executor, ENDPOINT); - client.init(SECURITY, COAP_CONFIG); + Thread.sleep(1000); - Thread.sleep(1000); + device.setFirmwareId(createFirmware().getId()); + final Device savedDevice = doPost("/api/device", device, Device.class); - device.setFirmwareId(createFirmware().getId()); - device = doPost("/api/device", device, Device.class); + Thread.sleep(1000); - Thread.sleep(5000); - - List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() { - })); - - List statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - List expectedStatuses = Collections.singletonList(FAILED); + List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + + savedDevice.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() {})); + List statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + List expectedStatuses = Collections.singletonList(FAILED); - Assert.assertEquals(expectedStatuses, statuses); - } finally { - if (client != null) { - client.destroy(); - } - } + Assert.assertEquals(expectedStatuses, statuses); } @Test public void testFirmwareUpdateByObject5() throws Exception { - LwM2MTestClient client = null; - try { - createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint("OTA_" + ENDPOINT); - Device device = createDevice(clientCredentials); - - device.setFirmwareId(createFirmware().getId()); - device = doPost("/api/device", device, Device.class); - Thread.sleep(1000); - - client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); - client.init(SECURITY, COAP_CONFIG); + String endpoint = "Ota5_Device"; + setEndpoint(endpoint); + createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); + NoSecClientCredential credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + createNewClient(SECURITY, COAP_CONFIG, false); - Thread.sleep(3000); + Thread.sleep(1000); - List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { - })); + device.setFirmwareId(createFirmware().getId()); + final Device savedDevice = doPost("/api/device", device, Device.class); - List statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + Thread.sleep(1000); - List expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - Assert.assertEquals(expectedStatuses, statuses); - } finally { - if (client != null) { - client.destroy(); - } - } + final List expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); + List ts = await("await on timeseries") + .atMost(30, TimeUnit.SECONDS) + .until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + + savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + + System.currentTimeMillis(), new TypeReference<>() { + })), hasSize(expectedStatuses.size())); + List statuses = ts.stream().sorted(Comparator + .comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString) + .map(OtaPackageUpdateStatus::valueOf) + .collect(Collectors.toList()); + + Assert.assertEquals(expectedStatuses, statuses); } /** @@ -220,25 +204,25 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { * */ @Test public void testSoftwareUpdateByObject9() throws Exception { - //given - final List expectedStatuses = List.of( - QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); - + String endpoint = "Ota9_Device"; + setEndpoint(endpoint); createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint("OTA_" + ENDPOINT); - final Device device = createDevice(clientCredentials); - device.setSoftwareId(createSoftware().getId()); + NoSecClientCredential credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + createNewClient(SECURITY, COAP_CONFIG, false); + + Thread.sleep(1000); + device.setSoftwareId(createSoftware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); //sync call + + Thread.sleep(1000); + assertThat(savedDevice).as("saved device").isNotNull(); assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - //when - log.warn("Init the client..."); - client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); - client.init(SECURITY, COAP_CONFIG); - + final List expectedStatuses = List.of( + QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); log.warn("AWAIT atMost {} SECONDS on timeseries List by API with list size {}...", TIMEOUT, expectedStatuses.size()); List ts = await("await on timeseries") .atMost(30, TimeUnit.SECONDS) @@ -247,9 +231,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { ts.sort(Comparator.comparingLong(TsKvEntry::getTs)); log.warn("Ts ordered: {}", ts); - ts.forEach((x) -> log.warn("ts: {} ", x)); - - List statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + ts.forEach((x) -> log.warn("ts: { Thread.sleep(1000);} ", x)); + List statuses = ts.stream().map(KvEntry::getValueAsString) + .map(OtaPackageUpdateStatus::valueOf) + .collect(Collectors.toList()); log.warn("Converted ts to statuses: {}", statuses); assertThat(statuses).isEqualTo(expectedStatuses); @@ -257,7 +242,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { private Device getDeviceFromAPI(UUID deviceId) throws Exception { final Device device = doGet("/api/device/" + deviceId, Device.class); - log.warn("Fetched device by API for deviceId {}, device is {}", deviceId, device); + log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device); return device; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java new file mode 100644 index 0000000000..95878b74d1 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -0,0 +1,206 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc; + +import org.junit.Before; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; +import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; +import static org.eclipse.leshan.core.LwM2mId.DEVICE; +import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; +import static org.eclipse.leshan.core.LwM2mId.SERVER; +import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; + +@DaoSqlTest +public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected String RPC_TRANSPORT_CONFIGURATION; + + protected ScheduledExecutorService executor; + protected TbTestWebSocketClient wsClient; + protected String deviceId; + public Set expectedObjects; + public Set expectedObjectIdVers; + public Set expectedInstances; + public Set expectedObjectIdVerInstances; + + protected String objectInstanceIdVer_1; + protected String objectIdVer_0; + protected String objectIdVer_2; + private static final Predicate predicate_3 = path -> (!((String) path).contains("/" + TEMPERATURE_SENSOR) && ((String) path).contains("/" + DEVICE)); + protected String objectIdVer_3; + protected String objectInstanceIdVer_3; + protected String objectInstanceIdVer_5; + protected String objectInstanceIdVer_9; + protected String objectIdVer_19; + protected String objectIdVer_50 = "/50"; + protected String objectIdVer_3303; + protected static AtomicInteger endpointSequence = new AtomicInteger(); + + public AbstractRpcLwM2MIntegrationTest(){ + setResources(resources); + } + + @Before + public void beforeTest() throws Exception { + setEndpoint("deviceEndpointRpc" + endpointSequence.incrementAndGet()); + init(); + createNewClient (SECURITY, COAP_CONFIG, true); + + expectedObjects = ConcurrentHashMap.newKeySet(); + expectedObjectIdVers = ConcurrentHashMap.newKeySet(); + expectedInstances = ConcurrentHashMap.newKeySet(); + expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet(); + client.getClient().getObjectTree().getObjectEnablers().forEach((key, val) -> { + if (key > 0) { + String objectVerId = "/" + key; + if (!val.getObjectModel().version.equals("1.0")) { + objectVerId += ("_" + val.getObjectModel().version); + } + expectedObjects.add("/" + key); + expectedObjectIdVers.add(objectVerId); + String finalObjectVerId = objectVerId; + val.getAvailableInstanceIds().forEach(inststanceId -> { + expectedInstances.add("/" + key + "/" + inststanceId); + expectedObjectIdVerInstances.add(finalObjectVerId + "/" + inststanceId); + }); + } + }); + String ver_Id_0 = client.getClient().getObjectTree().getModel().getObjectModel(objectId_0).version; + if ("1.0".equals(ver_Id_0)) { + objectIdVer_0 = "/" + objectId_0; + } + else { + objectIdVer_0 = "/" + objectId_0 + "_" + ver_Id_0; + } + objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get(); + objectIdVer_3 = (String) expectedObjects.stream().filter(predicate_3).findFirst().get(); + objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); + objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + TEMPERATURE_SENSOR)).findFirst().get(); + objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get(); + objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(predicate_3).findFirst().get(); + objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + FIRMWARE)).findFirst().get(); + objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); + + RPC_TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\": \"" + resourceIdName_3_9 + "\",\n" + + " \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\": \"" + resourceIdName_3_14 + "\",\n" + + " \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_0_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_1_0 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" + + " \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\"\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" + + " \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\",\n" + + " \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrapServerUpdateEnable\": true,\n" + + " \"bootstrap\": [\n" + + " {\n" + + " \"host\": \"0.0.0.0\",\n" + + " \"port\": 5687,\n" + + " \"binding\": \"U\",\n" + + " \"lifetime\": 300,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"shortServerId\": 111,\n" + + " \"notifIfDisabled\": true,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"defaultMinPeriod\": 1,\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " {\n" + + " \"host\": \"0.0.0.0\",\n" + + " \"port\": 5685,\n" + + " \"binding\": \"U\",\n" + + " \"lifetime\": 300,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"shortServerId\": 123,\n" + + " \"notifIfDisabled\": true,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"defaultMinPeriod\": 1,\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " ],\n" + + " \"clientLwM2mSettings\": {\n" + + " \"edrxCycle\": null,\n" + + " \"powerMode\": \"DRX\",\n" + + " \"fwUpdateResource\": null,\n" + + " \"fwUpdateStrategy\": 1,\n" + + " \"psmActivityTimer\": null,\n" + + " \"swUpdateResource\": null,\n" + + " \"swUpdateStrategy\": 1,\n" + + " \"pagingTransmissionWindow\": null,\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + createDeviceProfile(RPC_TRANSPORT_CONFIGURATION); + + NoSecClientCredential credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + deviceId = device.getId().getId().toString(); + + client.start(); + } + + protected String pathIdVerToObjectId(String pathIdVer) { + if (pathIdVer.contains("_")){ + String [] objVer = pathIdVer.split("/"); + objVer[1] = objVer[1].split("_")[0]; + return String.join("/", objVer); + } + return pathIdVer; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java new file mode 100644 index 0000000000..4ab0d68794 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java @@ -0,0 +1,133 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; + + +public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}} + * + * create_2_instances_in_object + * new ObjectInstance if Object is Multiple & Resource Single + * Create {"id":"/19_1.1/12","value":{"0":{"0":"00AC", "1":1}}} + * {"{"result":"CREATED"}"} + */ + @Test + public void testCreateObjectInstanceWithInstanceIdByIdKey_Result_CREATED() throws Exception { + String expectedPath = objectIdVer_19 + "/" + objectInstanceId_12; + String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}, \"1\":1}"; + String actualResult = sendRPCreateById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CREATED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}} + * + * create_2_instances_in_object + * new ObjectInstance if Object is Multiple & Resource Single + * Create {"id":"/19_1.1/0","value":{"0":{"0":"00AC", "1":1}}} + * {"result":"BAD_REQUEST","error":"instance 0 already exists"} + */ + @Test + public void testCreateObjectInstanceWithInstanceIdAlreadyExistsById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0; + String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}, \"1\":1}"; + String actualResult = sendRPCreateById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "instance " + objectInstanceId_0 + " already exists"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * failed: cannot_create_mandatory_single_object + * Create {"id":"/3/1,"value":{"0":"00AC"}} + * {"result":"BAD_REQUEST","error":"Path /3/1. Object must be Multiple !"} + */ + @Test + public void testCreateObjectInstanceWithInstanceIdMandatorySingleObjectById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_3 + "/" + objectInstanceId_1; + String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}}"; + String actualResult = sendRPCreateById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Path " + expectedPath + ". Object must be Multiple !"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * failed: cannot_create_instance_of_security_object + * Create {"id":"/0/2","value":{"2":4}} + * {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"} + */ + @Test + public void testCreateObjectInstanceWithInstanceIdSecurityObjectById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_0 + "/" + objectInstanceId_1; + String expectedValue = "{\"" + resourceId_0 + "\":{\"2\":4}}"; + String actualResult = sendRPCreateById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); + String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * failed: cannot_create_instance_of_absent_object + * Create {"id":"/50/1","value":{"0":"00AC"}} + * {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"} + */ + @Test + public void testCreateObjectInstanceWithInstanceIdAbsentObjectById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_50+ "/" + objectInstanceId_1; + String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}}"; + String actualResult = sendRPCreateById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); + String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + private String sendRPCreateById(String path, String value) throws Exception { + String setRpcRequest = "{\"method\": \"Create\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java new file mode 100644 index 0000000000..6f24d7176c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java @@ -0,0 +1,96 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; + + +public class RpcLwm2mIntegrationDeleteTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * if there is such an instance + * Delete {"id":"/3303/12"} + * {"result":"DELETE"} + */ + @Test + public void testDeleteObjectInstanceIsSuchByIdKey_Result_DELETED() throws Exception { + String expectedPath = objectIdVer_3303 + "/" + objectInstanceId_12; + String actualResult = sendRPCDeleteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.DELETED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * if there is no such instance + * Delete {"id":"/19/12"} + * {"result":"NOT_FOUND"} + */ + @Test + public void testDeleteObjectInstanceIsNotSuchByIdKey_Result_NOT_FOUND() throws Exception { + String expectedPath = objectIdVer_19 + "/" + objectInstanceId_12; + String actualResult = sendRPCDeleteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); + } + + /** + * delete object + * Delete {"id":"/19_1.1"} + * {"result":"BAD_REQUEST","error":"Invalid path /19 : Only object instances can be delete"} + */ + @Test + public void testDeleteObjectByIdKey_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_19; + String actualResult = sendRPCDeleteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Invalid path " + pathIdVerToObjectId((String) expectedPath) + " : Only object instances can be delete"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + + /** + * delete resource + * Delete {"id":"/3/0/9"} + * {"result":"METHOD_NOT_ALLOWED"} + */ + @Test + public void testDeleteResourceByIdKey_Result_METHOD_NOT_ALLOWED() throws Exception { + String expectedPath = objectIdVer_3 + "/" + objectInstanceId_0 + resourceId_9; + String actualResult = sendRPCDeleteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); + } + + + private String sendRPCDeleteById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Delete\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java new file mode 100644 index 0000000000..b8ca7be61f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -0,0 +1,165 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; + + +public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * DiscoverAll + * + * @throws Exception + */ + @Test + public void testDiscoverAll_Return_CONTENT_LinksAllObjectsAllInstancesOfClient() throws Exception { + String setRpcRequest = "{\"method\":\"DiscoverAll\"}"; + String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + JsonNode rpcActualValue = JacksonUtil.toJsonNode(rpcActualResult.get("value").asText()); + Set actualObjects = ConcurrentHashMap.newKeySet(); + Set actualInstances = ConcurrentHashMap.newKeySet(); + rpcActualValue.forEach(node -> { + if (!node.get("url").asText().equals("/")) { + LwM2mPath path = new LwM2mPath(node.get("url").asText()); + actualObjects.add("/" + path.getObjectId()); + if (path.isObjectInstance()) { + actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId()); + } + } + }); + assertEquals(expectedInstances, actualInstances); + assertEquals(expectedObjects, actualObjects); + } + + /** + * Discover {"id":"/3"} + * + * @throws Exception + */ + @Test + public void testDiscoverObject_Return_CONTENT_LinksInstancesAndResourcesOnLyExpectedObject() { + expectedObjectIdVers.forEach(expected -> { + try { + String actualResult = sendDiscover((String) expected); + String expectedObjectId = pathIdVerToObjectId((String) expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String[] actualValues = rpcActualResult.get("value").asText().split(","); + assertTrue(actualValues.length > 0); + assertEquals(0, Arrays.stream(actualValues).filter(path -> !path.contains(expectedObjectId)).collect(Collectors.toList()).size()); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** + * Discover {"id":"3/0"} + * If WriteAttributes not implemented: + * {"result":"CONTENT","value":",,,,,,,,,,,,,,,,,,,,,,,"} + * If WriteAttributes implemented and WriteAttributes saved + * Discover {"id":"19/0"} + * {"result":"CONTENT","value":"[;dim=2;pmin=10;pmax=60;gt=50;lt=42.2,;pmax=120, , , , , ;lt=45]"} + */ + @Test + public void testDiscoverInstance_Return_CONTENT_LinksResourcesOnLyExpectedInstance() throws Exception { + String expected = (String) expectedObjectIdVerInstances.stream().findAny().get(); + String actualResult = sendDiscover(expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedObjectInstanceId = pathIdVerToObjectId(expected); + String[] actualValues = rpcActualResult.get("value").asText().split(","); + assertTrue(actualValues.length > 0); + assertEquals(0, Arrays.stream(actualValues).filter(path -> !path.contains(expectedObjectInstanceId)).collect(Collectors.toList()).size()); + } + + /** + * Discover {"id":"3/0/14"} + * If WriteAttributes implemented: + * {"result":"CONTENT","value":";pmax=100, "pmin":10, "ver"=1.0"} + * If WriteAttributes not implemented: + * {"result":"CONTENT","value":""} + * Discover {"id":"19_1.1/0/0"} + * If WriteAttributes implemented: + * {"result":"CONTENT","value":";pmax=100, "pmin":10, "ver"=1.1"} + * If WriteAttributes not implemented: + * {"result":"CONTENT","value":""} + */ + @Test + public void testDiscoverResource_Return_CONTENT_LinksResourceOnLyExpectedResource() throws Exception { + String expectedInstance = (String) expectedInstances.stream().findFirst().get(); + String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance); + LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId); + int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); + String expected = expectedInstance + "/" + expectedResource; + String actualResult = sendDiscover(expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedResourceId = "<" + expectedObjectInstanceId + "/" + expectedResource + ">"; + String actualValue = rpcActualResult.get("value").asText(); + assertEquals(expectedResourceId, actualValue ); + + } + + /** + * Discover {"id":"2/0"} + *{"result":"NOT_FOUND"} + */ + @Test + public void testDiscoverObjectInstanceAbsentInObject_Return_NOT_FOUND() throws Exception { + String expected = objectIdVer_2 + "/" + objectInstanceId_0; + String actualResult = sendDiscover(expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); + } + /** + * Discover {"id":"2/0/2"} + * {"result":"NOT_FOUND"} + */ + @Test + public void testDiscoverResourceAbsentInObject_Return_NOT_FOUND() throws Exception { + String expected = objectIdVer_2 + "/" + objectInstanceId_0 + "/" + resourceId_2; + String actualResult = sendDiscover(expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); + } + + private String sendDiscover(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java new file mode 100644 index 0000000000..73ea0fb74c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java @@ -0,0 +1,186 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_4; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_8; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; + + +public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationTest { + + + /** + * Update FW + * Execute {"id":"5/0/2"} + * {"result":"CHANGED"} + */ + @Test + public void testExecuteUpdateFWById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_5 + "/" + resourceId_2; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Update SW + * Execute {"id":"9/0/4"} + * {"result":"CHANGED"} + */ + @Test + public void testExecuteUpdateSWById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_9 + "/" + resourceId_4; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Reboot + * Execute {"id":"3/0/4"} + * {"result":"CHANGED"} + */ + @Test + public void testExecuteRebootById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_4; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Registration Update Trigger + * Execute {"id":"1/0/8"} + * {"result":"CHANGED"} + */ + @Test + public void testExecuteRegistrationUpdateTriggerById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_1 + "/" + resourceId_8; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + } + + + /** + * execute_resource_with_parameters (execute reboot after 60 seconds on device) + * Execute {"id":"3/0/4","value":60} + * {"result":"CHANGED"} + */ + @Test + public void testExecuteResourceWithParametersById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_4; + Object expectedValue = 60; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Bootstrap-Request Trigger + * Execute {"id":"1/0/9"} + * {"result":"BAD_REQUEST","error":"probably no bootstrap server configured"} + */ + @Test + public void testExecuteBootstrapRequestTriggerById_Result_BAD_REQUEST_Error_NoBootstrapServerConfigured() throws Exception { + String expectedPath = objectInstanceIdVer_1 + "/" + resourceId_9; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "probably no bootstrap server configured"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * bad: resource operation not "E" + * Execute {"id":"5/0/3"} + * {"result":"BAD_REQUEST","error":"Resource with /5/0/3 is not executable."} + */ + @Test + public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception { + String expectedPath = objectInstanceIdVer_5 + "/" + resourceId_3; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + String expected = "Resource with " + expectedObjectId + " is not executable."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * bad: execute_non_existing_resource_on_non_existing_object + * Execute {"id":"50/0/3"} + * {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"} + */ + @Test + public void testExecuteNonExistingResourceOnNonExistingObjectById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_50 + "/" + objectInstanceId_0 + "/" + resourceId_3; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); + String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * bad: execute security object + * Execute {"id":"0/0/3"} + * {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"} + */ + @Test + public void testExecuteSecurityObjectById_Result_NOT_FOUND() throws Exception { + String expectedPath = objectIdVer_0 + "/" + objectInstanceId_0 + "/" + resourceId_3; + String actualResult = sendRPCExecuteById(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); + String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + + private String sendRPCExecuteById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCExecuteWithValueById(String path, Object value) throws Exception { + String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java new file mode 100644 index 0000000000..848a9563ed --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -0,0 +1,190 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; + +public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * ObserveReadAll + * @throws Exception + */ + @Test + public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception { + String actualResult = sendObserve("ObserveCancelAll", null); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + actualResult = sendObserve("ObserveReadAll", null); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + assertEquals("[]", rpcActualResult.get("value").asText()); + } + + /** + * Observe {"id":"/3/0/9"} + * @throws Exception + */ + @Test + public void testObserveSingleResource_Result_CONTENT_Value_SingleResource() throws Exception { + String expectedIdVer = objectInstanceIdVer_3 + "/" + resourceId_9; + String actualResult = sendObserve("Observe", expectedIdVer); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); + } + + /** + * Observe {"id":"/3_1.1/0/13"} + * @throws Exception + */ + @Test + public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() throws Exception { + String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get(); + LwM2mPath expectedPath = new LwM2mPath(expectedInstance); + int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); + String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; + String actualResult = sendObserve("Observe", expectedId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: 1.0"; + assertEquals(expected, rpcActualResult.get("error").asText()); + } + + /** + * Not implemented Instance + * Observe {"id":"/2/0"} + * @throws Exception + */ + @Test + public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception { + String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get(); + String expected = objectInstanceIdVer + "/" + objectInstanceId_0; + String actualResult = sendObserve("Observe", expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Not implemented Resource + * Observe {"id":"/19_1.1/0/0"} + * @throws Exception + */ + @Test + public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { + String objectIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); + String expected = objectIdVer + "/" + objectInstanceId_0 + "/" + resourceId_0; + String actualResult = sendObserve("Observe", expected); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String expectedValue = "values MUST NOT be null"; + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + assertEquals(expectedValue, rpcActualResult.get("error").asText()); + } + + /** + * Repeated request on Observe + * Observe {"id":"/5/0/0"} + * @throws Exception + */ + @Test + public void testObserveRSourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception { + String expectedId = objectInstanceIdVer_5 + "/" + resourceId_0; + sendObserve("Observe", expectedId); + String actualResult = sendObserve("Observe", expectedId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * Repeated request on Observe + * Observe {"id":"/3/0/0"} + * @throws Exception + */ + @Test + public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception { + String expectedId = objectInstanceIdVer_3 + "/" + resourceId_0; + sendObserve("Observe", expectedId); + String actualResult = sendObserve("Observe", expectedId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Observation is already registered!"; + assertEquals(expected, rpcActualResult.get("error").asText()); + } + + /** + * ObserveReadAll + * @throws Exception + */ + @Test + public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveAll() throws Exception { + sendObserve("ObserveCancelAll", null); + String expectedId_0 = objectInstanceIdVer_3 + "/" + resourceId_0; + String expectedId_9 = objectInstanceIdVer_3 + "/" + resourceId_9; + sendObserve("Observe", expectedId_0); + sendObserve("Observe", expectedId_9); + String actualResult = sendObserve("ObserveReadAll", null); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String actualValues = rpcActualResult.get("value").asText(); + assertTrue(actualValues.contains(expectedId_0)); + assertTrue(actualValues.contains(expectedId_9)); + assertEquals(2, actualValues.split(",").length); + } + + + /** + * ObserveCancel {"id":"/3/0/3"} + * ObserveCancel {"id":"/5/0/3"} + */ + @Test + public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception { + sendObserve("ObserveCancelAll", null); + String expectedId_0 = objectInstanceIdVer_3 + "/" + resourceId_0; + String expectedId_3 = objectInstanceIdVer_5 + "/" + resourceId_3; + sendObserve("Observe", expectedId_0); + sendObserve("Observe", expectedId_3); + String actualResult = sendObserve("ObserveCancel", expectedId_0); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + assertEquals("1", rpcActualResult.get("value").asText()); + } + + private String sendObserve(String method, String params) throws Exception { + String sendRpcRequest; + if (params == null) { + sendRpcRequest = "{\"method\": \"" + method + "\"}"; + } + else { + sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; + } + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java new file mode 100644 index 0000000000..9116659bc3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -0,0 +1,226 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.eclipse.leshan.core.LwM2mId.SERVER; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_11; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; + + +public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * Read {"id":"/3"} + * Read {"id":"/6"}... + */ + @Test + public void testReadAllObjectsInClientById_Result_CONTENT_Value_IsLwM2mObject_IsInstances() throws Exception { + expectedObjectIdVers.forEach(expected -> { + try { + String actualResult = sendRPCById((String) expected); + String expectedObjectId = pathIdVerToObjectId((String) expected); + LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; + if (expectedPath.getObjectId() == 2) { + expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; + } + assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** + * Read {"id":"/5/0"} + * + * @throws Exception + */ + @Test + public void testReadAllInstancesInClientById_Result_CONTENT_Value_IsInstances_IsResources() throws Exception{ + expectedObjectIdVerInstances.forEach(expected -> { + try { + String actualResult = sendRPCById((String) expected); + String expectedObjectId = pathIdVerToObjectId((String) expected); + LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedObjectInstances = "LwM2mObjectInstance [id=" + expectedPath.getObjectInstanceId() + ", resources={"; + assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** + * Read {"id":"/19/1/0"} + * + * @throws Exception + */ + @Test + public void testReadMultipleResourceById_Result_CONTENT_Value_IsLwM2mMultipleResource() throws Exception { + String expectedIdVer = objectInstanceIdVer_3 +"/" + resourceId_11 ; + String actualResult = sendRPCById(expectedIdVer); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected = "LwM2mMultipleResource [id=" + resourceId_11 + ", values={"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + } + + /** + * Read {"id":"/3/0/14"} + */ + @Test + public void testReadSingleResourceById_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception { + String expectedIdVer = objectInstanceIdVer_3 +"/" + resourceId_14 ; + String actualResult = sendRPCById(expectedIdVer); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value="; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + } + + /** + * Read {"key":"UtfOffset"} + */ + @Test + public void testReadSingleResourceByKey_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception { + String expectedKey = resourceIdName_3_14 ; + String actualResult = sendRPCByKey(expectedKey); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value="; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + } + + /** + * ReadComposite {"ids":["/1_1.2", "/3_1.0/0/1", "/3_1.0/0/11"]} + */ + @Test + public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectIsLwM2mSingleResourceIsLwM2mMultipleResource() throws Exception { + String expectedIdVer_1 = (String) expectedObjectIdVers.stream().filter(path -> (!((String)path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String)path).contains("/" + SERVER))).findFirst().get(); + String objectId_1 = pathIdVerToObjectId(expectedIdVer_1); + String expectedIdVer3_0_1 = objectInstanceIdVer_3 + "/" + resourceId_1; + String expectedIdVer3_0_11 = objectInstanceIdVer_3 + "/" + resourceId_11; + String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3); + String expectedIds = "[\"" + expectedIdVer_1 + "\", \"" + expectedIdVer3_0_1 + "\", \"" + expectedIdVer3_0_11 + "\"]"; + String actualResult = sendCompositeRPCByIds(expectedIds); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected1 = objectId_1 + "=LwM2mObject [id=" + new LwM2mPath(objectId_1).getObjectId() + ", instances={"; + String expected3_0_1 = objectInstanceId_3 + "/" + resourceId_1 + "=LwM2mSingleResource [id=" + resourceId_1 + ", value="; + String expected3_0_11 = objectInstanceId_3 + "/" + resourceId_11 + "=LwM2mMultipleResource [id=" + resourceId_11 + ", values={"; + String actualValues = rpcActualResult.get("value").asText(); + assertTrue(actualValues.contains(expected1)); + assertTrue(actualValues.contains(expected3_0_1)); + assertTrue(actualValues.contains(expected3_0_11)); + } + + /** + * ReadComposite {"ids":["/1_1.2/0/1", "/1_1.2/0/2", "/3_1.0/0"]} + */ + @Test + public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectInstanceIsLwM2mSingleResource() throws Exception { + String expectedIdVer3_0 = objectInstanceIdVer_3; + String expectedIdVer1_0_1 = objectInstanceIdVer_1 + "/" + resourceId_1; + String expectedIdVer1_0_2 = objectInstanceIdVer_1 + "/" + resourceId_2; + String expectedIds = "[\"" + expectedIdVer1_0_1 + "\", \"" + expectedIdVer1_0_2 + "\", \"" + expectedIdVer3_0 + "\"]"; + String actualResult = sendCompositeRPCByIds(expectedIds); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3); + LwM2mPath path = new LwM2mPath(objectInstanceId_3); + String expected3_0 = objectInstanceId_3 + "=LwM2mObjectInstance [id=" + path.getObjectInstanceId() + ", resources={"; + String objectInstanceId_1 = pathIdVerToObjectId(objectInstanceIdVer_1); + String expected1_0_1 = objectInstanceId_1 + "/" + resourceId_1 + "=LwM2mSingleResource [id=" + resourceId_1 + ", value="; + String expected1_0_2 = objectInstanceId_1 + "/" + resourceId_2 + "=null"; + String actualValues = rpcActualResult.get("value").asText(); + assertTrue(actualValues.contains(expected3_0)); + assertTrue(actualValues.contains(expected1_0_1)); + assertTrue(actualValues.contains(expected1_0_2)); + } + + /** + * ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]} + */ + @Test + public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1_Null() throws Exception { + String expectedKey3_0_9 = resourceIdName_3_9; + String expectedKey3_0_14 = resourceIdName_3_14; + String expectedKey19_0_0 = resourceIdName_19_0_0; + String expectedKey19_1_0 = resourceIdName_19_1_0; + String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]"; + String actualResult = sendCompositeRPCByKeys(expectedKeys); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3); + String objectId_19 = pathIdVerToObjectId(objectIdVer_19); + String expected3_0_9 = objectInstanceId_3 + "/" + resourceId_9 + "=LwM2mSingleResource [id=" + resourceId_9 + ", value="; + String expected3_0_14 = objectInstanceId_3 + "/" + resourceId_14 + "=LwM2mSingleResource [id=" + resourceId_14 + ", value="; + String expected19_0_0 = objectId_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "=null"; + String expected19_1_0 = objectId_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "=null"; + String actualValues = rpcActualResult.get("value").asText(); + assertTrue(actualValues.contains(expected3_0_9)); + assertTrue(actualValues.contains(expected3_0_14)); + assertTrue(actualValues.contains(expected19_0_0)); + assertTrue(actualValues.contains(expected19_1_0)); + } + + + private String sendRPCById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCByKey(String key) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendCompositeRPCByIds(String paths) throws Exception { + String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"ids\":" + paths + "}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendCompositeRPCByKeys(String keys) throws Exception { + String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"keys\":" + keys + "}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java new file mode 100644 index 0000000000..ea23ab48b9 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_4; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_8; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; + + +public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} + * if not implemented: + * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} + * if implemented: + * {"result":"CHANGED"} + */ + @Test + public void testWriteAttributesResourceWithParametersById_Result_INTERNAL_SERVER_ERROR() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_14; + String expectedValue = "{\"pmax\":100, \"pmin\":10}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); + String expected = "not implemented"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + private String sendRPCExecuteWithValueById(String path, String value) throws Exception { + String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java new file mode 100644 index 0000000000..1a92831a36 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java @@ -0,0 +1,335 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_2; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_15; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceInstanceId_2; + +public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTest { + + + /** + * update SingleResource: + * WriteReplace {"id":"3/0/14","value":"+12"} + * {"result":"CHANGED"} + */ + @Test + public void testWriteReplaceValueSingleResourceById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_14; + String expectedValue = "+12"; + String actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + } + + /** + * key + * WriteReplace {"key":"timezone","value":"+10"} + * {"result":"CHANGED"} + */ + @Test + public void testWriteReplaceValueSingleResourceByKey_Result_CHANGED() throws Exception { + String expectedKey = resourceIdName_3_14; + String expectedValue = "+09"; + String actualResult = sendRPCWriteByKey("WriteReplace", expectedKey, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadByKey(expectedKey); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + } + + + /** + * id + * WriteReplace {"id": "/19_1.1/0/0","value": {"0":"0000ad45675600", "15":"1525ad45675600cdef"}} + * {"result":"CHANGED"} + */ + @Test + public void testWriteReplaceValueMultipleResource_Result_CHANGED_Value_Multi_Instance_Resource_must_in_Json_format() throws Exception { + String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0; + int resourceInstanceId0 = 0; + int resourceInstanceId15 = 15; + String expectedValue0 = "0000ad45675600"; + String expectedValue15 = "1525ad45675600cdef"; + String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":\"" + expectedValue15 + "\"}"; + String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + String expectedPath0 = expectedPath + "/" + resourceInstanceId0; + String expectedPath15 = expectedPath + "/" + resourceInstanceId15; + actualResult = sendRPCReadById(expectedPath0); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath15); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + expectedValue15.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + } + + /** + * bad: singleResource, operation="R" - only read + * WriteReplace {"id":"/3/0/9","value":90} + * {"result":"METHOD_NOT_ALLOWED"} + */ + @Test + public void testWriteReplaceValueSingleResourceR_ById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + resourceId_9; + Integer expectedValue = 90; + String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); + } + + /** + * ids + * WriteUpdate {"id":"/3/0","value":{"14":"+5","15":"Kiyv/Europe"}} + * {"result":"CHANGED"} + */ + @Test + public void testWriteUpdateValueSingleResourceById_Result_CHANGED() throws Exception { + String expectedPath = objectInstanceIdVer_3; + String expectedValue14 = "+5"; + String expectedValue15 = "Kiyv/Europe"; + String expectedValue = "{\"" + resourceId_14 + "\":\"" + expectedValue14 + "\",\"" + resourceId_15 + "\":\"" + expectedValue15 + "\"}"; + String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + String expectedPath14 = objectInstanceIdVer_3 + "/" + resourceId_14; + String expectedPath15 = objectInstanceIdVer_3 + "/" + resourceId_15; + actualResult = sendRPCReadById(expectedPath14); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue14 + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath15); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mSingleResource [id=" + resourceId_15 + ", value=" + expectedValue15 + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + } + + /** + * id + * WriteUpdate {"id": "/19_1.1/0","value": {"0":{"0":"00ad456756", "25":"25ad456756"}}} + * {"result":"CHANGED"} + */ + @Test + public void testWriteUpdateValueMultipleResourceById_Result_CHANGED() throws Exception { + String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0; + int resourceInstanceId0 = 0; + int resourceInstanceId25 = 25; + String expectedValue0 = "00ad45675600"; + String expectedValue25 = "25ad45675600cdef"; + String expectedValue = "{\"" + resourceId_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + String expectedPath0 = expectedPath + "/" + resourceId_0 + "/" + resourceInstanceId0; + String expectedPath25 =expectedPath + "/" + resourceId_0 + "/" + resourceInstanceId25; + actualResult = sendRPCReadById(expectedPath0); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath25); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + } + + /** + * ResourceInstance + KeySingleResource + IdSingleResource + * WriteComposite {"nodes":{"/19/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}} + * {"result":"CHANGED"} + */ + @Test + public void testWriteCompositeValueSingleResourceResourceInstanceByIdKey_Result_CHANGED() throws Exception { + int resourceInstanceId2 = 2; + String expectedPath19_1_0_2 = objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "/" + resourceInstanceId2; + String expectedValue19_1_0_2 = "00001234"; + String expectedKey3_0_14 = resourceIdName_3_14; + String expectedValue3_0_14 = "+04"; + String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_15; + String expectedValue3_0_15 = "Kiyv/Europe"; + String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + + "\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; + String actualResult = sendCompositeRPC(nodes); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath19_1_0_2); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadByKey(expectedKey3_0_14); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath3_0_15); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mSingleResource [id=" + resourceId_15 + ", value=" + expectedValue3_0_15 + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + } + + /** + * multipleResource == error + * bad - cannot be used for value Json, only primitive: SingleResource, ResourceInstance (for Json: WriteUpdate, WriteReplace) + * WriteComposite {"nodes":{"/19/0/0":{"0":"abcd5678", "10":"abcd5678"}}} + */ + @Test + public void testWriteCompositeValueSingleMultipleResourceByIdKey_Result_BAD_REQUEST_WriteComposite_operation_for_SingleResources_or_and_ResourceInstance() throws Exception { + String nodes = "{\"/19/0/0\":{\"0\":\"abcd5678\", \"10\":\"abcd5678\"}}"; + String actualResult = sendCompositeRPC(nodes); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String actualValues = rpcActualResult.get("error").asText(); + String expectedNodes = nodes.replaceAll("\"", "").replaceAll(":", "="); + String expected = String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", expectedNodes); + assertEquals(expected, actualValues); + } + + + /** + * update_resourceInstances&update_singleResource + * new ResourceInstance if Resource is Multiple & Resource Single + * - WriteReplace {"id":"/19_1.2/1/0","value":{"2":ddff12"}} + * - WriteReplace {"key":"UtfOffset","value":"+04"} + * - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"} + * WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}} + * {"result":"CHANGED"} + */ + @Test + public void testWriteCompositeCreateResourceInstanceUpdateSingleResourceByIdKey_Result_CHANGED() throws Exception { + String expectedPath19_1_0_2 = objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "/" + resourceInstanceId_2; + String expectedValue19_1_0_2 = "00001234"; + String expectedKey3_0_14 = resourceIdName_3_14; + String expectedValue3_0_14 = "+04"; + String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_15; + String expectedValue3_0_15 = "Kiyv/Europe"; + String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + + "\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; + String actualResult = sendCompositeRPC(nodes); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath19_1_0_2); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId_2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadByKey(expectedKey3_0_14); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath3_0_15); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mSingleResource [id=" + resourceId_15 + ", value=" + expectedValue3_0_15 + ", type=STRING]"; + assertTrue(actualValues.contains(expected)); + } + + /** + * composite_not created_new_instance... + * new ObjectInstance if Object is Multiple - bad + * - WriteReplace {"id":"/19_1.2/2/0","value":{"2":ddff12"}} + * - WriteReplace {"key":"UtfOffset","value":"+04"} + * - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"} + * WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}} + * {"result":"BAD_REQUEST","error":"object instance /19/2 not found"} + */ + @Test + public void testWriteCompositeCreateObjectInstanceUpdateSingleResourceByIdKey_Result_BAD_REQUEST() throws Exception { + String expectedPath19_1_2_2 = objectIdVer_19 + "/" + objectInstanceId_2 + "/" + resourceId_0 + "/" + resourceInstanceId_2; + String expectedValue19_1_0_2 = "00001234"; + String expectedKey3_0_14 = resourceIdName_3_14; + String expectedValue3_0_14 = "+04"; + String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_15; + String expectedValue3_0_15 = "Kiyv/Europe"; + String nodes = "{\"" + expectedPath19_1_2_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + + "\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; + String actualResult = sendCompositeRPC(nodes); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expectedObjectId = pathIdVerToObjectId((String) expectedPath19_1_2_2); + LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); + String expected = "object instance " + "/" + expectedPathId.getObjectId() + "/" + expectedPathId.getObjectInstanceId() + " not found"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + private String sendRPCWriteStringById(String method, String path, String value) throws Exception { + String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": \"" + value + "\" }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCWriteObjectById(String method, String path, Object value) throws Exception { + String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCReadById(String id) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + id + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCWriteByKey(String method, String key, String value) throws Exception { + String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"key\": \"" + key + "\", \"value\": \"" + value + "\" }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCReadByKey(String key) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendCompositeRPC(String nodes) throws Exception { + String setRpcRequest = "{\"method\": \"WriteComposite\", \"params\": {\"nodes\":" + nodes + "}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java new file mode 100644 index 0000000000..ea30b43436 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -0,0 +1,176 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security; + +import org.eclipse.leshan.core.util.Hex; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.KeySpec; + +@DaoSqlTest +public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String pskIdentity; // client public key or id used for PSK + protected final String pskKey; // client private/secret key used for PSK + protected final PublicKey clientPublicKey; // client public key used for RPK + protected final PrivateKey clientPrivateKey; // client private key used for RPK + protected final PublicKey serverPublicKey; // server public key used for RPK + protected final PrivateKey serverPrivateKey; // server private key used for RPK + + // client private key used for X509 + protected final PrivateKey clientPrivateKeyFromCert; + // server private key used for X509 + protected final PrivateKey serverPrivateKeyFromCert; + // client certificate signed by rootCA with a good CN (CN start by leshan_integration_test) + protected final X509Certificate clientX509Cert; + // client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test) + protected final X509Certificate clientX509CertWithBadCN; + // client certificate self-signed with a good CN (CN start by leshan_integration_test) + protected final X509Certificate clientX509CertSelfSigned; + // client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test) + protected final X509Certificate clientX509CertNotTrusted; + // server certificate signed by rootCA + protected final X509Certificate serverX509Cert; + // self-signed server certificate + protected final X509Certificate serverX509CertSelfSigned; + // rootCA used by the server + protected final X509Certificate rootCAX509Cert; + // certificates trustedby the server (should contain rootCA) + protected final Certificate[] trustedCertificates = new Certificate[1]; + + protected static final String ENDPOINT = "deviceAEndpoint"; + + protected LwM2MTestClient client; + + private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials; + + private final String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; + + public AbstractSecurityLwM2MIntegrationTest() { + // create client credentials + setResources(this.resources); + setEndpoint(ENDPOINT); + try { + // Get keys PSK + this.pskIdentity = "SOME_PSK_ID"; + this.pskKey = "73656372657450534b73656372657450"; + + // Get point values + byte[] publicX = Hex + .decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray()); + byte[] publicY = Hex + .decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray()); + byte[] privateS = Hex + .decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray()); + + // Get Elliptic Curve Parameter spec for secp256r1 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + + // Create key specs + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + + // Get keys RPK + clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + + // Get certificates from key store + char[] clientKeyStorePwd = "client".toCharArray(); + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) { + clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd); + } + + clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd); + clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client"); + clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn"); + clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed"); + clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted"); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + + // create server credentials + try { + // Get point values + byte[] publicX = Hex + .decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray()); + byte[] publicY = Hex + .decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray()); + byte[] privateS = Hex + .decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray()); + + // Get Elliptic Curve Parameter spec for secp256r1 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + + // Create key specs + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + + // Get keys + serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + + // Get certificates from key store + char[] serverKeyStorePwd = "server".toCharArray(); + KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) { + serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd); + } + + serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd); + rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA"); + serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server"); + serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed"); + trustedCertificates[0] = serverX509Cert; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + + defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials(); + + NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); + + defaultBootstrapCredentials.setBootstrapServer(serverCredentials); + defaultBootstrapCredentials.setLwm2mServer(serverCredentials); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java new file mode 100644 index 0000000000..0e86c6a438 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; + +@Slf4j +public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + NoSecClientCredential clientCredentials = createNoSecClientCredentials(ENDPOINT); + super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java similarity index 69% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/PskLwm2mIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index ec7e11fa66..af9a668376 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -13,30 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.sql; +package org.thingsboard.server.transport.lwm2m.security.sql; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; -import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import java.nio.charset.StandardCharsets; import static org.eclipse.leshan.client.object.Security.psk; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; -public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { +public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { @Test public void testConnectWithPSKAndObserveTelemetry() throws Exception { - String pskIdentity = "SOME_PSK_ID"; - String pskKey = "73656372657450534b73656372657450"; - PSKClientCredentials clientCredentials = new PSKClientCredentials(); + PSKClientCredential clientCredentials = new PSKClientCredential(); clientCredentials.setEndpoint(ENDPOINT); clientCredentials.setKey(pskKey); clientCredentials.setIdentity(pskIdentity); Security security = psk(SECURE_URI, - 123, + SHORT_SERVER_ID, pskIdentity.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(pskKey.toCharArray())); super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, ENDPOINT); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java similarity index 62% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/RpkLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index 3bb83a3367..9e74beaa6b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -13,29 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.sql; +package org.thingsboard.server.transport.lwm2m.security.sql; import org.eclipse.leshan.client.object.Security; -import org.eclipse.leshan.core.util.Hex; import org.junit.Test; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; -import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import org.apache.commons.codec.binary.Base64;; import static org.eclipse.leshan.client.object.Security.rpk; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; -public class RpkLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { +public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { @Test public void testConnectWithRPKAndObserveTelemetry() throws Exception { - RPKClientCredentials rpkClientCredentials = new RPKClientCredentials(); + RPKClientCredential rpkClientCredentials = new RPKClientCredential(); rpkClientCredentials.setEndpoint(ENDPOINT); - rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded())); + rpkClientCredentials.setKey(new String(Base64.encodeBase64(clientPublicKey.getEncoded()))); Security security = rpk(SECURE_URI, - 123, + SHORT_SERVER_ID, clientPublicKey.getEncoded(), clientPrivateKey.getEncoded(), serverX509Cert.getPublicKey().getEncoded()); super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, ENDPOINT); } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java similarity index 60% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/X509LwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index 7f6ac87fb9..f55c21dcc2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -13,39 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.sql; +package org.thingsboard.server.transport.lwm2m.security.sql; import org.eclipse.leshan.client.object.Security; -import org.junit.Ignore; import org.junit.Test; -import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.transport.util.SslUtil; -import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import static org.eclipse.leshan.client.object.Security.x509; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; -public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { +public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { - @Test - public void testConnectAndObserveTelemetry() throws Exception { - X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(ENDPOINT); - Security security = x509(SECURE_URI, - 123, - clientX509Cert.getEncoded(), - clientPrivateKeyFromCert.getEncoded(), - serverX509Cert.getEncoded()); - super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); - } - - @Ignore //See LwM2mClientContextImpl.unregister @Test public void testConnectWithCertAndObserveTelemetry() throws Exception { - X509ClientCredentials credentials = new X509ClientCredentials(); + X509ClientCredential credentials = new X509ClientCredential(); credentials.setEndpoint(ENDPOINT); credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); Security security = x509(SECURE_URI, - 123, + SHORT_SERVER_ID, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java new file mode 100644 index 0000000000..32d176d598 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.sql; + +import org.eclipse.leshan.client.object.Security; +import org.junit.Test; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import static org.eclipse.leshan.client.object.Security.x509; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; + +public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + X509ClientCredential credentials = new X509ClientCredential(); + credentials.setEndpoint(ENDPOINT); + Security security = x509(SECURE_URI, + SHORT_SERVER_ID, + clientX509Cert.getEncoded(), + clientPrivateKeyFromCert.getEncoded(), + serverX509Cert.getEncoded()); + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java index 15af28284e..fabb2ba36f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_TELEMETRY; class LwM2mTransportServerHelperTest { diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index f2aef88273..3ae3bea89a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -152,7 +152,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); - String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index bfa4f6badf..27477a287a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -154,7 +154,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); - String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 6b2e8d154b..651b00ed67 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -4,6 +4,7 @@ transport.lwm2m.server.security.credentials.keystore.store_file=lwm2m/credential transport.lwm2m.server.security.credentials.keystore.store_password=server transport.lwm2m.server.security.credentials.keystore.key_alias=server transport.lwm2m.server.security.credentials.keystore.key_password=server +transport.lwm2m.bootstrap.enabled=false transport.lwm2m.bootstrap.security.credentials.enabled=true transport.lwm2m.bootstrap.security.credentials.type=KEYSTORE transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credentials/serverKeyStore.jks diff --git a/application/src/test/resources/lwm2m/19.xml b/application/src/test/resources/lwm2m/19.xml new file mode 100644 index 0000000000..ffa21864b7 --- /dev/null +++ b/application/src/test/resources/lwm2m/19.xml @@ -0,0 +1,144 @@ + + + + + BinaryAppDataContainer + + 19 + urn:oma:lwm2m:oma:19:1.1 + 1.1 + 1.1 + Multiple + Optional + + Data + RW + Multiple + Mandatory + Opaque + + + + + Data Priority + RW + Single + Optional + Integer + 1 bytes + + + + Data Creation Time + RW + Single + Optional + Time + + + + + Data Description + RW + Single + Optional + String + 32 bytes + + + + Data Format + RW + Single + Optional + String + 32 bytes + + + + App ID + RW + Single + Optional + Integer + 2 bytes + + + + + + diff --git a/application/src/test/resources/lwm2m/3303.xml b/application/src/test/resources/lwm2m/3303.xml new file mode 100644 index 0000000000..a6e5406b08 --- /dev/null +++ b/application/src/test/resources/lwm2m/3303.xml @@ -0,0 +1,103 @@ + + + + + Temperature + This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius. + 3303 + urn:oma:lwm2m:ext:3303 + 1.0 + 1.0 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value + + + + + diff --git a/application/src/test/resources/lwm2m/6.xml b/application/src/test/resources/lwm2m/6.xml new file mode 100644 index 0000000000..8b1b3da625 --- /dev/null +++ b/application/src/test/resources/lwm2m/6.xml @@ -0,0 +1,143 @@ + + + + + + + Location + + 6 + urn:oma:lwm2m:oma:6 + 1.0 + 1.0 + Single + Optional + + + Latitude + R + Single + Mandatory + Float + + lat + + + + Longitude + R + Single + Mandatory + Float + + lon + + + + Altitude + R + Single + Optional + Float + + m + + + + Radius + R + Single + Optional + Float + + m + + + + Velocity + R + Single + Optional + Opaque + + + + + + Timestamp + R + Single + Mandatory + Time + + + + + Speed + R + Single + Optional + Float + + m/s + + + + + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index cbe7094715..6e96893c22 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -21,7 +21,15 @@ public class StringUtils { return source == null || source.isEmpty(); } + public static boolean isBlank(String source) { + return source == null || source.isEmpty() || source.trim().isEmpty(); + } + public static boolean isNotEmpty(String source) { return source != null && !source.isEmpty(); } + + public static boolean isNotBlank(String source) { + return source != null && !source.isEmpty() && !source.trim().isEmpty(); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MServerCredentialsWithKeys.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MBootstrapClientCredentialWithKeys.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MServerCredentialsWithKeys.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MBootstrapClientCredentialWithKeys.java index c45fb3891b..e0712c4728 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MServerCredentialsWithKeys.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MBootstrapClientCredentialWithKeys.java @@ -19,11 +19,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.binary.Base64; @Getter @Setter -public abstract class AbstractLwM2MServerCredentialsWithKeys implements LwM2MServerCredentials { +public abstract class AbstractLwM2MBootstrapClientCredentialWithKeys implements LwM2MBootstrapClientCredential { private String clientPublicKeyOrId; private String clientSecretKey; @@ -40,6 +40,6 @@ public abstract class AbstractLwM2MServerCredentialsWithKeys implements LwM2MSer @SneakyThrows private static byte[] getDecoded(String key) { - return Hex.decodeHex(key.toLowerCase().toCharArray()); + return Base64.decodeBase64(key.getBytes()); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredential.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredential.java index 66eb523209..70cdc1545b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredential.java @@ -22,6 +22,6 @@ import lombok.Setter; @Getter @Setter @NoArgsConstructor -public abstract class AbstractLwM2MClientCredentials implements LwM2MClientCredentials { +public abstract class AbstractLwM2MClientCredential implements LwM2MClientCredential { private String endpoint; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientSecurityCredential.java similarity index 68% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientSecurityCredential.java index f90e85ff4a..27f0f79511 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientSecurityCredential.java @@ -17,14 +17,14 @@ package org.thingsboard.server.common.data.device.credentials.lwm2m; import lombok.Getter; import lombok.Setter; +import org.apache.commons.codec.DecoderException; -@Getter -@Setter -public class PSKClientCredentials extends AbstractLwM2MClientCredentialsWithKey { - private String identity; +public abstract class AbstractLwM2MClientSecurityCredential extends AbstractLwM2MClientCredential { + @Getter + @Setter + protected String key; - @Override - public LwM2MSecurityMode getSecurityConfigClientMode() { - return LwM2MSecurityMode.PSK; - } + protected byte[] securityInBytes; + + public abstract byte[] getDecoded() throws IllegalArgumentException, DecoderException; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredential.java similarity index 73% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MServerCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredential.java index 82e2b6feae..f27a7ceb4b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MServerCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredential.java @@ -24,13 +24,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; use = JsonTypeInfo.Id.NAME, property = "securityMode") @JsonSubTypes({ - @JsonSubTypes.Type(value = NoSecServerCredentials.class, name = "NO_SEC"), - @JsonSubTypes.Type(value = PSKServerCredentials.class, name = "PSK"), - @JsonSubTypes.Type(value = RPKServerCredentials.class, name = "RPK"), - @JsonSubTypes.Type(value = X509ServerCredentials.class, name = "X509") + @JsonSubTypes.Type(value = NoSecBootstrapClientCredential.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKBootstrapClientCredential.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKBootstrapClientCredential.class, name = "RPK"), + @JsonSubTypes.Type(value = X509BootstrapClientCredential.class, name = "X509") }) @JsonIgnoreProperties(ignoreUnknown = true) -public interface LwM2MServerCredentials { +public interface LwM2MBootstrapClientCredential { @JsonIgnore LwM2MSecurityMode getSecurityMode(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredentials.java similarity index 82% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredentials.java index 70a4846e86..fe46b77541 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MBootstrapClientCredentials.java @@ -20,7 +20,7 @@ import lombok.Setter; @Getter @Setter -public class LwM2MBootstrapCredentials { - private LwM2MServerCredentials bootstrapServer; - private LwM2MServerCredentials lwm2mServer; +public class LwM2MBootstrapClientCredentials { + private LwM2MBootstrapClientCredential bootstrapServer; + private LwM2MBootstrapClientCredential lwm2mServer; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredential.java similarity index 76% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredential.java index 7322c80359..f53e3885fe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredential.java @@ -24,13 +24,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; use = JsonTypeInfo.Id.NAME, property = "securityConfigClientMode") @JsonSubTypes({ - @JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"), - @JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"), - @JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"), - @JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509") + @JsonSubTypes.Type(value = NoSecClientCredential.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKClientCredential.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKClientCredential.class, name = "RPK"), + @JsonSubTypes.Type(value = X509ClientCredential.class, name = "X509") }) @JsonIgnoreProperties(ignoreUnknown = true) -public interface LwM2MClientCredentials { +public interface LwM2MClientCredential { @JsonIgnore LwM2MSecurityMode getSecurityConfigClientMode(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MDeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MDeviceCredentials.java index 3275b4313e..cd73484a51 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MDeviceCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MDeviceCredentials.java @@ -21,6 +21,6 @@ import lombok.Setter; @Getter @Setter public class LwM2MDeviceCredentials { - private LwM2MClientCredentials client; - private LwM2MBootstrapCredentials bootstrap; + private LwM2MClientCredential client; + private LwM2MBootstrapClientCredentials bootstrap; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecBootstrapClientCredential.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecServerCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecBootstrapClientCredential.java index a7a76e5192..12a28f062d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecServerCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecBootstrapClientCredential.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class NoSecServerCredentials implements LwM2MServerCredentials { +public class NoSecBootstrapClientCredential implements LwM2MBootstrapClientCredential { @Override public LwM2MSecurityMode getSecurityMode() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredential.java similarity index 91% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredential.java index 7e54a9b63d..fe40eef78f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredential.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class NoSecClientCredentials extends AbstractLwM2MClientCredentials { +public class NoSecClientCredential extends AbstractLwM2MClientCredential { @Override public LwM2MSecurityMode getSecurityConfigClientMode() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKBootstrapClientCredential.java similarity index 88% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKServerCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKBootstrapClientCredential.java index ab9e515482..6a4cc7f35e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKServerCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKBootstrapClientCredential.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class PSKServerCredentials extends AbstractLwM2MServerCredentialsWithKeys { +public class PSKBootstrapClientCredential extends AbstractLwM2MBootstrapClientCredentialWithKeys { @Override public LwM2MSecurityMode getSecurityMode() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredential.java new file mode 100644 index 0000000000..abb7931b52 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredential.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.credentials.lwm2m; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +@Getter +@Setter +public class PSKClientCredential extends AbstractLwM2MClientSecurityCredential { + private String identity; + + @Override + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.PSK; + } + + @Override + public byte[] getDecoded() throws IllegalArgumentException, DecoderException { + if (securityInBytes == null) { + securityInBytes = Hex.decodeHex(key.toLowerCase().toCharArray()); + } + return securityInBytes; + } +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKBootstrapClientCredential.java similarity index 88% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKServerCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKBootstrapClientCredential.java index f18e74f46c..c93db49eb1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKServerCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKBootstrapClientCredential.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class RPKServerCredentials extends AbstractLwM2MServerCredentialsWithKeys { +public class RPKBootstrapClientCredential extends AbstractLwM2MBootstrapClientCredentialWithKeys { @Override public LwM2MSecurityMode getSecurityMode() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredential.java similarity index 65% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredential.java index 4ebe2de71a..13869aa15d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredential.java @@ -15,10 +15,21 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class RPKClientCredentials extends AbstractLwM2MClientCredentialsWithKey { +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; + +public class RPKClientCredential extends AbstractLwM2MClientSecurityCredential { @Override public LwM2MSecurityMode getSecurityConfigClientMode() { return LwM2MSecurityMode.RPK; } + + @Override + public byte[] getDecoded() throws IllegalArgumentException, DecoderException { + if (securityInBytes == null) { + securityInBytes = Base64.decodeBase64(key.getBytes()); + } + return securityInBytes; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509BootstrapClientCredential.java similarity index 88% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ServerCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509BootstrapClientCredential.java index a7b98608e2..53eda29e60 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ServerCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509BootstrapClientCredential.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class X509ServerCredentials extends AbstractLwM2MServerCredentialsWithKeys { +public class X509BootstrapClientCredential extends AbstractLwM2MBootstrapClientCredentialWithKeys { @Override public LwM2MSecurityMode getSecurityMode() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredential.java similarity index 66% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredential.java index 712dcab5eb..1d52e22a33 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredential.java @@ -17,14 +17,25 @@ package org.thingsboard.server.common.data.device.credentials.lwm2m; import lombok.Getter; import lombok.Setter; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; @Getter @Setter -public class X509ClientCredentials extends AbstractLwM2MClientCredentials { +public class X509ClientCredential extends AbstractLwM2MClientSecurityCredential { + private String cert; @Override public LwM2MSecurityMode getSecurityConfigClientMode() { return LwM2MSecurityMode.X509; } + + @Override + public byte[] getDecoded() throws IllegalArgumentException, DecoderException { + if (securityInBytes == null && cert != null) { + securityInBytes = Base64.decodeBase64(cert.getBytes()); + } + return securityInBytes; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java index 31ae4594ea..08ad7acf5a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java @@ -17,9 +17,11 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; -import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; -import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; + +import java.util.List; @Data public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { @@ -27,7 +29,8 @@ public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTr private static final long serialVersionUID = 6257277825459600068L; private TelemetryMappingConfiguration observeAttr; - private BootstrapConfiguration bootstrap; + private boolean bootstrapServerUpdateEnable; + private List bootstrap; private OtherConfiguration clientLwM2mSettings; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java similarity index 93% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java index ee54a23a07..e0fcc7499c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.data.lwm2m; +package org.thingsboard.server.common.data.device.profile.lwm2m; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java similarity index 92% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java index 4722246b4f..ae4f04c10b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.data.lwm2m; +package org.thingsboard.server.common.data.device.profile.lwm2m; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; @@ -33,6 +33,4 @@ public class OtherConfiguration extends PowerSavingConfiguration { private Long pagingTransmissionWindow; private String fwUpdateResource; private String swUpdateResource; - private boolean compositeOperationsSupport; - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java similarity index 86% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java index 2f2683998d..4bdcee6b3f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.data.lwm2m; +package org.thingsboard.server.common.data.device.profile.lwm2m; import lombok.Data; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; import java.util.Map; import java.util.Set; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractLwM2MBootstrapServerCredential.java similarity index 62% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractLwM2MBootstrapServerCredential.java index 2a3c0ab434..ed0330c60b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractLwM2MBootstrapServerCredential.java @@ -13,27 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.credentials.lwm2m; +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.binary.Base64; -public abstract class AbstractLwM2MClientCredentialsWithKey extends AbstractLwM2MClientCredentials { - @Getter - @Setter - private String key; +@Getter +@Setter +public abstract class AbstractLwM2MBootstrapServerCredential extends LwM2MServerSecurityConfig implements LwM2MBootstrapServerCredential { - private byte[] keyInBytes; + @JsonIgnore + public byte[] getDecodedCServerPublicKey() { + return getDecoded(serverPublicKey); + } @SneakyThrows - @JsonIgnore - public byte[] getDecodedKey() { - if (keyInBytes == null) { - keyInBytes = Hex.decodeHex(key.toLowerCase().toCharArray()); - } - return keyInBytes; + private static byte[] getDecoded(String key) { + return Base64.decodeBase64(key.getBytes()); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java new file mode 100644 index 0000000000..b22e80d81f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "securityMode") +@JsonSubTypes({ + @JsonSubTypes.Type(value = NoSecLwM2MBootstrapServerCredential.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKLwM2MBootstrapServerCredential.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKLwM2MBootstrapServerCredential.class, name = "RPK"), + @JsonSubTypes.Type(value = X509LwM2MBootstrapServerCredential.class, name = "X509") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +public interface LwM2MBootstrapServerCredential { + @JsonIgnore + LwM2MSecurityMode getSecurityMode(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java new file mode 100644 index 0000000000..0ff1dc2eb5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import lombok.Data; + +import java.util.List; + +@Data +public class LwM2MBootstrapServersConfiguration { + + List bootstrap; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java new file mode 100644 index 0000000000..12dd9c5a64 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@ApiModel +@Data +public class LwM2MServerSecurityConfig { + + @ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + + "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", readOnly = true) + protected Integer shortServerId = 123; + /** Security -> ObjectId = 0 'LWM2M Security' */ + @ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " + + "The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " + + "The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " + + "(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", readOnly = true) + protected boolean bootstrapServerIs = false; + @ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) + protected String host; + @ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", readOnly = true) + protected Integer port; + @ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", readOnly = true) + protected Integer clientHoldOffTime = 1; + @ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + + "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) + protected String serverPublicKey; + @ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", readOnly = true) + Integer bootstrapServerAccountTimeout = 0; + + /** Config -> ObjectId = 1 'LwM2M Server' */ + @ApiModelProperty(position = 10, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true) + private Integer lifetime = 300; + @ApiModelProperty(position = 11, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + + "If this Resource doesn’t exist, the default value is 0.", example = "1", readOnly = true) + private Integer defaultMinPeriod = 1; + /** ResourceID=6 'Notification Storing When Disabled or Offline' */ + @ApiModelProperty(position = 12, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + + "If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " + + "The default value is true.", example = "true", readOnly = true) + private boolean notifIfDisabled = true; + @ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " + + "If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", readOnly = true) + private String binding = "U"; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java new file mode 100644 index 0000000000..2c79507810 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@ApiModel +@Data +public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig { + @ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) + protected String securityHost; + @ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", readOnly = true) + protected Integer securityPort; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java new file mode 100644 index 0000000000..7730c79ee9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class NoSecLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.NO_SEC; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java new file mode 100644 index 0000000000..30f7f8c472 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class PSKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.PSK; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java new file mode 100644 index 0000000000..c529820709 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class RPKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.RPK; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java new file mode 100644 index 0000000000..e95bf7742d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class X509LwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.X509; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java deleted file mode 100644 index 79b3ef40b6..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.lwm2m; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -@ApiModel -@Data -public class ServerSecurityConfig { - @ApiModelProperty(position = 1, value = "Is Bootstrap Server", example = "true", readOnly = true) - boolean bootstrapServerIs = true; - @ApiModelProperty(position = 2, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) - String host; - @ApiModelProperty(position = 3, value = "Port for 'No Security' mode", example = "5687", readOnly = true) - Integer port; - @ApiModelProperty(position = 4, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) - String securityHost; - @ApiModelProperty(position = 5, value = "Port for 'Security' mode (DTLS)", example = "5688", readOnly = true) - Integer securityPort; - @ApiModelProperty(position = 5, value = "Server short Id", example = "111", readOnly = true) - Integer serverId = 111; - @ApiModelProperty(position = 7, value = "Client Hold Off Time", example = "1", readOnly = true) - Integer clientHoldOffTime = 1; - @ApiModelProperty(position = 8, value = "Server Public Key (base64 encoded)", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + - "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) - String serverPublicKey; - @ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout", example = "0", readOnly = true) - Integer bootstrapServerAccountTimeout = 0; -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index b277178503..e7b448f800 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java @@ -112,5 +112,4 @@ public class DeviceCredentials extends BaseData implements + credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id=" + id + "]"; } - } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index d8fbbad543..1421d4c64e 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -28,15 +28,30 @@ public class EncryptionUtil { private EncryptionUtil() { } - public static String trimNewLines(String input) { + public static String certTrimNewLines(String input) { return input.replaceAll("-----BEGIN CERTIFICATE-----", "") - .replaceAll("-----END CERTIFICATE-----", "") .replaceAll("\n", "") - .replaceAll("\r", ""); + .replaceAll("\r", "") + .replaceAll("-----END CERTIFICATE-----", ""); } + public static String pubkTrimNewLines(String input) { + return input.replaceAll("-----BEGIN PUBLIC KEY-----", "") + .replaceAll("\n", "") + .replaceAll("\r", "") + .replaceAll("-----END PUBLIC KEY-----", ""); + } + + public static String prikTrimNewLines(String input) { + return input.replaceAll("-----BEGIN EC PRIVATE KEY-----", "") + .replaceAll("\n", "") + .replaceAll("\r", "") + .replaceAll("-----END EC PRIVATE KEY-----", ""); + } + + public static String getSha3Hash(String data) { - String trimmedData = trimNewLines(data); + String trimmedData = certTrimNewLines(data); byte[] dataBytes = trimmedData.getBytes(); SHA3Digest md = new SHA3Digest(256); md.reset(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mBootstrapTransportComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mBootstrapTransportComponent.java new file mode 100644 index 0000000000..d9de9269d0 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mBootstrapTransportComponent.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.lwm2m.enabled}'=='true')) && '${transport.lwm2m.bootstrap.enabled:false}'=='true'") +public @interface TbLwM2mBootstrapTransportComponent { +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java index 638f9f6fb2..344031a4bb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java @@ -21,6 +21,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.lwm2m.enabled}'=='true')") public @interface TbLwM2mTransportComponent { } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 78b25c2d42..0aa01246e3 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -52,7 +52,6 @@ import java.util.List; import java.util.Optional; import java.util.Random; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 0ea9be4388..bc2bd9b9aa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -17,39 +17,30 @@ package org.thingsboard.server.transport.lwm2m.bootstrap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.leshan.core.model.ObjectLoader; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.config.ssl.SslCredentials; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter; +import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.cert.X509Certificate; -import java.util.List; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')") +@TbLwM2mBootstrapTransportComponent @RequiredArgsConstructor public class LwM2MTransportBootstrapService { private boolean pskMode = false; @@ -58,7 +49,7 @@ public class LwM2MTransportBootstrapService { private final LwM2MTransportBootstrapConfig bootstrapConfig; private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; - + private final TransportService transportService; private LeshanBootstrapServer server; @PostConstruct @@ -84,15 +75,11 @@ public class LwM2MTransportBootstrapService { /* Create CoAP Config */ builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); - /* Define model provider (Create Models )*/ - List models = ObjectLoader.loadDefault(); - builder.setModel(new StaticModel(models)); - /* Create credentials */ this.setServerWithCredentials(builder); /* Set securityStore with new ConfigStore */ - builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore)); + builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); /* SecurityStore */ builder.setSecurityStore(lwM2MBootstrapSecurityStore); @@ -107,7 +94,7 @@ public class LwM2MTransportBootstrapService { /* Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); - BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); + BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService); builder.setSessionManager(sessionManager); /* Create BootstrapServer */ diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 7dca87458b..124de67fa0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -15,32 +15,31 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MBootstrapClientCredentialWithKeys; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import java.io.Serializable; import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +@Slf4j @Data public class LwM2MBootstrapConfig implements Serializable { - /* - interface BootstrapSecurityConfig - servers: BootstrapServersSecurityConfig, - bootstrapServer: ServerSecurityConfig, - lwm2mServer: ServerSecurityConfig - } - */ - /** -servers - * shortId: number, - * lifetime: number, - * defaultMinPeriod: number, - * notifIfDisabled: boolean, - * binding: string - * */ - LwM2MBootstrapServers servers; + + List serverConfiguration; /** -bootstrapServer, lwm2mServer * interface ServerSecurityConfig @@ -55,50 +54,90 @@ public class LwM2MBootstrapConfig implements Serializable { * serverId?: number, * bootstrapServerAccountTimeout: number * */ - LwM2MServerBootstrap bootstrapServer; + @Getter + @Setter + private LwM2MBootstrapClientCredential bootstrapServer; + + @Getter + @Setter + private LwM2MBootstrapClientCredential lwm2mServer; - LwM2MServerBootstrap lwm2mServer; + public LwM2MBootstrapConfig(){}; + public LwM2MBootstrapConfig(List serverConfiguration, LwM2MBootstrapClientCredential bootstrapClientServer, LwM2MBootstrapClientCredential lwm2mClientServer) { + this.serverConfiguration = serverConfiguration; + this.bootstrapServer = bootstrapClientServer; + this.lwm2mServer = lwm2mClientServer; + + } + + @JsonIgnore public BootstrapConfig getLwM2MBootstrapConfig() { BootstrapConfig configBs = new BootstrapConfig(); - /* Delete old security objects */ - configBs.toDelete.add("/0"); - configBs.toDelete.add("/1"); - /* Server Configuration (object 1) as defined in LWM2M 1.0.x TS. */ - BootstrapConfig.ServerConfig server0 = new BootstrapConfig.ServerConfig(); - server0.shortId = servers.getShortId(); - server0.lifetime = servers.getLifetime(); - server0.defaultMinPeriod = servers.getDefaultMinPeriod(); - server0.notifIfDisabled = servers.isNotifIfDisabled(); - server0.binding = BindingMode.parse(servers.getBinding()); - configBs.servers.put(0, server0); - /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ - this.bootstrapServer.setBootstrapServerIs(true); - configBs.security.put(0, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); - /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */ - configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); + configBs.autoIdForSecurityObject = true; + int id = 0; + for (LwM2MBootstrapServerCredential serverCredential : serverConfiguration) { + BootstrapConfig.ServerConfig serverConfig = setServerConfig((AbstractLwM2MBootstrapServerCredential) serverCredential); + configBs.servers.put(id, serverConfig); + BootstrapConfig.ServerSecurity serverSecurity = setServerSecurity((AbstractLwM2MBootstrapServerCredential) serverCredential, serverCredential.getSecurityMode()); + configBs.security.put(id, serverSecurity); + id++; + } + /** in LwM2mDefaultBootstrapSessionManager -> initTasks + * Delete all security/config objects if update bootstrap server and lwm2m server + * if other: del or update only instances */ + return configBs; } - private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, String securityHost, Integer securityPort, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { + private BootstrapConfig.ServerSecurity setServerSecurity(AbstractLwM2MBootstrapServerCredential serverCredential, LwM2MSecurityMode securityMode) { BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity(); - if (securityMode.equals(SecurityMode.NO_SEC)) { - serverSecurity.uri = "coap://" + host + ":" + Integer.toString(port); - } else { - serverSecurity.uri = "coaps://" + securityHost + ":" + Integer.toString(securityPort); + String serverUri = "coap://"; + byte[] publicKeyOrId = new byte[]{}; + byte[] secretKey = new byte[]{}; + byte[] serverPublicKey = new byte[]{}; + serverSecurity.serverId = serverCredential.getShortServerId(); + log.info("serverId = [{}]", serverSecurity.serverId); + serverSecurity.securityMode = SecurityMode.valueOf(securityMode.name()); + serverSecurity.bootstrapServer = serverCredential.isBootstrapServerIs(); + if (!LwM2MSecurityMode.NO_SEC.equals(securityMode)) { + AbstractLwM2MBootstrapClientCredentialWithKeys server; + if (serverSecurity.bootstrapServer) { + server = (AbstractLwM2MBootstrapClientCredentialWithKeys) this.bootstrapServer; + + } else { + server = (AbstractLwM2MBootstrapClientCredentialWithKeys) this.lwm2mServer; + } + serverUri = "coaps://"; + if (LwM2MSecurityMode.PSK.equals(securityMode)) { + publicKeyOrId = server.getClientPublicKeyOrId().getBytes(); + secretKey = Hex.decodeHex(server.getClientSecretKey().toCharArray()); + log.info("publicKeyOrId [{}]", new String(publicKeyOrId, StandardCharsets.UTF_8)); + } else { + publicKeyOrId = server.getDecodedClientPublicKeyOrId(); + secretKey = server.getDecodedClientSecretKey(); + log.info("publicKeyOrId [{}]", Hex.encodeHexString(publicKeyOrId)); + } + serverPublicKey = serverCredential.getDecodedCServerPublicKey(); } - serverSecurity.bootstrapServer = bootstrapServer; - serverSecurity.securityMode = securityMode; - serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode); - serverSecurity.serverPublicKey = (serverPublicKey != null && !serverPublicKey.isEmpty()) ? Hex.decodeHex(serverPublicKey.toCharArray()) : new byte[]{}; - serverSecurity.secretKey = (secretKey != null && !secretKey.isEmpty()) ? Hex.decodeHex(secretKey.toCharArray()) : new byte[]{}; - serverSecurity.serverId = serverId; + log.info("secretKey [{}]", Hex.encodeHexString(secretKey)); + serverUri += (((serverCredential.getHost().equals("0.0.0.0") ? "localhost" : serverCredential.getHost()) + ":" + serverCredential.getPort())); + log.info("serverSecurity.uri = [{}]", serverUri); + serverSecurity.uri = serverUri; + serverSecurity.publicKeyOrId = publicKeyOrId; + serverSecurity.secretKey = secretKey; + serverSecurity.serverPublicKey = serverPublicKey; + log.info("server [{}]", Hex.encodeHexString(serverSecurity.serverPublicKey)); return serverSecurity; } - private byte[] setPublicKeyOrId(String publicKeyOrIdStr, SecurityMode securityMode) { - return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} : - SecurityMode.PSK.equals(securityMode) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : - Hex.decodeHex(publicKeyOrIdStr.toCharArray()); + private BootstrapConfig.ServerConfig setServerConfig (AbstractLwM2MBootstrapServerCredential serverCredential) { + BootstrapConfig.ServerConfig serverConfig = new BootstrapConfig.ServerConfig(); + serverConfig.shortId = serverCredential.getShortServerId(); + serverConfig.lifetime = serverCredential.getLifetime(); + serverConfig.defaultMinPeriod = serverCredential.getDefaultMinPeriod(); + serverConfig.notifIfDisabled = serverCredential.isNotifIfDisabled(); + serverConfig.binding = BindingMode.parse(serverCredential.getBinding()); + return serverConfig; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java deleted file mode 100644 index 12325f8c22..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.bootstrap.secure; - -import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; -import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; - -public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter { - - public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) { - super(store); - } - -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index c8a004f52c..0e425f2a05 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -42,8 +42,6 @@ public class LwM2MServerBootstrap { public LwM2MServerBootstrap() { } - ; - public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) { this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 4e34c3dc6d..4d3f0a55f0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -16,23 +16,42 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +import org.eclipse.leshan.core.request.BootstrapFinishRequest; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; +import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause; import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; +import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider; +import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapConfigStoreTaskProvider; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; -import java.util.Collections; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; + +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO; @Slf4j public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { private BootstrapSecurityStore bsSecurityStore; private SecurityChecker securityChecker; + private BootstrapTaskProvider tasksProvider; + private LwM2mBootstrapModelProvider modelProvider; + private TransportService transportService; /** * Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new @@ -40,29 +59,170 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}. */ - public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) { - this(bsSecurityStore, new SecurityChecker()); + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) { + this(bsSecurityStore, new SecurityChecker(), new LwM2MBootstrapConfigStoreTaskProvider(configStore), + new StandardBootstrapModelProvider()); + this.transportService = transportService; } - public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker) { - super(bsSecurityStore); + /** + * Create a {@link DefaultBootstrapSessionManager}. + * + * @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}. + * @param securityChecker used to accept or refuse new {@link BootstrapSession}. + */ + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker, + BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { + super(bsSecurityStore, securityChecker, tasksProvider, modelProvider); this.bsSecurityStore = bsSecurityStore; this.securityChecker = securityChecker; + this.tasksProvider = tasksProvider; + this.modelProvider = modelProvider; } - @SuppressWarnings("deprecation") + @Override public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { - boolean authorized; - if (bsSecurityStore != null) { - Iterator securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? - Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName()); - log.info("Bootstrap session started securityInfos: [{}]", securityInfos); - authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); - } else { - authorized = true; + boolean authorized = true; + Iterator securityInfos; + try { + if (bsSecurityStore != null && securityChecker != null) { + if (clientIdentity.isSecure() && clientIdentity.isPSK()) { + securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity()); + } else { + securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName()); + } + authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); + } + } catch (LwM2MAuthException e) { + authorized = false; } DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); - log.info("Bootstrap session started : {}", session); + if (authorized) { + this.sendLogs (request.getEndpointName(), + String.format("%s: Bootstrap session started...", LOG_LWM2M_INFO, request.getEndpointName())); + } return session; } + + @Override + public boolean hasConfigFor(BootstrapSession session) { + BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null); + if (firstTasks == null) { + return false; + } + initTasks(session, firstTasks); + return true; + } + + protected void initTasks(BootstrapSession bssession, BootstrapTaskProvider.Tasks tasks) { + DefaultBootstrapSession session = (DefaultBootstrapSession) bssession; + // set models + if (tasks.supportedObjects != null) + session.setModel(modelProvider.getObjectModel(session, tasks.supportedObjects)); + + // set Requests to Send + log.info("tasks.requestsToSend = [{}]", tasks.requestsToSend); + session.setRequests(tasks.requestsToSend); + + // prepare list where we will store Responses + session.setResponses(new ArrayList(tasks.requestsToSend.size())); + + // is last Tasks ? + session.setMoreTasks(!tasks.last); + } + + @Override + public BootstrapDownlinkRequest getFirstRequest(BootstrapSession bsSession) { + return nextRequest(bsSession); + } + + protected BootstrapDownlinkRequest nextRequest(BootstrapSession bsSession) { + DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; + List> requestsToSend = session.getRequests(); + + if (!requestsToSend.isEmpty()) { + // get next requests + return requestsToSend.remove(0); + } else { + if (session.hasMoreTasks()) { + BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses()); + if (nextTasks == null) { + session.setMoreTasks(false); + return new BootstrapFinishRequest(); + } + + initTasks(session, nextTasks); + return nextRequest(bsSession); + } else { + return new BootstrapFinishRequest(); + } + } + } + + @Override + public BootstrapPolicy onResponseSuccess(BootstrapSession bsSession, + BootstrapDownlinkRequest request, LwM2mResponse response) { + if (!(request instanceof BootstrapFinishRequest)) { + // store response + DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; + session.getResponses().add(response); + String msg = String.format("%s: receives success response for: %s %s %s", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), request.getPath().toString(), response.toString()); + this.sendLogs(bsSession.getEndpoint(), msg); + + // on success for NOT bootstrap finish request we send next request + return BootstrapPolicy.continueWith(nextRequest(bsSession)); + } else { + // on success for bootstrap finish request we stop the session + this.sendLogs(bsSession.getEndpoint(), + String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO)); + return BootstrapPolicy.finished(); + } + } + + @Override + public BootstrapPolicy onResponseError(BootstrapSession bsSession, + BootstrapDownlinkRequest request, LwM2mResponse response) { + if (!(request instanceof BootstrapFinishRequest)) { + // store response + DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; + session.getResponses().add(response); + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: %s %s receives error response %s ", LOG_LWM2M_INFO, + request.getClass().getSimpleName(), + request.getPath().toString(), response.toString())); + // on response error for NOT bootstrap finish request we continue any sending next request + return BootstrapPolicy.continueWith(nextRequest(bsSession)); + } else { + // on response error for bootstrap finish request we stop the session + this.sendLogs (bsSession.getEndpoint(), + String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString())); + return BootstrapPolicy.failed(); + } + } + + @Override + public BootstrapPolicy onRequestFailure(BootstrapSession bsSession, + BootstrapDownlinkRequest request, Throwable cause) { + this.sendLogs(bsSession.getEndpoint(), + String.format("%s: %s %s failed because of %s", LOG_LWM2M_ERROR, request.getClass().getSimpleName(), + request.getPath().toString(), cause.toString())); + return BootstrapPolicy.failed(); + } + + @Override + public void end(BootstrapSession bsSession) { + this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO)); + } + + @Override + public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) { + this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session failed because of %s", LOG_LWM2M_ERROR, + cause.toString())); + } + + private void sendLogs(String endpointName, String logMsg) { + log.info(logMsg); + transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java new file mode 100644 index 0000000000..a3b213239e --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java @@ -0,0 +1,284 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.bootstrap.store; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.BootstrapDeleteRequest; +import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; +import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; +import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; +import org.eclipse.leshan.server.bootstrap.BootstrapUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest; + +@Slf4j +public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvider { + + private BootstrapConfigStore store; + + private Map supportedObjects; + + /** + * Map + */ + protected Map securityInstances; + protected Map serverInstances; + protected Integer bootstrapServerIdOld; + protected Integer bootstrapServerIdNew; + + public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) { + this.store = store; + } + + @Override + public Tasks getTasks(BootstrapSession session, List previousResponse) { + BootstrapConfig config = store.get(session.getEndpoint(), session.getIdentity(), session); + if (config == null) { + return null; + } + if (previousResponse == null && shouldStartWithDiscover(config)) { + Tasks tasks = new Tasks(); + tasks.requestsToSend = new ArrayList<>(1); + tasks.requestsToSend.add(new BootstrapDiscoverRequest()); + tasks.last = false; + return tasks; + } else { + Tasks tasks = new Tasks(); + if (this.supportedObjects == null) { + initSupportedObjectsDefault(); + } + // add supportedObjects + tasks.supportedObjects = this.supportedObjects; + // handle bootstrap discover response + if (previousResponse != null) { + if (previousResponse.get(0) instanceof BootstrapDiscoverResponse) { + BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0); + if (discoverResponse.isSuccess()) { + this.initAfterBootstrapDiscover(discoverResponse); + findSecurityInstanceId(discoverResponse.getObjectLinks()); + } else { + log.warn( + "Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}", + discoverResponse, session); + } + if (this.securityInstances.get(0) == null) { + log.error( + "Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}", + discoverResponse, session); + return null; + } + tasks.requestsToSend = new ArrayList<>(1); + tasks.requestsToSend.add(new BootstrapReadRequest("/1")); + tasks.last = false; + return tasks; + } + BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0); + if (readResponse.isSuccess()) { + findServerInstanceId(readResponse); + } else { + log.warn( + "Bootstrap ReadResponse return error {} : to continue bootstrap session without find Server Instance Id. {}", + readResponse, session); + } + // create requests from config + tasks.requestsToSend = this.toRequests(config, + config.contentFormat != null ? config.contentFormat : session.getContentFormat()); + } else { + // create requests from config + tasks.requestsToSend = BootstrapUtil.toRequests(config, + config.contentFormat != null ? config.contentFormat : session.getContentFormat()); + } + return tasks; + } + } + + protected boolean shouldStartWithDiscover(BootstrapConfig config) { + return config.autoIdForSecurityObject; + } + + protected void findSecurityInstanceId(Link[] objectLinks) { + log.info("Object after discover: [{}]", objectLinks); + this.securityInstances = new HashMap<>(); + for (Link link : objectLinks) { + if (link.getUrl().startsWith("/0/")) { + try { + LwM2mPath path = new LwM2mPath(link.getUrl()); + if (path.isObjectInstance()) { + if (link.getAttributes().containsKey("ssid")) { + int serverId = Integer.parseInt(link.getAttributes().get("ssid")); + if (!this.securityInstances.containsKey(serverId)) { + this.securityInstances.put(serverId, path.getObjectInstanceId()); + } else { + log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); + } + this.securityInstances.put(Integer.valueOf(link.getAttributes().get("ssid")), path.getObjectInstanceId()); + } else { + if (!this.securityInstances.containsKey(0)) { + this.securityInstances.put(0, path.getObjectInstanceId()); + } else { + log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId()); + } + } + } + } catch (Exception e) { + // ignore if this is not a LWM2M path + log.error("Invalid LwM2MPath starting by \"/0/\""); + } + } + } + } + + protected void findServerInstanceId(BootstrapReadResponse readResponse) { + this.serverInstances = new HashMap<>(); + ((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> { + serverInstances.put(((Long) instance.getResource(0).getValue()).intValue(), instance.getId()); + }); + if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) { + this.findBootstrapServerId(); + } + } + + protected void findBootstrapServerId() { + Map filteredMap = this.serverInstances.entrySet() + .stream().filter(x -> !this.securityInstances.containsKey(x.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (filteredMap.size() > 0) { + this.bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get(); + } + } + + public BootstrapConfigStore getStore() { + return this.store; + } + + private void initAfterBootstrapDiscover(BootstrapDiscoverResponse response) { + Link[] links = response.getObjectLinks(); + Arrays.stream(links).forEach(link -> { + LwM2mPath path = new LwM2mPath(link.getUrl()); + if (!path.isRoot() && path.getObjectId() < 3) { + if (path.isObject()) { + String ver = link.getAttributes().get("ver") != null ? link.getAttributes().get("ver") : "1.0"; + this.supportedObjects.put(path.getObjectId(), ver); + } + } + }); + } + + + public List> toRequests(BootstrapConfig bootstrapConfig, + ContentFormat contentFormat) { + List> requests = new ArrayList<>(); + Set pathsDelete = new HashSet<>(); + List> requestsWrite = new ArrayList<>(); + boolean isBsServer = false; + boolean isLwServer = false; + /** Map */ + Map instances = new HashMap<>(); + // handle security + int id = 0; + for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) { + if (security.bootstrapServer) { + requestsWrite.add(toWriteRequest(this.securityInstances.get(0), security, contentFormat)); + isBsServer = true; + this.bootstrapServerIdNew = security.serverId; + instances.put(security.serverId, this.securityInstances.get(0)); + } else { + if (id == this.securityInstances.get(0)) { + id++; + } + requestsWrite.add(toWriteRequest(id, security, contentFormat)); + instances.put(security.serverId, id); + isLwServer = true; + if (!isBsServer && this.securityInstances.containsKey(security.serverId) && id != this.securityInstances.get(security.serverId)) { + pathsDelete.add("/0/" + this.securityInstances.get(security.serverId)); + } + /** + * If there is an instance in the serverInstances with serverId which we replace in the securityInstances + */ + // find serverId in securityInstances by id (instance) + Integer serverIdOld = null; + for (Map.Entry entry : this.securityInstances.entrySet()) { + if (entry.getValue().equals(id)) { + serverIdOld = entry.getKey(); + } + } + if (!isBsServer && serverIdOld != null && this.serverInstances.containsKey(serverIdOld)) { + pathsDelete.add("/1/" + this.serverInstances.get(serverIdOld)); + } + id++; + } + } + // handle server + for (Map.Entry server : bootstrapConfig.servers.entrySet()) { + int securityInstanceId = instances.get(server.getValue().shortId); + requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat)); + if (!isBsServer) { + /** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */ + if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew && + (this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) { + pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld)); + /** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */ + } else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) { + pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId)); + } + } + } + // handle acl + for (Map.Entry acl : bootstrapConfig.acls.entrySet()) { + requestsWrite.add(toWriteRequest(acl.getKey(), acl.getValue(), contentFormat)); + } + // handle delete + if (isBsServer && isLwServer) { + requests.add(new BootstrapDeleteRequest("/0")); + requests.add(new BootstrapDeleteRequest("/1")); + } else { + pathsDelete.forEach(pathDelete -> requests.add(new BootstrapDeleteRequest(pathDelete))); + } + // handle write + if (requestsWrite.size() > 0) { + requests.addAll(requestsWrite); + } + return (requests); + } + + private void initSupportedObjectsDefault() { + this.supportedObjects = new HashMap<>(); + this.supportedObjects.put(0, "1.1"); + this.supportedObjects.put(1, "1.1"); + this.supportedObjects.put(2, "1.0"); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java similarity index 55% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java index c7ec42d879..da09d77987 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java @@ -13,43 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.bootstrap.secure; +package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import java.io.IOException; -import java.security.GeneralSecurityException; import java.util.Collections; import java.util.Iterator; +import java.util.Map; +import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_TELEMETRY; @Slf4j @Service("LwM2MBootstrapSecurityStore") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true' && '${transport.lwm2m.bootstrap.enable}'=='true')") +@TbLwM2mBootstrapTransportComponent public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private final EditableBootstrapConfigStore bootstrapConfigStore; @@ -58,6 +57,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private final LwM2mTransportContext context; private final LwM2mTransportServerHelper helper; + private final Map bsSessions = new ConcurrentHashMap<>(); public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) { this.bootstrapConfigStore = bootstrapConfigStore; @@ -69,12 +69,25 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public Iterator getAllByEndpoint(String endPoint) { TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP); - if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { + if (store.getBootstrapCredentialConfig() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); + endPoint = store.getEndpoint(); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); if (bsConfigNew != null) { try { + boolean bootstrapServerUpdateEnable = ((Lwm2mDeviceProfileTransportConfiguration)store.getDeviceProfile().getProfileData().getTransportConfiguration()).isBootstrapServerUpdateEnable(); + if (!bootstrapServerUpdateEnable) { + Optional> securities = bsConfigNew.security.entrySet().stream().filter(sec -> sec.getValue().bootstrapServer).findAny(); + if (securities.isPresent()) { + bsConfigNew.security.entrySet().remove(securities.get()); + int serverSortId = securities.get().getValue().serverId; + Optional> serverConfigs = bsConfigNew.servers.entrySet().stream().filter(serv -> ((BootstrapConfig.ServerConfig)serv.getValue()).shortId==serverSortId).findAny(); + if (serverConfigs.isPresent()) { + bsConfigNew.servers.entrySet().remove(serverConfigs.get()); + } + } + } for (String config : bootstrapConfigStore.getAll().keySet()) { if (config.equals(endPoint)) { bootstrapConfigStore.remove(config); @@ -82,7 +95,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } bootstrapConfigStore.add(endPoint, bsConfigNew); } catch (InvalidConfigurationException e) { - log.error("", e); + if (e.getMessage().contains("Psk identity") && e.getMessage().contains("already used for this bootstrap server")) { + log.trace("Invalid Bootstrap Configuration", e); + } + else { + log.error("Invalid Bootstrap Configuration", e); + } } return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator(); } @@ -101,7 +119,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { try { bootstrapConfigStore.add(store.getEndpoint(), bsConfig); } catch (InvalidConfigurationException e) { - log.error("", e); + log.error("Invalid Bootstrap Configuration", e); } return store.getSecurityInfo(); } @@ -113,34 +131,6 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { /* BootstrapConfig */ LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { - /* Security info */ - switch (lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode()) { - /* Use RPK only */ - case PSK: - store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(), - lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(), - Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray()))); - store.setSecurityMode(SecurityMode.PSK); - break; - case RPK: - try { - store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(), - SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray())))); - store.setSecurityMode(SecurityMode.RPK); - break; - } catch (IOException | GeneralSecurityException e) { - log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage()); - } - case X509: - store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint())); - store.setSecurityMode(SecurityMode.X509); - break; - case NO_SEC: - store.setSecurityMode(SecurityMode.NO_SEC); - store.setSecurityInfo(null); - break; - default: - } BootstrapConfig bootstrapConfig = lwM2MBootstrapConfig.getLwM2MBootstrapConfig(); store.setBootstrapConfig(bootstrapConfig); } @@ -149,28 +139,20 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) { LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); if (lwM2MBootstrapConfig != null) { - BootstrapConfiguration bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); - lwM2MBootstrapConfig.servers = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getServers()), LwM2MBootstrapServers.class); - LwM2MServerBootstrap profileServerBootstrap = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getBootstrapServer()), LwM2MServerBootstrap.class); - LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); UUID sessionUUiD = UUID.randomUUID(); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); + bsSessions.put(store.getEndpoint(), sessionInfo); context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService())); - if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { - lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); - lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); - String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LWM2M_INFO, store.getEndpoint()); - helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo); + if (this.getValidatedSecurityMode(lwM2MBootstrapConfig)) { return lwM2MBootstrapConfig; } else { log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint()); log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint()); - String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint()); + String logMsg = String.format("%s: Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR); helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), sessionInfo); return null; } } - log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint()); return null; } @@ -179,14 +161,34 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { * Bootstrap security have to sync between (bootstrapServer in credential and bootstrapServer in profile) * and (lwm2mServer in credential and lwm2mServer in profile * - * @param bootstrapFromCredential - Bootstrap -> Security of bootstrapServer in credential - * @param profileServerBootstrap - Bootstrap -> Security of bootstrapServer in profile - * @param lwm2mFromCredential - Bootstrap -> Security of lwm2mServer in credential - * @param profileLwm2mServer - Bootstrap -> Security of lwm2mServer in profile * @return false if not sync between SecurityMode of Bootstrap credential and profile */ - private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) { - return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && - lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode())); +// private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap bootstrapServerProfile, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) { + private boolean getValidatedSecurityMode(LwM2MBootstrapConfig lwM2MBootstrapConfig) { + LwM2MSecurityMode bootstrapServerSecurityMode = lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode(); + LwM2MSecurityMode lwm2mServerSecurityMode = lwM2MBootstrapConfig.getLwm2mServer().getSecurityMode(); + AtomicBoolean validBs = new AtomicBoolean(true); + AtomicBoolean validLw = new AtomicBoolean(true); + lwM2MBootstrapConfig.getServerConfiguration().forEach(serverCredential -> { + if (((AbstractLwM2MBootstrapServerCredential)serverCredential).isBootstrapServerIs()) { + if (!bootstrapServerSecurityMode.equals(serverCredential.getSecurityMode())) { + validBs.set(false); + } + } + else { + if (!lwm2mServerSecurityMode.equals(serverCredential.getSecurityMode())) { + validLw.set(false); + } + } + }); + return validBs.get()&validLw.get(); + } + + public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) { + return bsSessions.get(endpoint); + } + + public TransportProtos.SessionInfoProto removeSessionByEndpoint(String endpoint) { + return bsSessions.remove(endpoint); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java new file mode 100644 index 0000000000..d27ec707c2 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java @@ -0,0 +1,83 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.bootstrap.store; + +import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; +import java.util.Map; + +public class LwM2MConfigurationChecker extends ConfigurationChecker { + + @Override + public void verify(BootstrapConfig config) throws InvalidConfigurationException { + // check security configurations + for (Map.Entry e : config.security.entrySet()) { + BootstrapConfig.ServerSecurity sec = e.getValue(); + + // checks security config + switch (sec.securityMode) { + case NO_SEC: + checkNoSec(sec); + break; + case PSK: + checkPSK(sec); + break; + case RPK: + checkRPK(sec); + break; + case X509: + checkX509(sec); + break; + case EST: + throw new InvalidConfigurationException("EST is not currently supported.", e); + } + + validateMandatoryField(sec); + } + + // does each server have a corresponding security entry? + validateOneSecurityByServer(config); + } + + protected void validateOneSecurityByServer(BootstrapConfig config) throws InvalidConfigurationException { + for (Map.Entry e : config.servers.entrySet()) { + BootstrapConfig.ServerConfig srvCfg = e.getValue(); + + // shortId checks + if (srvCfg.shortId == 0) { + throw new InvalidConfigurationException("short ID must not be 0"); + } + + // look for security entry + BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId); + + if (security == null) { + throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey()); + } + } + } + + protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) { + for (Map.Entry es : config.security.entrySet()) { + if (es.getValue().serverId == shortId) { + return es.getValue(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java similarity index 56% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java index bc44922fcf..8d6a62e89d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.bootstrap.secure; +package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent; import java.util.Map; import java.util.concurrent.locks.Lock; @@ -29,11 +32,17 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; @Slf4j @Component("LwM2MInMemoryBootstrapConfigStore") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')") +@TbLwM2mBootstrapTransportComponent public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigStore { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = readWriteLock.readLock(); private final Lock writeLock = readWriteLock.writeLock(); + protected final ConfigurationChecker configChecker = new LwM2MConfigurationChecker(); + + @Override + public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) { + return bootstrapByEndpoint.get(endpoint); + } @Override public Map getAll() { @@ -56,16 +65,35 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt } @Override - public BootstrapConfig remove(String enpoint) { + public BootstrapConfig remove(String endpoint) { writeLock.lock(); try { - return super.remove(enpoint); + return super.remove(endpoint); } finally { writeLock.unlock(); } } public void addToStore(String endpoint, BootstrapConfig config) throws InvalidConfigurationException { - super.add(endpoint, config); + configChecker.verify(config); + // Check PSK identity uniqueness for bootstrap server: + PskByServer pskToAdd = getBootstrapPskIdentity(config); + if (pskToAdd != null) { + BootstrapConfig existingConfig = bootstrapByPskId.get(pskToAdd); + if (existingConfig != null) { + // check if this config will be replace by the new one. + BootstrapConfig previousConfig = bootstrapByEndpoint.get(endpoint); + if (previousConfig != existingConfig) { + throw new InvalidConfigurationException( + "Psk identity [%s] already used for this bootstrap server [%s]", pskToAdd.identity, + pskToAdd.serverUrl); + } + } + } + + bootstrapByEndpoint.put(endpoint, config); + if (pskToAdd != null) { + bootstrapByPskId.put(pskToAdd, config); + } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java index 749f7352ba..83383f6950 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enabled:false}'=='true'") public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig { @Getter diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index 11a562720a..3e3780f6cf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -33,7 +33,7 @@ import java.util.List; @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'") @ConfigurationProperties(prefix = "transport.lwm2m") public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index bf30723ce0..f75aca52c3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -17,21 +17,23 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.DecoderException; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentials; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer; @@ -47,6 +49,7 @@ import static org.eclipse.leshan.core.SecurityMode.PSK; import static org.eclipse.leshan.core.SecurityMode.RPK; import static org.eclipse.leshan.core.SecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP; +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; @Slf4j @Component @@ -66,17 +69,16 @@ public class LwM2mCredentialsSecurityInfoValidator { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { log.trace("Validated credentials: [{}] [{}]", credentialsId, msg); - String credentialsBody = msg.getCredentials(); - resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue); - resultSecurityStore[0].setMsg(msg); - resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile()); + resultSecurityStore[0] = createSecurityInfo(credentialsId, msg, keyValue); latch.countDown(); } @Override public void onError(Throwable e) { log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e); - resultSecurityStore[0] = createSecurityInfo(credentialsId, null, null); + TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo(); + result.setEndpoint(credentialsId); + resultSecurityStore[0] = result; latch.countDown(); } }); @@ -87,51 +89,46 @@ public class LwM2mCredentialsSecurityInfoValidator { } TbLwM2MSecurityInfo securityInfo = resultSecurityStore[0]; - - if (securityInfo.getSecurityMode() == null) { + if ((CLIENT.equals(keyValue) && securityInfo.getSecurityMode() == null)) { throw new LwM2MAuthException(); } - return securityInfo; } /** * Create new SecurityInfo - * @param endpoint - - * @param jsonStr - - * @param keyValue - + * * @return SecurityInfo */ - private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTypeServer keyValue) { + private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, ValidateDeviceCredentialsResponse msg, LwM2mTypeServer keyValue) { TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo(); - LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class); + LwM2MClientCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MClientCredentials.class); if (credentials != null) { + result.setMsg(msg); + result.setDeviceProfile(msg.getDeviceProfile()); + result.setEndpoint(credentials.getClient().getEndpoint()); +// if ((keyValue.equals(CLIENT))) { + switch (credentials.getClient().getSecurityConfigClientMode()) { + case NO_SEC: + createClientSecurityInfoNoSec(result); + break; + case PSK: + createClientSecurityInfoPSK(result, endpoint, credentials.getClient()); + break; + case RPK: + createClientSecurityInfoRPK(result, endpoint, credentials.getClient()); + break; + case X509: + createClientSecurityInfoX509(result, endpoint, credentials.getClient()); + break; + default: + break; + } +// } else if (keyValue.equals(BOOTSTRAP)) { - result.setBootstrapCredentialConfig(credentials.getBootstrap()); - if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { - PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient(); - endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint; - } - result.setEndpoint(endpoint); - result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode()); - } else { - result.setEndpoint(credentials.getClient().getEndpoint()); - switch (credentials.getClient().getSecurityConfigClientMode()) { - case NO_SEC: - createClientSecurityInfoNoSec(result); - break; - case PSK: - createClientSecurityInfoPSK(result, endpoint, credentials.getClient()); - break; - case RPK: - createClientSecurityInfoRPK(result, endpoint, credentials.getClient()); - break; - case X509: - createClientSecurityInfoX509(result, endpoint, credentials.getClient()); - break; - default: - break; - } + LwM2MBootstrapConfig bootstrapCredentialConfig = new LwM2MBootstrapConfig(((Lwm2mDeviceProfileTransportConfiguration) msg.getDeviceProfile().getProfileData().getTransportConfiguration()).getBootstrap(), + credentials.getBootstrap().getBootstrapServer(), credentials.getBootstrap().getLwm2mServer()); + result.setBootstrapCredentialConfig(bootstrapCredentialConfig); } } return result; @@ -142,18 +139,18 @@ public class LwM2mCredentialsSecurityInfoValidator { result.setSecurityMode(NO_SEC); } - private void createClientSecurityInfoPSK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { - PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; + private void createClientSecurityInfoPSK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) { + PSKClientCredential pskConfig = (PSKClientCredential) clientCredentialsConfig; if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { - if (pskConfig.getDecodedKey() != null && pskConfig.getDecodedKey().length > 0) { + if (pskConfig.getDecoded() != null && pskConfig.getDecoded().length > 0) { endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; if (endpoint != null && !endpoint.isEmpty()) { - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getDecodedKey())); + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getDecoded())); result.setSecurityMode(PSK); } } - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | DecoderException e) { log.error("Missing PSK key: " + e.getMessage()); } } else { @@ -161,22 +158,22 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { - RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; + private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) { + RPKClientCredential rpkConfig = (RPKClientCredential) clientCredentialsConfig; try { - if (rpkConfig.getDecodedKey() != null) { - PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getDecodedKey()); + if (rpkConfig.getDecoded() != null) { + PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getDecoded()); result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); result.setSecurityMode(RPK); } else { log.error("Missing RPK key"); } - } catch (IllegalArgumentException | IOException | GeneralSecurityException e) { + } catch (IllegalArgumentException | IOException | GeneralSecurityException | DecoderException e) { log.error("RPK: Invalid security info content: " + e.getMessage()); } } - private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index bd08aa44eb..a836eae27e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; @@ -30,6 +31,8 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; +import java.util.Arrays; + @Component @RequiredArgsConstructor @TbLwM2mTransportComponent @@ -57,13 +60,18 @@ public class TbLwM2MAuthorizer implements Authorizer { } // If session info is not found, this may be the trusted certificate, so we still need to check all other options below. } - SecurityInfo expectedSecurityInfo = null; - try { - expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); - } catch (LwM2MAuthException e) { - log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint()); - return null; - } + SecurityInfo expectedSecurityInfo; + try { + expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + if (expectedSecurityInfo != null && expectedSecurityInfo.usePSK() && expectedSecurityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString()) + && expectedSecurityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString()) + && Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), expectedSecurityInfo.getPreSharedKey())) { + expectedSecurityInfo = null; + } + } catch (LwM2MAuthException e) { + log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint()); + return null; + } if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) { return registration; } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index 1837093e8c..bae7522c78 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -29,19 +29,21 @@ import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier; import org.eclipse.californium.scandium.util.ServerNames; +import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; -import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentials; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; @@ -114,24 +116,35 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer cert.checkValidity(); } + + TbLwM2MSecurityInfo securityInfo = null; + // verify if trust + if (config.getTrustSslCredentials().getTrustedCertificates().length > 0) { + if (verifyIssuer(cert, config.getTrustSslCredentials().getTrustedCertificates()) != null) { + String endpoint = config.getTrustSslCredentials().getValueFromSubjectNameByKey(cert.getSubjectX500Principal().getName(), "CN"); + securityInfo = StringUtils.isNotEmpty(endpoint) ? securityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, CLIENT) : null; + } + } + // if not trust or cert trust securityInfo == null String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - TbLwM2MSecurityInfo securityInfo; - try { - securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT); - } catch (LwM2MAuthException e) { - securityInfo = null; + if (securityInfo == null) { + try { + securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT); + } catch (LwM2MAuthException e) { + log.trace("Failed find security info: {}", sha3Hash, e); + } } ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { - LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); + LwM2MClientCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MClientCredentials.class); if (!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)) { continue; } - X509ClientCredentials config = (X509ClientCredentials) credentials.getClient(); + X509ClientCredential config = (X509ClientCredential) credentials.getClient(); String certBody = config.getCert(); String endpoint = config.getEndpoint(); - if (strCert.equals(certBody)) { + if (StringUtils.isBlank(certBody) || strCert.equals(certBody)) { x509CredentialsFound = true; DeviceProfile deviceProfile = msg.getDeviceProfile(); if (msg.hasDeviceInfo() && deviceProfile != null) { @@ -179,4 +192,32 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer public void setResultHandler(HandshakeResultHandler resultHandler) { } + + private X509Certificate verifyIssuer(X509Certificate certificate, X509Certificate[] certificates) { + String issuerCN = config.getTrustSslCredentials().getValueFromSubjectNameByKey(certificate.getIssuerX500Principal().getName(), "CN"); + if (!StringUtils.isBlank(issuerCN)) { + for (int index = 0; index < certificates.length; ++index) { + X509Certificate trust = certificates[index]; + String trustCN = config.getTrustSslCredentials().getValueFromSubjectNameByKey(trust.getSubjectX500Principal().getName(), "CN"); + if (!StringUtils.isBlank(trustCN) && issuerCN.length() >= trustCN.length() && issuerCN.substring(issuerCN.length()-trustCN.length()).equals(trustCN)) { + if (verifyCertificate(certificate)) { + return certificate; + } + } + } + } + return null; + } + + private static boolean verifyCertificate(X509Certificate certificate) { + try { + // date + certificate.checkValidity(); + // Validate X509. + SecurityUtil.certificate.decode(certificate.getEncoded()); + return true; + } catch (Exception e) { + return false; + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java index bc45a77b58..fe5ede1bda 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java @@ -28,12 +28,13 @@ import java.io.Serializable; @Data public class TbLwM2MSecurityInfo implements Serializable { private ValidateDeviceCredentialsResponse msg; + private DeviceProfile deviceProfile; + private String endpoint; private SecurityInfo securityInfo; private SecurityMode securityMode; + /** bootstrap */ - private DeviceProfile deviceProfile; private LwM2MBootstrapConfig bootstrapCredentialConfig; - private String endpoint; private BootstrapConfig bootstrapConfig; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentials.java similarity index 89% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentials.java index bbc733b40b..a10e5ec231 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentials.java @@ -16,11 +16,11 @@ package org.thingsboard.server.transport.lwm2m.secure.credentials; import lombok.Data; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data -public class LwM2MCredentials { - private LwM2MClientCredentials client; +public class LwM2MClientCredentials { + private LwM2MClientCredential client; private LwM2MBootstrapConfig bootstrap; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java index f23b84488b..5aed494934 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java @@ -16,11 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.core.californium.LwM2mCoapResource; -import org.thingsboard.server.common.transport.TransportServiceCallback; @Slf4j public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 147e20c02b..6b2a4a8ceb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; -import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; @@ -34,7 +34,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PreDestroy; @@ -44,7 +44,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RESOURCE; @Slf4j @@ -59,7 +59,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final OtaPackageDataCache otaPackageDataCache; - private final DefaultLwM2MUplinkMsgHandler handler; + private final DefaultLwM2mUplinkMsgHandler handler; private final CaliforniumRegistrationStore registrationStore; private final TbSecurityStore securityStore; private final TbLwM2MDtlsCertificateVerifier certificateVerifier; @@ -105,9 +105,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { LeshanServerBuilder builder = new LeshanServerBuilder(); builder.setLocalAddress(config.getHost(), config.getPort()); builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); - builder.setDecoder(new DefaultLwM2mNodeDecoder()); + builder.setDecoder(new DefaultLwM2mDecoder()); /* Use a magic converter to support bad type send by the UI. */ - builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); + builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); /* Create CoAP Config */ builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java similarity index 99% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java index 2b488a3ab9..2df90cd1ea 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java @@ -22,7 +22,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; -public class LwM2mNetworkConfig { +public class LwM2MNetworkConfig { public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { NetworkConfig coapConfig = new NetworkConfig(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MOperationType.java similarity index 80% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MOperationType.java index 6cd204299a..48eb300c8b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MOperationType.java @@ -20,7 +20,7 @@ import lombok.Getter; /** * Define the behavior of a write request. */ -public enum LwM2mOperationType { +public enum LwM2MOperationType { READ(0, "Read", true), READ_COMPOSITE(1, "ReadComposite", false, true), @@ -50,14 +50,14 @@ public enum LwM2mOperationType { WRITE_COMPOSITE(14, "WriteComposite", false, true), WRITE_ATTRIBUTES(15, "WriteAttributes", true), DELETE(16, "Delete", true), + CREATE(17, "Create", true); + // only for RPC - future +// FW_UPDATE(18, "FirmwareUpdate", false), +// FW_READ_INFO(19, "FirmwareReadInfo", false), +// SW_UPDATE(20, "SoftwareUpdate", false), +// SW_READ_INFO(21, "SoftwareReadInfo", false), +// SW_UNINSTALL(22, "SoftwareUninstall", false); - // only for RPC - FW_UPDATE(17, "FirmwareUpdate", false); - -// FW_READ_INFO(18, "FirmwareReadInfo"), -// SW_READ_INFO(19, "SoftwareReadInfo"), -// SW_UPDATE(20, "SoftwareUpdate"), -// SW_UNINSTALL(21, "SoftwareUninstall"); @Getter private final int code; @@ -69,22 +69,22 @@ public enum LwM2mOperationType { @Getter private final boolean composite; - LwM2mOperationType(int code, String type, boolean hasObjectId) { + LwM2MOperationType(int code, String type, boolean hasObjectId) { this(code, type, hasObjectId, false); } - LwM2mOperationType(int code, String type, boolean hasObjectId, boolean composite) { + LwM2MOperationType(int code, String type, boolean hasObjectId, boolean composite) { this.code = code; this.type = type; this.hasObjectId = hasObjectId; this.composite = composite; - if(hasObjectId && composite){ + if (hasObjectId && composite) { throw new IllegalArgumentException("Can't set both Composite and hasObjectId for the same operation!"); } } - public static LwM2mOperationType fromType(String type) { - for (LwM2mOperationType to : LwM2mOperationType.values()) { + public static LwM2MOperationType fromType(String type) { + for (LwM2MOperationType to : LwM2MOperationType.values()) { if (to.type.equals(type)) { return to; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 2ece617666..a2797487a8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -27,7 +27,7 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import java.util.Collection; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; @Slf4j public class LwM2mServerListener { @@ -40,7 +40,7 @@ public class LwM2mServerListener { public final RegistrationListener registrationListener = new RegistrationListener() { /** - * Register – запрос, представленный в виде POST /rd?… + * Register – query represented as POST /rd?… */ @Override public void registered(Registration registration, Registration previousReg, @@ -49,7 +49,7 @@ public class LwM2mServerListener { } /** - * Update – представляет из себя CoAP POST запрос на URL, полученный в ответ на Register. + * Update – query represented as CoAP POST request for the URL received in response to Register. */ @Override public void updated(RegistrationUpdate update, Registration updatedRegistration, @@ -58,7 +58,7 @@ public class LwM2mServerListener { } /** - * De-register (CoAP DELETE) – отправляется клиентом в случае инициирования процедуры выключения. + * De-register (CoAP DELETE) – Sent by the client when a shutdown procedure is initiated. */ @Override public void unregistered(Registration registration, Collection observations, boolean expired, @@ -98,7 +98,9 @@ public class LwM2mServerListener { @Override public void onError(Observation observation, Registration registration, Exception error) { - log.error("Unable to handle notification of [{}:{}]", observation.getRegistrationId(), observation.getPath(), error); + if (error != null) { + log.debug("Unable to handle notification of [{}:{}] [{}]", observation.getRegistrationId(), observation.getPath(), error.getMessage()); + } } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java index bde1725807..4182a5ecf5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java @@ -138,8 +138,8 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { UUID currentId = UUID.fromString(idStr); Response response = new Response(CoAP.ResponseCode.CONTENT); byte[] otaData = this.getOtaData(currentId); - log.debug("Read ota data (length): [{}]", otaData.length); - if (otaData.length > 0) { + if (otaData != null && otaData.length > 0) { + log.debug("Read ota data (length): [{}]", otaData.length); response.setPayload(otaData); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); @@ -150,6 +150,8 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { log.trace("With block1 Send currentId: [{}], length: [{}], ", currentId.toString(), otaData.length); } exchange.respond(response); + } else { + log.trace("Ota packaged currentId: [{}] is not found.", currentId.toString()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index f3b35004f9..136f95dd35 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import com.google.gson.JsonParser; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.DDFFileParser; @@ -50,6 +51,7 @@ import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType. public class LwM2mTransportServerHelper { private final LwM2mTransportContext context; + private final static JsonParser JSON_PARSER = new JsonParser(); public void sendParametersOnThingsboardAttribute(List result, SessionInfoProto sessionInfo) { PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder(); @@ -229,7 +231,11 @@ public class LwM2mTransportServerHelper { case STRING_V: return kv.getStringV(); case JSON_V: - return kv.getJsonV(); + try { + return JSON_PARSER.parse(kv.getJsonV()); + } catch (Exception e) { + return null; + } } return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java index 9c192ae801..fbda86629a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java @@ -18,18 +18,18 @@ package org.thingsboard.server.transport.lwm2m.server.adaptors; import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import java.util.Collection; import java.util.Random; @Slf4j @Component("LwM2MJsonAdaptor") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +@TbLwM2mTransportComponent public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor { @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java index 8cba658859..d054354035 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java @@ -17,11 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.attributes; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import com.google.gson.JsonElement; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.springframework.stereotype.Service; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -30,16 +33,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponse import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.ArrayList; @@ -47,11 +49,24 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_TAG; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_TITLE; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_URL; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_TAG; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_URL; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_WARN; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.compareAttNameKeyOta; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromJson; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.valueEquals; @Slf4j @Service @@ -71,6 +86,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { private final LwM2mDownlinkMsgHandler downlinkHandler; private final LwM2MTelemetryLogService logService; private final LwM2MOtaUpdateService otaUpdateService; + private final LwM2mModelProvider modelProvider; @Override public ListenableFuture> getSharedAttributes(LwM2mClient client, Collection keys) { @@ -129,23 +145,25 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { List otherAttributes = new ArrayList<>(); for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) { String attrName = tsKvProto.getKv().getKey(); - if (DefaultLwM2MOtaUpdateService.FIRMWARE_TITLE.equals(attrName)) { - newFirmwareTitle = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) { - newFirmwareVersion = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_TAG.equals(attrName)) { - newFirmwareTag = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) { - newFirmwareUrl = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) { - newSoftwareTitle = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) { - newSoftwareVersion = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TAG.equals(attrName)) { - newSoftwareTag = getStrValue(tsKvProto); - } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { - newSoftwareUrl = getStrValue(tsKvProto); - }else { + if (compareAttNameKeyOta(attrName)) { + if (FIRMWARE_TITLE.equals(attrName)) { + newFirmwareTitle = getStrValue(tsKvProto); + } else if (FIRMWARE_VERSION.equals(attrName)) { + newFirmwareVersion = getStrValue(tsKvProto); + } else if (FIRMWARE_TAG.equals(attrName)) { + newFirmwareTag = getStrValue(tsKvProto); + } else if (FIRMWARE_URL.equals(attrName)) { + newFirmwareUrl = getStrValue(tsKvProto); + } else if (SOFTWARE_TITLE.equals(attrName)) { + newSoftwareTitle = getStrValue(tsKvProto); + } else if (SOFTWARE_VERSION.equals(attrName)) { + newSoftwareVersion = getStrValue(tsKvProto); + } else if (SOFTWARE_TAG.equals(attrName)) { + newSoftwareTag = getStrValue(tsKvProto); + } else if (SOFTWARE_URL.equals(attrName)) { + newSoftwareUrl = getStrValue(tsKvProto); + } + } else { otherAttributes.add(tsKvProto); } } @@ -156,7 +174,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag)); } if (!otherAttributes.isEmpty()) { - onAttributesUpdate(lwM2MClient, otherAttributes); + onAttributesUpdate(lwM2MClient, otherAttributes, true); } } else if (lwM2MClient == null) { log.error("OnAttributeUpdate, lwM2MClient is null"); @@ -169,42 +187,95 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { * => send to client Request Update of value (new value from shared attribute) * and LwM2MClient.delayedRequests.add(path) * #2.1 if there is not a difference in values between the current resource values and the shared attribute values - * */ @Override - public void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos) { + public void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos, boolean logFailedUpdateOfNonChangedValue) { log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); + Map attributesUpdate = new ConcurrentHashMap<>(); tsKvProtos.forEach(tsKvProto -> { - String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); - if (pathIdVer != null) { - // #1.1 - if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { - if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { + try { + String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); + if (pathIdVer != null) { + // #1.1 + if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { + if (tsKvProto.getTs() >= lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { + lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); + attributesUpdate.put(pathIdVer, tsKvProto); + } + } else { lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); + attributesUpdate.put(pathIdVer, tsKvProto); } - } else { - lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); } + } catch (IllegalArgumentException e) { + log.error("Failed update resource [" + lwM2MClient.getEndpoint() + "] onAttributesUpdate:", e); + String logMsg = String.format("%s: Failed update resource onAttributesUpdate %s.", + LOG_LWM2M_ERROR, e.getMessage()); + logService.log(lwM2MClient, logMsg); } }); clientContext.update(lwM2MClient); // #2.1 - lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { - this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), - getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + attributesUpdate.forEach((pathIdVer, tsKvProto) -> { + ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, modelProvider); + Object newValProto = getValueFromKvProto(tsKvProto.getKv()); + Object oldResourceValue = this.getResourceValueFormatKv(lwM2MClient, pathIdVer); + if (!resourceModel.multiple || !(newValProto instanceof JsonElement)) { + this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, logFailedUpdateOfNonChangedValue); + } else { + try { + pushUpdateMultiToClientIfNeeded(lwM2MClient, resourceModel, (JsonElement) newValProto, + (Map) oldResourceValue, pathIdVer, logFailedUpdateOfNonChangedValue); + } catch (Exception e) { + log.error("Failed update resource [" + lwM2MClient.getEndpoint() + "] onAttributesUpdate:", e); + String logMsg = String.format("%s: Failed update resource onAttributesUpdate %s.", + LOG_LWM2M_ERROR, e.getMessage()); + logService.log(lwM2MClient, logMsg); + } + } }); } - private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { - if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { + private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object oldValue, Object newValue, + String versionedId, boolean logFailedUpdateOfNonChangedValue) { + if (newValue == null) { + String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. New value is bad", + LOG_LWM2M_ERROR, versionedId, "null"); + logService.log(lwM2MClient, logMsg); + log.error("Failed update resource [{}] [{}]", versionedId, "null"); + } else if ((oldValue == null) || !valueEquals(newValue, oldValue)) { TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); - } else { - log.error("Failed update resource [{}] [{}]", versionedId, newValue); - String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", - LOG_LWM2M_ERROR, versionedId, newValue); + } else if (logFailedUpdateOfNonChangedValue) { + String logMsg = String.format("%s: Didn't update the versionedId resource - %s value - %s. Value is not changed", + LOG_LWM2M_WARN, versionedId, newValue); logService.log(lwM2MClient, logMsg); - log.info("Failed update resource [{}] [{}]", versionedId, newValue); + log.warn("Didn't update resource [{}] [{}]. Value is not changed", versionedId, newValue); + } + } + + private void pushUpdateMultiToClientIfNeeded(LwM2mClient client, ResourceModel resourceModel, JsonElement newValProto, + Map valueOld, String versionedId, + boolean logFailedUpdateOfNonChangedValue) { + Map newValues = convertMultiResourceValuesFromJson(newValProto, resourceModel.type, versionedId); + if (newValues.size() > 0 && valueOld != null && valueOld.size() > 0) { + valueOld.values().forEach((v) -> { + if (newValues.containsKey(v.getId())) { + if (valueEquals(newValues.get(v.getId()), v.getValue())) { + newValues.remove(v.getId()); + } + } + }); + } + + if (newValues.size() > 0) { + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValues).timeout(this.config.getTimeout()).build(); + downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId)); + } else if (logFailedUpdateOfNonChangedValue) { + log.warn("Didn't update resource [{}] [{}]", versionedId, newValProto); + String logMsg = String.format("%s: Didn't update resource versionedId - %s value - %s. Value is not changed", + LOG_LWM2M_WARN, versionedId, newValProto); + logService.log(client, logMsg); } } @@ -213,12 +284,18 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { * @return - value of Resource into format KvProto or null */ private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { - LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); + LwM2mResource resourceValue = LwM2MTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); if (resourceValue != null) { ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); - return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, - new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); + if (!resourceValue.isMultiInstances()) { + return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); + } else if (resourceValue.getInstances().size() > 0) { + return resourceValue.getInstances(); + } else { + return null; + } } else { return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java index b9d7fa9e21..cce7a29c88 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java @@ -30,5 +30,5 @@ public interface LwM2MAttributesService { void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo); - void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos); + void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos, boolean logFailedUpdateOfNonChangedValue); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 331881a327..e1565d5be2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -22,11 +22,13 @@ import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.Device; @@ -42,7 +44,9 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -55,13 +59,14 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_OBJECT_VERSION_DEFAULT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.equalsResourceTypeGetSimpleName; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.getVerFromPathIdVerOrId; @Slf4j @EqualsAndHashCode(of = {"endpoint"}) @@ -99,11 +104,10 @@ public class LwM2mClient implements Serializable { @Getter private Long psmActivityTimer; @Getter - private Long edrxCycle; - @Getter private Long pagingTransmissionWindow; @Getter - @Setter + private Long edrxCycle; + @Getter private Registration registration; @Getter @Setter @@ -116,6 +120,10 @@ public class LwM2mClient implements Serializable { private boolean firstEdrxDownlink = true; + @Getter + private Set clientSupportContentFormats; + @Getter + private ContentFormat defaultContentFormat; @Getter private final AtomicInteger retryAttempts; @@ -149,6 +157,12 @@ public class LwM2mClient implements Serializable { this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow(); } + public void setRegistration(Registration registration) { + this.registration = registration; + this.clientSupportContentFormats = clientSupportContentFormat(registration); + this.defaultContentFormat = calculateDefaultContentFormat(registration); + } + public void lock() { lock.lock(); } @@ -212,15 +226,16 @@ public class LwM2mClient implements Serializable { .build(); } - public boolean saveResourceValue(String pathRezIdVer, LwM2mResource rez, LwM2mModelProvider modelProvider) { - if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) { - this.resources.get(pathRezIdVer).setLwM2mResource(rez); + public boolean saveResourceValue(String pathRezIdVer, LwM2mResource resource, LwM2mModelProvider modelProvider, Mode mode) { + if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null && + resourceEqualsModel(resource, this.resources.get(pathRezIdVer).getResourceModel())) { + this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode); return true; } else { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); - if (resourceModel != null) { - this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel)); + if (resourceModel != null && resourceEqualsModel(resource, resourceModel)) { + this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel)); return true; } else { return false; @@ -228,6 +243,11 @@ public class LwM2mClient implements Serializable { } } + private boolean resourceEqualsModel(LwM2mResource resource, ResourceModel resourceModel) { + return ((!resourceModel.multiple && resource instanceof LwM2mSingleResource) || + (resourceModel.multiple && resource instanceof LwM2mMultipleResource)); + } + public Object getResourceValue(String pathRezIdVer, String pathRezId) { String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; if (this.resources.get(pathRez) != null) { @@ -262,7 +282,7 @@ public class LwM2mClient implements Serializable { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(pathIdVer); - return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) + return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; } @@ -279,10 +299,11 @@ public class LwM2mClient implements Serializable { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(pathIdVer); - return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) + return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()) : null; } + public Collection getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverter converter) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); @@ -299,31 +320,64 @@ public class LwM2mClient implements Serializable { return resources; } + /** + * The instance must have all the resources that have the property + * Mandatory + */ public Collection getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverter converter) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; - resourceModels.forEach((resId, resourceModel) -> { - if (((Map) params).containsKey(String.valueOf(resId))) { - Object value = ((Map) params).get((String.valueOf(resId))); - resources.add(LwM2mSingleResource.newResource(resId, - converter.convertValue(value, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type)); - - } - }); + if (params instanceof Map && ((Map) params).size() > 0) { + Map paramsMap = (Map) params; + resourceModels.forEach((resourceId, resourceModel) -> { + if (paramsMap.containsKey(String.valueOf(resourceId))) { + Object value = paramsMap.get((String.valueOf(resourceId))); + LwM2mResource resource; + if (resourceModel.multiple) { + try { + Map values = convertMultiResourceValuesFromRpcBody(value, resourceModel.type, pathRezIdVer); + resource = LwM2mMultipleResource.newResource(resourceId, values, resourceModel.type); + } catch (Exception e) { + throw new IllegalArgumentException("Resource id=" + resourceId + ", class = " + + value.getClass().getSimpleName() + ", value = " + value + " is bad. Value of Multi-Instance Resource must be in Json format!"); + } + } else { + Object valueRez = value instanceof Integer ? ((Integer) value).longValue() : value; + resource = LwM2mSingleResource.newResource(resourceId, + converter.convertValue(valueRez, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type); + } + if (resource != null) { + resources.add(resource); + } else if (resourceModel.operations.isWritable() && resourceModel.mandatory) { + throw new IllegalArgumentException("Resource id=" + resourceId + " is mandatory. The value of this resource must not be null."); + } + } else if (resourceModel.operations.isWritable() && resourceModel.mandatory) { + throw new IllegalArgumentException("Resource id=" + resourceId + " is mandatory. The value of this resource must not be null."); + } + }); + } else if (params == null) { + throw new IllegalArgumentException("The value of this resource must not be null."); + } else { + throw new IllegalArgumentException("The value of this resource must be in Map format and size > 0"); + } return resources; } - public void isValidObjectVersion(String path) { + public String isValidObjectVersion(String path) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); - String verRez = getVerFromPathIdVerOrId(path); - if ((verRez != null && !verRez.equals(verSupportedObject)) || - (verRez == null && !LWM2M_OBJECT_VERSION_DEFAULT.equals(verSupportedObject))) { - throw new IllegalArgumentException(String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject)); + if (verSupportedObject == null) { + return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId()); + } else { + String verRez = getVerFromPathIdVerOrId(path); + if (verRez == null || !verRez.equals(verSupportedObject)) { + return String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject); + } } + return ""; } /** @@ -365,12 +419,27 @@ public class LwM2mClient implements Serializable { .collect(Collectors.toSet()); } - public ContentFormat getDefaultContentFormat() { + private ContentFormat calculateDefaultContentFormat(Registration registration) { if (registration == null) { return ContentFormat.DEFAULT; - } else { - return LwM2mVersion.fromVersionStr(registration.getLwM2mVersion()).getContentFormat(); + } else{ + return LwM2mVersion.fromVersion(registration.getLwM2mVersion()).getContentFormat(); + } + } + + static private Set clientSupportContentFormat(Registration registration) { + Set contentFormats = new HashSet<>(); + contentFormats.add(ContentFormat.DEFAULT); + String code = Arrays.stream(registration.getObjectLinks()).filter(link -> link.getUrl().equals("/")).findFirst().get().getAttributes().get("ct"); + if (code != null) { + Set codes = Stream.of(code.replaceAll("\"", "").split(" ", -1)) + .map(String::trim) + .map(Integer::parseInt) + .map(ContentFormat::fromCode) + .collect(Collectors.toSet()); + contentFormats.addAll(codes); } + return contentFormats; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index 4df0dc5ec2..239becdda3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -59,8 +59,6 @@ public interface LwM2mClientContext { void sendMsgsAfterSleeping(LwM2mClient lwM2MClient); - boolean isComposite(LwM2mClient client); - void onUplink(LwM2mClient client); Long getRequestTimeout(LwM2mClient client); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 246444ddaa..d5dfbf0eca 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.springframework.beans.factory.annotation.Autowired; @@ -26,8 +25,8 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.PowerMode; -import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -36,17 +35,15 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; -import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfigService; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.session.LwM2MSessionManager; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MClientStore; import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil; import java.util.Arrays; import java.util.Collection; @@ -60,9 +57,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import static org.eclipse.leshan.core.SecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; @Slf4j @Service @@ -76,12 +71,11 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { private final TbLwM2MClientStore clientStore; private final LwM2MSessionManager sessionManager; private final TransportDeviceProfileCache deviceProfileCache; - private final LwM2mVersionedModelProvider modelProvider; private final LwM2MModelConfigService modelConfigService; @Autowired @Lazy - private DefaultLwM2MUplinkMsgHandler defaultLwM2MUplinkMsgHandler; + private DefaultLwM2mUplinkMsgHandler defaultLwM2MUplinkMsgHandler; @Autowired @Lazy private LwM2MOtaUpdateService otaUpdateService; @@ -235,10 +229,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { throw new LwM2MClientStateException(client.getState(), "Client is in invalid state."); } client.setRegistration(registration); - onUplink(client); - if (compareAndSetSleepFlag(client, false)) { - sendMsgsAfterSleeping(client); - } else { + if (!awake(client)) { clientStore.put(client); } } finally { @@ -298,11 +289,14 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName) { Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getProfileId()); - - return profile.getObserveAttr().getKeyName().entrySet().stream() - .filter(e -> e.getValue().equals(keyName) && validateResourceInModel(client, e.getKey(), false)).findFirst().orElseThrow( - () -> new IllegalArgumentException(keyName + " is not configured in the device profile!") - ).getKey(); + for (Map.Entry entry : profile.getObserveAttr().getKeyName().entrySet()) { + String k = entry.getKey(); + String v = entry.getValue(); + if (v.equals(keyName) && client.isValidObjectVersion(k).isEmpty()) { + return k; + } + } + throw new IllegalArgumentException(keyName + " is not configured in the device profile!"); } public Registration getRegistration(String registrationId) { @@ -337,7 +331,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { PowerMode powerMode = getPowerMode(lwM2MClient); if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) { modelConfigService.sendUpdates(lwM2MClient); - defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient); + defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient, false); TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg .newBuilder() .setSessionInfo(lwM2MClient.getSession()) @@ -390,7 +384,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public Lwm2mDeviceProfileTransportConfiguration profileUpdate(DeviceProfile deviceProfile) { - Lwm2mDeviceProfileTransportConfiguration clientProfile = LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile); + Lwm2mDeviceProfileTransportConfiguration clientProfile = LwM2MTransportUtil.toLwM2MClientProfile(deviceProfile); profiles.put(deviceProfile.getUuidId(), clientProfile); return clientProfile; } @@ -412,12 +406,6 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { return lwM2mClientsByRegistrationId.values().stream().filter(e -> deviceId.equals(e.getDeviceId())).findFirst().orElse(null); } - @Override - public boolean isComposite(LwM2mClient client) { - return LwM2mVersion.fromVersionStr(client.getRegistration().getLwM2mVersion()).isComposite() & - getProfile(client.getProfileId()).getClientLwM2mSettings().isCompositeOperationsSupport(); - } - @Override public boolean isDownlinkAllowed(LwM2mClient client) { PowerMode powerMode = client.getPowerMode(); @@ -430,7 +418,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { powerMode = PowerMode.DRX; } } - if (PowerMode.DRX.equals(powerMode)) { + if (PowerMode.DRX.equals(powerMode) || otaUpdateService.isOtaDownloading(client)) { return true; } client.lock(); @@ -514,7 +502,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { sleepTask.cancel(false); } Future task = context.getScheduler().schedule(() -> { - if (uplinkTime == client.getLastUplinkTime()) { + if (uplinkTime == client.getLastUplinkTime() && !otaUpdateService.isOtaDownloading(client)) { asleep(client); } return null; @@ -543,13 +531,4 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { return timeout; } - private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, modelProvider); - Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId(); - String objectVer = validateObjectVerFromKey(pathIdVer); - return resourceModel != null && (isWritableNotOptional ? - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java index 7f61caf58a..3a9ef3236b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java @@ -16,13 +16,20 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.request.WriteRequest.Mode; import java.io.Serializable; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; +@Slf4j @Data public class ResourceValue implements Serializable { @@ -32,32 +39,34 @@ public class ResourceValue implements Serializable { private TbResourceModel resourceModel; public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) { - this.lwM2mResource = toTbLwM2MResource(lwM2mResource); this.resourceModel = toTbResourceModel(resourceModel); + updateLwM2mResource(lwM2mResource, Mode.UPDATE); } - public void setLwM2mResource(LwM2mResource lwM2mResource) { - this.lwM2mResource = toTbLwM2MResource(lwM2mResource); + public void updateLwM2mResource(LwM2mResource lwM2mResource, Mode mode) { + if (lwM2mResource instanceof LwM2mSingleResource) { + this.lwM2mResource = new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType()); + } else if (lwM2mResource instanceof LwM2mMultipleResource) { + if (lwM2mResource.getInstances().values().size() > 0) { + Set instancesSet = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()); + if (Mode.REPLACE.equals(mode) && this.lwM2mResource != null) { + Map oldInstances = this.lwM2mResource.getInstances(); + oldInstances.values().forEach(v -> { + if (instancesSet.stream().noneMatch(vIns -> v.getId() == vIns.getId())){ + instancesSet.add(toTbLwM2MResourceInstance(v)); + } + }); + } + TbLwM2MResourceInstance[] instances = instancesSet.toArray(new TbLwM2MResourceInstance[0]); + this.lwM2mResource = new TbLwM2mMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); + } + } } public void setResourceModel(ResourceModel resourceModel) { this.resourceModel = toTbResourceModel(resourceModel); } - private static TbLwM2MResource toTbLwM2MResource(LwM2mResource lwM2mResource) { - if (lwM2mResource.isMultiInstances()) { - if ( lwM2mResource.getInstances().values().size() > 0) { - TbLwM2MResourceInstance [] instances = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()).toArray(new TbLwM2MResourceInstance[0]); - return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); - } - else { - return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), new TbLwM2MResourceInstance[0]); - } - } else { - return new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType()); - } - } - private static TbLwM2MResourceInstance toTbLwM2MResourceInstance(LwM2mResourceInstance instance) { return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java similarity index 82% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java index ad5431b383..dce642f336 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java @@ -17,16 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mResourceInstance; import java.io.Serializable; -import java.util.Collection; -public class TbLwM2MMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable { +public class TbLwM2mMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable { private static final long serialVersionUID = 4658477128628087186L; - public TbLwM2MMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) { + public TbLwM2mMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) { super(id, type, instances); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java index 1f1dff784f..52dd7221d1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.common; import org.thingsboard.common.util.ThingsBoardExecutors; -import javax.annotation.PreDestroy; import java.util.concurrent.ExecutorService; public abstract class LwM2MExecutorAwareService { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java index 561b103277..aba73f8d5d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR; @Slf4j public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRequestCallback { @@ -35,12 +35,12 @@ public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRe @Override public void onValidationError(String params, String msg) { log.trace("[{}] Request [{}] validation failed. Reason: {}", client.getEndpoint(), params, msg); - logService.log(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg)); + logService.log(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_ERROR, params, msg)); } @Override public void onError(String params, Exception e) { log.trace("[{}] Request [{}] processing failed", client.getEndpoint(), params, e); - logService.log(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e)); + logService.log(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_ERROR, params, e)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 2ec0122c28..170112c924 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -18,16 +18,20 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.ObjectLink; -import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.CompositeDownlinkRequest; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DownlinkRequest; @@ -40,7 +44,9 @@ import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.exception.ClientSleepingException; +import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.request.exception.TimeoutException; +import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -55,8 +61,7 @@ import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -76,7 +81,9 @@ import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -86,7 +93,13 @@ import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.STEP; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.createModelsDefault; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.getVerFromPathIdVerOrId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.validateVersionedId; @Slf4j @Service @@ -126,39 +139,44 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback) { validateVersionedId(client, request); - ReadRequest downlink = new ReadRequest(getRequestContentFormat(client, request, modelProvider), request.getObjectId()); + ReadRequest downlink = new ReadRequest(getRequestContentFormat(client, request.getVersionedId(), modelProvider), request.getObjectId()); sendSimpleRequest(client, downlink, request.getTimeout(), callback); } @Override - public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback) { - validateVersionedIds(client, request); - ContentFormat requestContentFormat = ContentFormat.SENML_JSON; - ContentFormat responseContentFormat = ContentFormat.SENML_JSON; - - ReadCompositeRequest downlink = new ReadCompositeRequest(requestContentFormat, responseContentFormat, request.getObjectIds()); - sendCompositeRequest(client, downlink, request.getTimeout(), callback); + public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, + DownlinkRequestCallback callback, ContentFormat compositeContentFormat) { + try { + ReadCompositeRequest downlink = new ReadCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds()); + sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); + } } @Override public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { - validateVersionedId(client, request); - LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); - if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { - ObserveRequest downlink; - ContentFormat contentFormat = getRequestContentFormat(client, request, modelProvider); - if (resultIds.isResource()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); - } else if (resultIds.isObjectInstance()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + try { + validateVersionedId(client, request); + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); + if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { + ObserveRequest downlink; + ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider); + if (resultIds.isResource()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + } else { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); + } + log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); + sendSimpleRequest(client, downlink, request.getTimeout(), callback); } else { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); + callback.onValidationError(resultIds.toString(), "Observation is already registered!"); } - log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } else { - callback.onValidationError(resultIds.toString(), "Observation is already registered!"); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); } } @@ -176,28 +194,50 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback) { - ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), modelProvider); - if (resourceModelExecute != null) { - ExecuteRequest downlink; - if (request.getParams() != null && !resourceModelExecute.multiple) { - downlink = new ExecuteRequest(request.getVersionedId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); + try { + validateVersionedId(client, request); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(request.getVersionedId())); + ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), modelProvider); + if (resourceModelExecute == null) { + LwM2mModel model = createModelsDefault(); + if (pathIds.isResource()) { + resourceModelExecute = model.getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); + } + } + if (resourceModelExecute == null) { + callback.onValidationError(request.toString(), "ResourceModel with " + request.getVersionedId() + + " is absent in system. Need ddd Lwm2m Model with id=" + pathIds.getObjectId() + " ver=" + + getVerFromPathIdVerOrId(request.getVersionedId()) + " to profile."); + } else if (resourceModelExecute.operations.isExecutable()) { + ExecuteRequest downlink; + if (request.getParams() != null && !resourceModelExecute.multiple) { + downlink = new ExecuteRequest(request.getObjectId(), (String) this.converter.convertValue(request.getParams(), + resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); + } else { + downlink = new ExecuteRequest(request.getObjectId()); + } + sendSimpleRequest(client, downlink, request.getTimeout(), callback); } else { - downlink = new ExecuteRequest(request.getVersionedId()); + callback.onValidationError(request.toString(), "Resource with " + request.getVersionedId() + " is not executable."); } - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } - else { - callback.onValidationError(toString(request), "Tenant hasn't such the TbResources: " + request.getVersionedId() + "!"); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); } } @Override public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback) { - sendSimpleRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); + try { + validateVersionedId(client, request); + sendSimpleRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); + } } @Override public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { + validateVersionedId(client, request); int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); callback.onSuccess(request, observeCancelCnt); } @@ -214,51 +254,92 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im sendSimpleRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); } + /** + * Example # 1: + * AttributeSet attributes = new AttributeSet(new Attribute(Attribute.MINIMUM_PERIOD, 10L), + * new Attribute(Attribute.MAXIMUM_PERIOD, 100L)); + * WriteAttributesRequest requestTest = new WriteAttributesRequest(3, 0, 14, attributes); + * sendSimpleRequest(client, requestTest, request.getTimeout(), callback); + *

+ * Example # 2 + * Dimension and Object version are read only attributes. + * addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); + * addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity()); + */ @Override public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback) { - validateVersionedId(client, request); - if (request.getAttributes() == null) { - throw new IllegalArgumentException("Attributes to write are not specified!"); + try { + validateVersionedId(client, request); + if (request.getAttributes() == null) { + throw new IllegalArgumentException("Attributes to write are not specified!"); + } + ObjectAttributes params = request.getAttributes(); + List attributes = new LinkedList<>(); + addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); + addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); + addAttribute(attributes, GREATER_THAN, params.getGt()); + addAttribute(attributes, LESSER_THAN, params.getLt()); + addAttribute(attributes, STEP, params.getSt()); + AttributeSet attributeSet = new AttributeSet(attributes); + sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(request.toString(), e.getMessage()); } - ObjectAttributes params = request.getAttributes(); - List attributes = new LinkedList<>(); -// Dimension and Object version are read only attributes. -// addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); -// addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity()); - addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); - addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); - addAttribute(attributes, GREATER_THAN, params.getGt()); - addAttribute(attributes, LESSER_THAN, params.getLt()); - addAttribute(attributes, STEP, params.getSt()); - AttributeSet attributeSet = new AttributeSet(attributes); - sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); } @Override public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback) { - ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), modelProvider); - if (resourceModelWrite != null) { - ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); - try { - LwM2mPath path = new LwM2mPath(request.getObjectId()); - WriteRequest downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, - path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } catch (Exception e) { - callback.onError(toString(request), e); + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + if (resultIds.isResource() || resultIds.isResourceInstance()) { + validateVersionedId(client, request); + ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), modelProvider); + if (resourceModelWrite != null) { + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, modelProvider); + try { + WriteRequest downlink = null; + String msgError = ""; + if (resourceModelWrite.multiple) { + try { + Map value = convertMultiResourceValuesFromRpcBody(request.getValue(), resourceModelWrite.type, request.getObjectId()); + downlink = new WriteRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), + value, resourceModelWrite.type); + } catch (Exception e) { + } + } + if (downlink == null) { + try { + downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, + resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), request.getValue()); + } catch (Exception e) { + msgError = "Resource id=" + resultIds.toString() + ", value = " + request.getValue() + + ", class = " + request.getValue().getClass().getSimpleName() + ". Format value is bad. Value for this Single Resource must be " + resourceModelWrite.type + "!"; + } + } + if (downlink != null) { + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } else { + callback.onValidationError(toString(request), msgError); + } + } catch (Exception e) { + callback.onError(toString(request), e); + } + } else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); } } else { - callback.onValidationError(toString(request), "Tenant hasn't such the TbResources: " + request.getVersionedId() + "!"); + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for Resource or ResourceInstance!"); } } @Override - public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, DownlinkRequestCallback callback) { - ContentFormat contentFormat = ContentFormat.SENML_JSON; + public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, + DownlinkRequestCallback callback, ContentFormat contentFormatComposite) { try { - WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormat, rpcWriteCompositeRequest.getNodes()); + WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormatComposite, rpcWriteCompositeRequest.getNodes()); //TODO: replace config.getTimeout(); - sendWriteCompositeRequest(client, downlink, config.getTimeout(), callback); + sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback); + } catch (InvalidRequestException e) { + callback.onValidationError(rpcWriteCompositeRequest.toString(), e.getMessage()); } catch (Exception e) { callback.onError(toString(rpcWriteCompositeRequest), e); } @@ -266,43 +347,111 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { - LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - if (resultIds.isResource()) { - /* - * send request: path = '/3/0' node == wM2mObjectInstance - * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} - **/ - Collection resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), modelProvider, this.converter); - ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), modelProvider); - if (resourceModelWrite != null) { - ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); - WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), - resultIds.getObjectInstanceId(), resources); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); - } - else { - callback.onValidationError(toString(request), "Tenant hasn't such the TbResources: " + request.getVersionedId() + " !"); + try { + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + if (resultIds.isObjectInstance() || resultIds.isResource()) { + validateVersionedId(client, request); + WriteRequest downlink = null; + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, modelProvider); + String msgError = ""; + if (resultIds.isObjectInstance()) { + /* + * params = "{\"id\":0,\"value\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" + * int rscId = resultIds.getObjectInstanceId(); + * contentFormat – Format of the payload (TLV or JSON). + */ + Collection resources = client.getNewResourcesForInstance(request.getVersionedId(), + request.getValue(), modelProvider, this.converter); + if (resources.size() > 0) { + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resources); + } else { + msgError = " No resources to update!"; + } + } else if (resultIds.isResource()) { + ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), modelProvider); + if (resourceModelWrite != null) { + if (resourceModelWrite.multiple) { + try { + Map value = convertMultiResourceValuesFromRpcBody(request.getValue(), resourceModelWrite.type, request.getObjectId()); + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resultIds.getResourceId(), + value, resourceModelWrite.type); + } catch (Exception e1) { + msgError = " Resource id=" + resultIds.toString() + + ", class = " + request.getValue().getClass().getSimpleName() + + ", value = " + request.getValue() + " is bad. " + + "Value of Multi-Instance Resource must be in Json format!"; + } + } + } else { + msgError = " Resource " + request.getVersionedId() + " is not configured in the device profile!"; + } + } + if (downlink != null) { + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + + ". This operation can only be used for ObjectInstance or Multi-Instance Resource !" + msgError); + } + } else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + + ". This operation can only be used for ObjectInstance or Resource (multiple)"); } - } else if (resultIds.isObjectInstance()) { - /* - * params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" - * int rscId = resultIds.getObjectInstanceId(); - * contentFormat – Format of the payload (TLV or JSON). - */ - Collection resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), modelProvider, this.converter); - if (resources.size() > 0) { - ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : ContentFormat.DEFAULT; - WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } catch (Exception e) { + callback.onValidationError(toString(request), e.getMessage()); + } + } + + public void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback callback) { + validateVersionedId(client, request); + CreateRequest downlink = null; + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + ObjectModel objectModel = client.getObjectModel(request.getVersionedId(), modelProvider); + // POST /{Object ID}/{Object Instance ID} && Resources is Mandatory + if (objectModel != null) { + if (objectModel.multiple) { + + // LwM2M CBOR, SenML CBOR, SenML JSON, or TLV (see [LwM2M-CORE]) + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, modelProvider); + if (resultIds.isObject() || resultIds.isObjectInstance()) { + Collection resources; + if (resultIds.isObject()) { + if (request.getValue() != null) { + resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), modelProvider, this.converter); + downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), resources); + } else if (request.getNodes() != null && request.getNodes().size() > 0) { + Set instances = ConcurrentHashMap.newKeySet(); + request.getNodes().forEach((key, value) -> { + Collection resourcesForInstance = client.getNewResourcesForInstance(request.getVersionedId(), value, modelProvider, this.converter); + LwM2mObjectInstance instance = new LwM2mObjectInstance(Integer.parseInt(key), resourcesForInstance); + instances.add(instance); + }); + LwM2mObjectInstance[] instanceArrays = instances.toArray(new LwM2mObjectInstance[instances.size()]); + downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), instanceArrays); + } + + } else { + resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), modelProvider, this.converter); + LwM2mObjectInstance instance = new LwM2mObjectInstance(resultIds.getObjectInstanceId(), resources); + downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), instance); + } + } + if (downlink != null) { + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } else { + callback.onValidationError(toString(request), "Path " + request.getVersionedId() + + ". Object must be Multiple !"); + } } else { - callback.onValidationError(toString(request), "No resources to update!"); + throw new IllegalArgumentException("Path " + request.getVersionedId() + ". Object must be Multiple !"); } } else { - callback.onValidationError(toString(request), "Update of the root level object is not supported yet!"); + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + + " is not configured in the device profile!"); } } - private , T extends LwM2mResponse> void sendSimpleRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback callback) { sendRequest(client, request, timeoutInMs, callback, r -> request.getPath().toString()); } @@ -410,24 +559,6 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private void validateVersionedId(LwM2mClient client, HasVersionedId request) { - client.isValidObjectVersion(request.getVersionedId()); - if (request.getObjectId() == null) { - throw new IllegalArgumentException("Specified object id is null!"); - } - } - - private void validateVersionedIds(LwM2mClient client, HasVersionedIds request) { - for (String versionedId : request.getVersionedIds()) { - client.isValidObjectVersion(versionedId); - } - for (String objectId : request.getObjectIds()) { - if (objectId == null) { - throw new IllegalArgumentException("Specified object id is null!"); - } - } - } - private static void addAttribute(List attributes, String attributeName, T value) { addAttribute(attributes, attributeName, value, null, null); } @@ -442,52 +573,82 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { - switch (type) { - case BOOLEAN: - case STRING: - case TIME: - case INTEGER: - case FLOAT: - return client.getDefaultContentFormat(); - case OPAQUE: - return ContentFormat.OPAQUE; - case OBJLNK: - return ContentFormat.LINK; - default: + private static ContentFormat getReadRequestContentFormat(LwM2mClient client, T request, LwM2mModelProvider modelProvider) { + if (request.getRequestContentFormat().isPresent()) { + return request.getRequestContentFormat().get(); + } else { + return getRequestContentFormat(client, request.getVersionedId(), modelProvider); } - throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); } - private static ContentFormat getRequestContentFormat(LwM2mClient client, HasContentFormat request, LwM2mModelProvider modelProvider) { - if (request.getRequestContentFormat() != null) { - return request.getRequestContentFormat(); + private static ContentFormat getWriteRequestContentFormat(LwM2mClient client, TbLwM2MDownlinkRequest request, LwM2mModelProvider modelProvider) { + if (request instanceof TbLwM2MWriteReplaceRequest && ((TbLwM2MWriteReplaceRequest) request).getContentFormat() != null) { + return ((TbLwM2MWriteReplaceRequest) request).getContentFormat(); + } else if (request instanceof TbLwM2MWriteUpdateRequest && ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat() != null) { + return ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat(); } else { String versionedId = null; - if (request instanceof TbLwM2MReadRequest) { - versionedId = ((TbLwM2MReadRequest) request).getVersionedId(); - } else if (request instanceof TbLwM2MObserveRequest) { - versionedId = ((TbLwM2MObserveRequest) request).getVersionedId(); + if (request instanceof TbLwM2MWriteReplaceRequest) { + versionedId = ((TbLwM2MWriteReplaceRequest) request).getVersionedId(); + } else if (request instanceof TbLwM2MWriteUpdateRequest) { + versionedId = ((TbLwM2MWriteUpdateRequest) request).getVersionedId(); + } else if (request instanceof TbLwM2MCreateRequest) { + versionedId = ((TbLwM2MCreateRequest) request).getVersionedId(); + } + return getRequestContentFormat(client, versionedId, modelProvider); + } + } + + private static ContentFormat getRequestContentFormat(LwM2mClient client, String versionedId, LwM2mModelProvider modelProvider) { + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); + if (pathIds.isResource() || pathIds.isResourceInstance()) { + ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); + if (resourceModel != null && (pathIds.isResourceInstance() || (pathIds.isResource() && !resourceModel.multiple))) { + if (OBJLNK.equals(resourceModel.type)) { + return ContentFormat.LINK; + } else if (OPAQUE.equals(resourceModel.type)) { + return ContentFormat.OPAQUE; + } else { + return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); + } + } else { + return getContentFormatForComplex(client); } - String id = fromVersionedIdToObjectId(versionedId); - if (id != null && new LwM2mPath(id).isResource() && !client.isResourceMultiInstances(versionedId, modelProvider)) { - return client.getDefaultContentFormat(); + } else { + return getContentFormatForComplex(client); + } + } + + private static ContentFormat getContentFormatForComplex(LwM2mClient client) { + if (LwM2m.Version.V1_0.equals(client.getRegistration().getLwM2mVersion())) { + return ContentFormat.TLV; + } else if (LwM2m.Version.V1_1.equals(client.getRegistration().getLwM2mVersion())) { + ContentFormat result = findFirst(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON); + if (result != null) { + return result; } else { - return ContentFormat.DEFAULT; + throw new RuntimeException("The client does not support any of SenML CBOR, SenML JSON, TLV or JSON formats. Can't send complex requests. Try using singe-instance requests."); } + } else { + throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!"); } } + private static ContentFormat findFirst(Set supported, ContentFormat defaultValue, ContentFormat... desiredFormats) { + for (ContentFormat contentFormat : desiredFormats) { + if (supported.contains(contentFormat)) { + return contentFormat; + } + } + return defaultValue; + } + private String toString(R request) { try { - try { - return JacksonUtil.toString(request); - } catch (Exception e) { - return request.toString(); - } + return request != null ? request.toString() : ""; } catch (Exception e) { - log.warn("Failed to convert request to string", e); - return request != null ? request.getClass().getSimpleName() : ""; + log.debug("Failed to convert request to string", e); + return request.getClass().getSimpleName(); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java index f2618224e7..833ff76cab 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java @@ -17,9 +17,11 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.ContentFormat; +import java.util.Optional; + public interface HasContentFormat { - ContentFormat getRequestContentFormat(); + Optional getRequestContentFormat(); default ContentFormat getResponseContentFormat() { return null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java index fcbb195935..7e4c25a82d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java @@ -15,14 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil; public interface HasVersionedId { String getVersionedId(); default String getObjectId(){ - return LwM2mTransportUtil.fromVersionedIdToObjectId(getVersionedId()); + return LwM2MTransportUtil.fromVersionedIdToObjectId(getVersionedId()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedIds.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedIds.java index 5ccef5ecea..3dda62669f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedIds.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedIds.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -27,7 +27,7 @@ public interface HasVersionedIds { default String[] getObjectIds() { Set objectIds = ConcurrentHashMap.newKeySet(); for (String versionedId : getVersionedIds()) { - objectIds.add(LwM2mTransportUtil.fromVersionedIdToObjectId(versionedId)); + objectIds.add(LwM2MTransportUtil.fromVersionedIdToObjectId(versionedId)); } return objectIds.toArray(String[]::new); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index 871a59a63b..a4537af703 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.ExecuteRequest; @@ -25,6 +27,7 @@ import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -45,7 +48,7 @@ public interface LwM2mDownlinkMsgHandler { void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); - void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback); + void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); @@ -67,9 +70,10 @@ public interface LwM2mDownlinkMsgHandler { void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback); + void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); + void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java index f90e0f8b97..961d6db928 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO; @Slf4j public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java index f4e02e6222..4d80b0932e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest { @@ -30,8 +30,8 @@ public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.OBSERVE_CANCEL_ALL; + public LwM2MOperationType getType() { + return LwM2MOperationType.OBSERVE_CANCEL_ALL; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index 311be03462..9647519cf0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java @@ -16,12 +16,10 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO; @Slf4j public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java index cb4d056578..dd243e2a2e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -26,8 +26,8 @@ public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlink } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.OBSERVE_CANCEL; + public LwM2MOperationType getType() { + return LwM2MOperationType.OBSERVE_CANCEL; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java new file mode 100644 index 0000000000..ecc66c20f9 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.CreateResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; + +import java.util.Map; + +public class TbLwM2MCreateRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Getter + private final Object value; + @Getter + private final ContentFormat objectContentFormat; + @Getter + private final Map nodes; + + @Builder + private TbLwM2MCreateRequest(String versionedId, long timeout, Object value, ContentFormat objectContentFormat, Map nodes) { + super(versionedId, timeout); + this.value = value; + this.objectContentFormat = objectContentFormat; + this.nodes = nodes; + } + + @Override + public LwM2MOperationType getType() { + return LwM2MOperationType.CREATE; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java new file mode 100644 index 0000000000..316bbd4636 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.downlink; + +import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.response.CreateResponse; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +public class TbLwM2MCreateResponseCallback extends TbLwM2MUplinkTargetedCallback { + + public TbLwM2MCreateResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(handler, logService, client, targetId); + } + + @Override + public void onSuccess(CreateRequest request, CreateResponse response) { + super.onSuccess(request, response); + handler.onCreateResponseOk(client, versionedId, request); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java index 4c0a758572..2b3c544dae 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java @@ -19,7 +19,6 @@ import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.response.DeleteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java index b9649c0708..053106ca2d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MDeleteRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -27,8 +27,8 @@ public class TbLwM2MDeleteRequest extends AbstractTbLwM2MTargetedDownlinkRequest } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.DELETE; + public LwM2MOperationType getType() { + return LwM2MOperationType.DELETE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java index 291bdc907c..bf29a0c027 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest { @@ -30,8 +30,8 @@ public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.DISCOVER_ALL; + public LwM2MOperationType getType() { + return LwM2MOperationType.DISCOVER_ALL; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java index 41172f0599..cc855fd3a8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java @@ -17,9 +17,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java index 85aa75de3c..b4a5975ec4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -27,8 +27,8 @@ public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkReque } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.DISCOVER; + public LwM2MOperationType getType() { + return LwM2MOperationType.DISCOVER; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java index 3ea174c72d..43e532a380 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public interface TbLwM2MDownlinkRequest { - LwM2mOperationType getType(); + LwM2MOperationType getType(); long getTimeout(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java index 992cf6d616..9315e9ded6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java @@ -17,9 +17,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.response.ExecuteResponse; -import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java index 76cf8cd0eb..dbdc692f53 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MExecuteRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -32,8 +32,8 @@ public class TbLwM2MExecuteRequest extends AbstractTbLwM2MTargetedDownlinkReques } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.EXECUTE; + public LwM2MOperationType getType() { + return LwM2MOperationType.EXECUTE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java index 2aba4f0a58..4813d38181 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; import java.util.Set; @@ -32,8 +32,8 @@ public class TbLwM2MObserveAllRequest implements TbLwM2MDownlinkRequest { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java index 0d13b4a1ff..1c41a48b2f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java @@ -16,27 +16,30 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ObserveResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; + +import java.util.Optional; public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { - @Getter - private final ContentFormat requestContentFormat; + private final Optional requestContentFormat; @Builder private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { super(versionedId, timeout); - this.requestContentFormat = requestContentFormat; + this.requestContentFormat = Optional.ofNullable(requestContentFormat); } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.OBSERVE; + public LwM2MOperationType getType() { + return LwM2MOperationType.OBSERVE; } - + @Override + public Optional getRequestContentFormat() { + return this.requestContentFormat; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java index 59247b103b..033e9c12bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java @@ -16,8 +16,11 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.util.Hex; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; @@ -31,8 +34,25 @@ public class TbLwM2MReadCallback extends TbLwM2MUplinkTargetedCallback 0) { + String valueReplace = len + "Bytes"; + String valueStr = Hex.encodeHexString(valueInBytes); + return response.toString().replace(valueReplace, valueStr); + } + } + } + return response.toString(); + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java index 6f388237c2..5e5ffb163a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java @@ -16,27 +16,30 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; + +import java.util.Optional; public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { - @Getter - private final ContentFormat requestContentFormat; + private final Optional requestContentFormat; @Builder private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { super(versionedId, timeout); - this.requestContentFormat = requestContentFormat; + this.requestContentFormat = Optional.ofNullable(requestContentFormat); } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.READ; + public LwM2MOperationType getType() { + return LwM2MOperationType.READ; } - + @Override + public Optional getRequestContentFormat() { + return this.requestContentFormat; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java index 6b013ef9b5..cf33a5857f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java @@ -16,12 +16,16 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import java.util.Arrays; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO; @Slf4j public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MRequestCallback { @@ -44,9 +48,22 @@ public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MReque @Override public void onSuccess(R request, T response) { //TODO convert camelCase to "camel case" using .split("(? ResponseCode.CONTENT_CODE) { + log.error("[{}] [{}] [{}] failed to process successful response [{}] ", client.getEndpoint(), requestName, + versionedId != null ? versionedId : Arrays.toString(versionedIds), responseStr); + logService.log(client, String.format("[%s]: %s [%s] failed to process successful. Result: %s", LOG_LWM2M_ERROR, + requestName, versionedId != null ? versionedId : Arrays.toString(versionedIds), responseStr)); + } else { + log.trace("[{}] {} [{}] successful: {}", client.getEndpoint(), requestName, versionedId != null ? + versionedId : versionedIds, responseStr); + logService.log(client, String.format("[%s]: %s [%s] successful. Result: %s", LOG_LWM2M_INFO, requestName, + versionedId != null ? versionedId : Arrays.toString(versionedIds), responseStr)); + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java index e346ad965a..3e4847ce1e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java @@ -19,7 +19,6 @@ import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java index 6ef198a28f..7d0436b1b8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java @@ -18,8 +18,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.response.WriteAttributesResponse; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MWriteAttributesRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -33,8 +33,8 @@ public class TbLwM2MWriteAttributesRequest extends AbstractTbLwM2MTargetedDownli } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.WRITE_ATTRIBUTES; + public LwM2MOperationType getType() { + return LwM2MOperationType.WRITE_ATTRIBUTES; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java index f4b1059944..9760e13e5a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.WriteResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -36,8 +36,8 @@ public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkR } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.WRITE_REPLACE; + public LwM2MOperationType getType() { + return LwM2MOperationType.WRITE_REPLACE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java index 4746077f19..a452c3b187 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java @@ -15,12 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +@Slf4j public class TbLwM2MWriteResponseCallback extends TbLwM2MUplinkTargetedCallback { public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { @@ -30,7 +32,7 @@ public class TbLwM2MWriteResponseCallback extends TbLwM2MUplinkTargetedCallback< @Override public void onSuccess(WriteRequest request, WriteResponse response) { super.onSuccess(request, response); - handler.onWriteResponseOk(client, versionedId, request); + handler.onWriteResponseOk(client, versionedId, request, response.getCode().getCode()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java index a9465e082d..6e8708053b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.WriteResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; public class TbLwM2MWriteUpdateRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -36,8 +36,8 @@ public class TbLwM2MWriteUpdateRequest extends AbstractTbLwM2MTargetedDownlinkRe } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.WRITE_UPDATE; + public LwM2MOperationType getType() { + return LwM2MOperationType.WRITE_UPDATE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java index 43b55f371f..f9c4afcc40 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java @@ -19,13 +19,15 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ReadCompositeResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat; +import java.util.Optional; + public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest implements HasContentFormat { - @Getter - private final ContentFormat requestContentFormat; + + private final Optional requestContentFormatOpt; @Getter private final ContentFormat responseContentFormat; @@ -33,12 +35,17 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink @Builder private TbLwM2MReadCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) { super(versionedIds, timeout); - this.requestContentFormat = requestContentFormat; + this.requestContentFormatOpt = Optional.ofNullable(requestContentFormat); this.responseContentFormat = responseContentFormat; } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.READ_COMPOSITE; + public LwM2MOperationType getType() { + return LwM2MOperationType.READ_COMPOSITE; + } + + @Override + public Optional getRequestContentFormat() { + return this.requestContentFormatOpt; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteCompositeRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteCompositeRequest.java index 0e3ffaed28..c3fe137cae 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteCompositeRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteCompositeRequest.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.WriteCompositeResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; import org.thingsboard.server.transport.lwm2m.server.downlink.AbstractTbLwM2MTargetedDownlinkRequest; public class TbLwM2MWriteCompositeRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -37,8 +37,8 @@ public class TbLwM2MWriteCompositeRequest extends AbstractTbLwM2MTargetedDownlin } @Override - public LwM2mOperationType getType() { - return LwM2mOperationType.WRITE_REPLACE; + public LwM2MOperationType getType() { + return LwM2MOperationType.WRITE_REPLACE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteResponseCompositeCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteResponseCompositeCallback.java index 9dccc98185..6d5de06e90 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteResponseCompositeCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteResponseCompositeCallback.java @@ -31,7 +31,7 @@ public class TbLwM2MWriteResponseCompositeCallback extends TbLwM2MUplinkTargeted @Override public void onSuccess(WriteCompositeRequest request, WriteCompositeResponse response) { super.onSuccess(request, response); - handler.onWriteCompositeResponseOk(client, request); + handler.onWriteCompositeResponseOk(client, request, response.getCode().getCode()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java index eb5dae8f4a..214c7c2d5e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java @@ -21,9 +21,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_TELEMETRY; @Slf4j @Service diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java index 5c0acc150c..ba6f8bd5fb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java @@ -20,7 +20,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; import java.util.HashSet; import java.util.Map; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java index 9f0a84cbce..fd84b52af0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 2776b64fef..e52cdc9956 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -25,7 +25,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; import org.thingsboard.server.common.data.ota.OtaPackageKey; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; @@ -74,8 +74,10 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDA import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult.UPDATE_SUCCESSFULLY; +import static org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult.NOT_ENOUGH_STORAGE; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; @Slf4j @Service @@ -180,9 +182,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings(); - - onFirmwareStrategyUpdate(client, clientSettings); - onCurrentSoftwareStrategyUpdate(client, clientSettings); + initFwStrategy(client, clientSettings); + initSwStrategy(client, clientSettings); if (!attributesToFetch.isEmpty()) { var future = attributesService.getSharedAttributes(client, attributesToFetch); @@ -192,7 +193,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl Optional newFwVersion = getAttributeValue(attrs, FIRMWARE_VERSION); Optional newFwTag = getAttributeValue(attrs, FIRMWARE_TAG); Optional newFwUrl = getAttributeValue(attrs, FIRMWARE_URL); - if (newFwTitle.isPresent() && newFwVersion.isPresent()) { + if (newFwTitle.isPresent() && newFwVersion.isPresent() && !isOtaDownloading(client) && !UPDATING.equals(fwInfo.status)) { onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl, newFwTag); } } @@ -244,19 +245,27 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void onFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) { log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy()); + startFirmwareUpdateIfNeeded(client, initFwStrategy(client, configuration)); + } + + private LwM2MClientFwOtaInfo initFwStrategy(LwM2mClient client, OtherConfiguration configuration) { LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); fwInfo.setStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(configuration.getFwUpdateStrategy())); fwInfo.setBaseUrl(configuration.getFwUpdateResource()); - startFirmwareUpdateIfNeeded(client, fwInfo); + return fwInfo; } @Override public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) { log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy()); + startSoftwareUpdateIfNeeded(client, initSwStrategy(client, configuration)); + } + + private LwM2MClientSwOtaInfo initSwStrategy(LwM2mClient client, OtherConfiguration configuration) { LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client); swInfo.setStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(configuration.getSwUpdateStrategy())); swInfo.setBaseUrl(configuration.getSwUpdateResource()); - startSoftwareUpdateIfNeeded(client, swInfo); + return swInfo; } @Override @@ -307,6 +316,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (FirmwareUpdateResult.INITIAL.equals(result) && OtaPackageUpdateStatus.UPDATING.equals(fwInfo.getStatus())) { status = Optional.of(UPDATED); fwInfo.setRetryAttempts(0); + fwInfo.setFailedPackageId(null); } status.ifPresent(otaStatus -> { @@ -387,6 +397,22 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl startSoftwareUpdateIfNeeded(client, fwInfo); } + @Override + public boolean isOtaDownloading(LwM2mClient client) { + String endpoint = client.getEndpoint(); + LwM2MClientFwOtaInfo fwInfo = fwStates.get(endpoint); + LwM2MClientSwOtaInfo swInfo = swStates.get(endpoint); + + if (fwInfo != null && (DOWNLOADING.equals(fwInfo.getStatus()))) { + return true; + } + if (swInfo != null && (DOWNLOADING.equals(swInfo.getStatus()))) { + return true; + } + + return false; + } + private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientFwOtaInfo fwInfo) { try { if (!fwInfo.isSupported() && fwInfo.isAssigned()) { @@ -400,9 +426,12 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion()); startUpdateUsingBinary(client, fwInfo); } + } else if (fwInfo.getResult() != null && fwInfo.getResult().getCode() > UPDATE_SUCCESSFULLY.getCode()) { + log.trace("[{}] Previous update failed. [{}]", client.getEndpoint(), fwInfo); + logService.log(client, "Previous update firmware failed. Result: " + fwInfo.getResult().name()); } } catch (Exception e) { - log.info("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e); + log.error("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e); sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Internal server error: " + e.getMessage()); } } @@ -425,6 +454,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl startUpdateUsingBinary(client, swInfo); } } + } else if (swInfo.getResult() != null && swInfo.getResult().getCode() >= NOT_ENOUGH_STORAGE.getCode()) { + log.trace("[{}] Previous update failed. [{}]", client.getEndpoint(), swInfo); + logService.log(client, "Previous update software failed. Result: " + swInfo.getResult().name()); } } catch (Exception e) { log.info("[{}] failed to update client: {}", client.getEndpoint(), swInfo, e); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index b03fe81d28..4aa3776682 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota; -import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import java.util.Optional; @@ -55,4 +55,6 @@ public interface LwM2MOtaUpdateService { void onCurrentSoftwareStateUpdate(LwM2mClient lwM2MClient, Long value); void onCurrentSoftwareResultUpdate(LwM2mClient client, Long result); + + boolean isOtaDownloading(LwM2mClient client); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java index ddd04fbfd0..9d3c0b06f0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota.firmware; +import lombok.Getter; + /** * /** State R * 0: Idle (before downloading or after successful updating) @@ -28,8 +30,10 @@ public enum FirmwareUpdateState { DOWNLOADED(2, "Downloaded"), UPDATING(3, "Updating"); - public int code; - public String type; + @Getter + private int code; + @Getter + private String type; FirmwareUpdateState(int code, String type) { this.code = code; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java index 547d713251..e4b8336f92 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java @@ -20,12 +20,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java index 7bf5473a1b..6ceb263db5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java @@ -56,10 +56,12 @@ public enum SoftwareUpdateResult { INSTALL_FAILURE(58, "Software installation failure", true), UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true); - public int code; - public String type; @Getter - public boolean isAgain; + private int code; + @Getter + private String type; + @Getter + private boolean isAgain; SoftwareUpdateResult(int code, String type, boolean isAgain) { this.code = code; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java index 394d2e501a..c2e954f556 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota.software; +import lombok.Getter; + /** * SW Update State R * 0: INITIAL Before downloading. (see 5.1.2.1) @@ -33,8 +35,10 @@ public enum SoftwareUpdateState { DELIVERED(3, "Delivered"), INSTALLED(4, "Installed"); - public int code; - public String type; + @Getter + private int code; + @Getter + private String type; SoftwareUpdateState(int code, String type) { this.code = code; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index a728a0b6e0..9aae838cdb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -15,18 +15,25 @@ */ package org.thingsboard.server.transport.lwm2m.server.rpc; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MOperationType; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; @@ -34,6 +41,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllOb import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCreateRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCreateResponseCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest; @@ -60,10 +69,15 @@ import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcReadRespon import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; + @Slf4j @Service @TbLwM2mTransportComponent @@ -72,16 +86,16 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private final TransportService transportService; private final LwM2mClientContext clientContext; - private final LwM2MTransportServerConfig config; private final LwM2mUplinkMsgHandler uplinkHandler; private final LwM2mDownlinkMsgHandler downlinkHandler; private final LwM2MTelemetryLogService logService; + private final LwM2mModelProvider modelProvider; @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest, TransportProtos.SessionInfoProto sessionInfo) { log.debug("Received params: {}", rpcRequest.getParams()); try { - LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequest.getMethodName()); + LwM2MOperationType operationType = LwM2MOperationType.fromType(rpcRequest.getMethodName()); if (operationType == null) { this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED, "Unsupported operation type: " + rpcRequest.getMethodName()); return; @@ -105,7 +119,8 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } try { if (operationType.isHasObjectId()) { - String objectId = getIdFromParameters(client, rpcRequest); + LwM2MRpcRequestHeader header = JacksonUtil.fromString(rpcRequest.getParams(), LwM2MRpcRequestHeader.class); + String objectId = getIdFromParameters(client, header); switch (operationType) { case READ: sendReadRequest(client, rpcRequest, objectId); @@ -134,17 +149,21 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { case WRITE_REPLACE: sendWriteReplaceRequest(client, rpcRequest, objectId); break; + case CREATE: + sendCreateRequest(client, rpcRequest, objectId); + break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } } else if (operationType.isComposite()) { - if (clientContext.isComposite(client)) { + ContentFormat contentFormatComposite = this.getCompositeContentFormat(client); + if (contentFormatComposite != null) { switch (operationType) { case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequest); + sendReadCompositeRequest(client, rpcRequest, contentFormatComposite); break; case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequest); + sendWriteCompositeRequest(client, rpcRequest, contentFormatComposite); break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); @@ -164,8 +183,6 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { case DISCOVER_ALL: sendDiscoverAllRequest(client, rpcRequest); break; - case FW_UPDATE: - //TODO: implement and add break statement default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } @@ -186,12 +203,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendReadRequest(client, request, rpcCallback); } - private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { String[] versionedIds = getIdsFromParameters(client, requestMsg); TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds); - var rpcCallback = new RpcReadResponseCompositeCallback<>(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback); + var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback, contentFormatComposite); } private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -244,8 +261,28 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback); } + private void sendCreateRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + RpcCreateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcCreateRequest.class); + TbLwM2MCreateRequest.TbLwM2MCreateRequestBuilder builder = TbLwM2MCreateRequest.builder().versionedId(versionedId); + builder.value(requestBody.getValue()).nodes(requestBody.getNodes()).timeout(clientContext.getRequestTimeout(client)); + var mainCallback = new TbLwM2MCreateResponseCallback(uplinkHandler, logService, client, versionedId); + var rpcCallback = new RpcCreateResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendCreateRequest(client, builder.build(), rpcCallback); + } + private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); + LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); + if (path.isResource()) { + ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); + if (resourceModel != null && resourceModel.multiple) { + try { + Map value = convertMultiResourceValuesFromRpcBody(requestBody.getValue(), resourceModel.type, versionedId); + requestBody.setValue(value); + } catch (Exception e) { + } + } + } TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) .value(requestBody.getValue()) .timeout(clientContext.getRequestTimeout(client)).build(); @@ -262,11 +299,52 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { * nodes.put("/1/0/2", 100); * nodes.put("/5/0/1", "coap://localhost:5685"); */ - private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); - var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); - var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); + Map validNodes = validateNodes(client, rpcWriteCompositeRequest.getNodes()); + if (validNodes.size() > 0) { + rpcWriteCompositeRequest.setNodes(validNodes); + var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback, contentFormatComposite); + } else { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value", rpcWriteCompositeRequest.getNodes().toString())); + } + } + + private Map validateNodes(LwM2mClient client, Map nodes) { + Map newNodes = new LinkedHashMap<>(); + nodes.forEach((key, value) -> { + String versionedId; + try { + LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(key)); + if (path.isResource() || path.isResourceInstance()) { + versionedId = key; + } + else { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } + } catch (Exception e) { + versionedId = clientContext.getObjectIdByKeyNameFromProfile(client, key); + } + // validate value. Must be only primitive, not JsonObject or JsonArray + try { + JsonElement element = JsonUtils.parse(value.toString()); + if (!element.isJsonNull() && !element.isJsonPrimitive()) { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } + else if (versionedId != null) { + newNodes.put(fromVersionedIdToObjectId(versionedId), value); + } + } catch (JsonSyntaxException jse) { + if (versionedId != null) { + newNodes.put(fromVersionedIdToObjectId(versionedId), value); + } + } + }); + return newNodes; } private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -290,21 +368,20 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); } - private String getIdFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { - IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class); + private String getIdFromParameters(LwM2mClient client, LwM2MRpcRequestHeader header) { String targetId; - if (StringUtils.isNotEmpty(requestParams.getKey())) { - targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); - } else if (StringUtils.isNotEmpty(requestParams.getId())) { - targetId = requestParams.getId(); + if (StringUtils.isNotEmpty(header.getKey())) { + targetId = clientContext.getObjectIdByKeyNameFromProfile(client, header.getKey()); + } else if (StringUtils.isNotEmpty(header.getId())) { + targetId = header.getId(); } else { throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!"); } return targetId; } - private String[] getIdsFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { - RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), RpcReadCompositeRequest.class); + private String[] getIdsFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { + RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequest.getParams(), RpcReadCompositeRequest.class); if (requestParams.getKeys() != null && requestParams.getKeys().length > 0) { Set targetIds = ConcurrentHashMap.newKeySet(); for (String key : requestParams.getKeys()) { @@ -336,4 +413,16 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { log.info("[{}] toServerRpcResponse", toServerResponse); } + + private ContentFormat getCompositeContentFormat(LwM2mClient client) { + if (client.getClientSupportContentFormats().contains(ContentFormat.SENML_JSON)) { + return ContentFormat.SENML_JSON; + } + else if (client.getClientSupportContentFormats().contains(ContentFormat.SENML_CBOR)) { + return ContentFormat.SENML_CBOR; + } + else { + return null; + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java similarity index 92% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java index bef4fa37e8..996f248ea6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java @@ -20,8 +20,9 @@ import lombok.Data; @Data @JsonIgnoreProperties(ignoreUnknown = true) -public class IdOrKeyRequest { +public class LwM2MRpcRequestHeader { private String key; private String id; + private String contentFormat; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java index d2b7ffb834..c40d21cc4b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java @@ -21,7 +21,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java index 1d1f1de230..f7971783ac 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java @@ -16,19 +16,12 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import org.eclipse.leshan.core.ResponseCode; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mResource; -import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; -import java.util.Optional; - public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy { public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java similarity index 69% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java index 66bdfd1f4c..febd0f8e40 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.data.lwm2m; +package org.thingsboard.server.transport.lwm2m.server.rpc; import lombok.Data; +import lombok.EqualsAndHashCode; import java.util.Map; @Data -public class BootstrapConfiguration { +@EqualsAndHashCode(callSuper = true) +public class RpcCreateRequest extends LwM2MRpcRequestHeader { - //TODO: define the objects; - private Map servers; - private Map lwm2mServer; - private Map bootstrapServer; + private Object value; + private String contentFormat; + private Map nodes; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java new file mode 100644 index 0000000000..f27d8e89d7 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.rpc; + +import org.eclipse.leshan.core.request.LwM2mRequest; +import org.eclipse.leshan.core.response.CreateResponse; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public class RpcCreateResponseCallback, T extends CreateResponse> extends RpcLwM2MDownlinkCallback { + + public RpcCreateResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + @Override + protected Optional serializeSuccessfulResponse(T response) { + String value = response.getLocation() != null ? "location=" + response.getLocation() : ""; + return Optional.of(value); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java index ba8a634de0..936d177b2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java @@ -16,14 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import org.eclipse.leshan.core.Link; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.eclipse.leshan.core.response.ReadResponse; -import org.jetbrains.annotations.NotNull; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java index 82d880c206..115b3df90d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java @@ -15,18 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.rpc; -import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.ResponseCode; -import org.eclipse.leshan.core.response.DiscoverResponse; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; -import java.util.Optional; -import java.util.Set; - public class RpcLinkSetCallback extends RpcDownlinkRequestCallbackProxy { public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java index 0a5fa70b5b..453c1e361c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java @@ -24,7 +24,7 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.contentToString; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.contentToString; public class RpcReadResponseCallback, T extends ReadResponse> extends RpcLwM2MDownlinkCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java index 67642078c6..1dd3a4fd18 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java @@ -17,11 +17,11 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; @Data @EqualsAndHashCode(callSuper = true) -public class RpcWriteAttributesRequest extends IdOrKeyRequest { +public class RpcWriteAttributesRequest extends LwM2MRpcRequestHeader { private ObjectAttributes attributes; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java index 55fb2b49aa..36ad1be345 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java @@ -20,7 +20,7 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) -public class RpcWriteReplaceRequest extends IdOrKeyRequest { +public class RpcWriteReplaceRequest extends LwM2MRpcRequestHeader { private Object value; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java index b56fea7eb6..a38e7fb6c1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java @@ -17,11 +17,10 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; @Data @EqualsAndHashCode(callSuper = true) -public class RpcWriteUpdateRequest extends IdOrKeyRequest { +public class RpcWriteUpdateRequest extends LwM2MRpcRequestHeader { private Object value; private String contentFormat; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java index ccd4767fee..f4620e2bcf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java @@ -22,7 +22,7 @@ import lombok.Data; @JsonIgnoreProperties(ignoreUnknown = true) public class RpcReadCompositeRequest { - private String [] keys; - private String [] ids; + private String[] keys; + private String[] ids; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java index 11f6849410..f8a5e1c60c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java @@ -25,7 +25,7 @@ import org.thingsboard.server.transport.lwm2m.server.rpc.RpcLwM2MDownlinkCallbac import java.util.Optional; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.contentToString; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.contentToString; public class RpcReadResponseCompositeCallback, T extends ReadCompositeResponse> extends RpcLwM2MDownlinkCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java index 9c0b109082..0286e1556f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; @@ -48,9 +49,15 @@ public class TbInMemorySecurityStore implements TbEditableSecurityStore { readLock.lock(); try { TbLwM2MSecurityInfo securityInfo = securityByEp.get(endpoint); - if (securityInfo != null) { - return securityInfo.getSecurityInfo(); - } else { + if (securityInfo != null ) { + if (SecurityMode.NO_SEC.equals(securityInfo.getSecurityMode())) { + return SecurityInfo.newPreSharedKeyInfo(SecurityMode.NO_SEC.toString(), SecurityMode.NO_SEC.toString(), + SecurityMode.NO_SEC.toString().getBytes()); + } else { + return securityInfo.getSecurityInfo(); + } + } + else { return null; } } finally { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java index fcc588473a..f3b6629e8d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java @@ -15,21 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; -import org.eclipse.leshan.server.security.SecurityInfo; -import org.jetbrains.annotations.Nullable; -import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.integration.redis.util.RedisLockRegistry; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; -import java.util.concurrent.locks.Lock; - public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore { private static final String OTA_EP = "OTA#EP#"; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 1493123bc2..b8da8467e2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -726,7 +726,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto public synchronized void start() { if (!started) { started = true; - cleanerTask = schedExecutor.scheduleAtFixedRate(new TbLwM2mRedisRegistrationStore.Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS); + cleanerTask = schedExecutor.scheduleAtFixedRate(new Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index 54200be434..4990923ce8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.nustaq.serialization.FSTConfiguration; @@ -49,7 +50,13 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { if (data == null || data.length == 0) { return null; } else { - return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + if (SecurityMode.NO_SEC.equals(((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityMode())) { + return SecurityInfo.newPreSharedKeyInfo(SecurityMode.NO_SEC.toString(), SecurityMode.NO_SEC.toString(), + SecurityMode.NO_SEC.toString().getBytes()); + } + else { + return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + } } } finally { if (lock != null) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java index ac0d3d0e68..fd7061b9d7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -16,12 +16,15 @@ package org.thingsboard.server.transport.lwm2m.server.store; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.jetbrains.annotations.Nullable; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -46,11 +49,21 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore { return securityStore.getTbLwM2MSecurityInfoByEndpoint(endpoint); } + /** + * @param endpoint + * @return : If SecurityMode == NO_SEC: + * return SecurityInfo.newPreSharedKeyInfo(SecurityMode.NO_SEC.toString(), SecurityMode.NO_SEC.toString(), + * SecurityMode.NO_SEC.toString().getBytes()); + */ @Override public SecurityInfo getByEndpoint(String endpoint) { SecurityInfo securityInfo = securityStore.getByEndpoint(endpoint); if (securityInfo == null) { securityInfo = fetchAndPutSecurityInfo(endpoint); + } else if (securityInfo.usePSK() && securityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString()) + && securityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString()) + && Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), securityInfo.getPreSharedKey())) { + return null; } return securityInfo; } @@ -59,7 +72,12 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore { public SecurityInfo getByIdentity(String pskIdentity) { SecurityInfo securityInfo = securityStore.getByIdentity(pskIdentity); if (securityInfo == null) { - securityInfo = fetchAndPutSecurityInfo(pskIdentity); + try { + securityInfo = fetchAndPutSecurityInfo(pskIdentity); + } catch (LwM2MAuthException e) { + log.info("Registration failed: FORBIDDEN, endpointId: [{}]", pskIdentity); + securityInfo = SecurityInfo.newPreSharedKeyInfo(pskIdentity, pskIdentity, new byte[]{0x00}); + } } return securityInfo; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java similarity index 92% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 0ce98333ee..db1390768e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -21,17 +21,24 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadResponse; @@ -43,9 +50,9 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; -import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; -import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -59,7 +66,6 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; @@ -73,8 +79,6 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; @@ -88,6 +92,7 @@ import org.thingsboard.server.transport.lwm2m.server.model.LwM2MModelConfigServi import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.session.LwM2MSessionManager; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; +import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -110,12 +115,6 @@ import java.util.stream.Collectors; import static org.thingsboard.common.util.CollectionsUtil.diffSets; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID; @@ -127,12 +126,18 @@ import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaU import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_VER_ID; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LOG_LWM2M_WARN; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertOtaUpdateValueToString; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j @Service @TbLwM2mTransportComponent -public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mUplinkMsgHandler { +public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mUplinkMsgHandler { public LwM2mValueConverterImpl converter; @@ -152,7 +157,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl private final TbLwM2mSecurityStore securityStore; private final LwM2MModelConfigService modelConfigService; - public DefaultLwM2MUplinkMsgHandler(TransportService transportService, + public DefaultLwM2mUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, @@ -234,7 +239,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl + registration.getLwM2mVersion() + " and modes: " + registration.getQueueMode() + ", " + registration.getBindingMode()); sessionManager.register(lwM2MClient.getSession()); this.initClientTelemetry(lwM2MClient); - this.initAttributes(lwM2MClient); + this.initAttributes(lwM2MClient, true); otaService.init(lwM2MClient); lwM2MClient.getRetryAttempts().set(0); } catch (LwM2MClientStateException stateException) { @@ -325,19 +330,21 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl */ @Override public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response) { - if (response.getContent() != null) { + LwM2mNode content = response.getContent(); + if (content != null) { LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path, modelProvider); if (objectModelVersion != null) { - if (response.getContent() instanceof LwM2mObject) { - LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); - this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path); - } else if (response.getContent() instanceof LwM2mObjectInstance) { - LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent(); - this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path); - } else if (response.getContent() instanceof LwM2mResource) { - LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); - this.updateResourcesValue(lwM2MClient, lwM2mResource, path); + int responseCode = response.getCode().getCode(); + if (content instanceof LwM2mObject) { + LwM2mObject lwM2mObject = (LwM2mObject) content; + this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path, responseCode); + } else if (content instanceof LwM2mObjectInstance) { + LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) content; + this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path, responseCode); + } else if (content instanceof LwM2mResource) { + LwM2mResource lwM2mResource = (LwM2mResource) content; + this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE, responseCode); } } if (clientContext.awake(lwM2MClient)) { @@ -355,12 +362,13 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); response.getContent().forEach((k, v) -> { if (v != null) { + int responseCode = response.getCode().getCode(); if (v instanceof LwM2mObject) { - this.updateObjectResourceValue(lwM2MClient, (LwM2mObject) v, k.toString()); + this.updateObjectResourceValue(lwM2MClient, (LwM2mObject) v, k.toString(), responseCode); } else if (v instanceof LwM2mObjectInstance) { - this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString()); + this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString(), responseCode); } else if (v instanceof LwM2mResource) { - this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString()); + this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString(), Mode.UPDATE, responseCode); } } }); @@ -449,8 +457,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * #1 clientOnlyObserveAfterConnect == true * - Only Observe Request to the client marked as observe from the profile configuration. * #2. clientOnlyObserveAfterConnect == false - * После регистрации отправляю запрос на read всех ресурсов, которые после регистрации есть у клиента, - * а затем запрос на observe (edited) * - Read Request to the client after registration to read all resource values for all objects * - then Observe Request to the client marked as observe from the profile configuration. * @@ -516,11 +522,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } } - private void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId) { - TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(targetId).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); - defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(logService, lwM2MClient, targetId)); - } - private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)); } @@ -549,19 +550,19 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(logService, client, versionedId)); } - private void updateObjectResourceValue(LwM2mClient client, LwM2mObject lwM2mObject, String pathIdVer) { + private void updateObjectResourceValue(LwM2mClient client, LwM2mObject lwM2mObject, String pathIdVer, int code) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObject.getInstances().forEach((instanceId, instance) -> { String pathInstance = pathIds.toString() + "/" + instanceId; - this.updateObjectInstanceResourceValue(client, instance, pathInstance); + this.updateObjectInstanceResourceValue(client, instance, pathInstance, code); }); } - private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer) { + private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer, int code) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIds.toString() + "/" + resourceId; - this.updateResourcesValue(client, resource, pathRez); + this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code); }); } @@ -571,14 +572,14 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * #2 Update new Resources (replace old Resource Value on new Resource Value) * #3 If fr_update -> UpdateFirmware * #4 updateAttrTelemetry - * - * @param lwM2MClient - Registration LwM2M Client + * @param lwM2MClient - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() * @param path - resource + * @param mode - Replace, Update */ - private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path) { + private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode, int code) { Registration registration = lwM2MClient.getRegistration(); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider)) { + if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider, mode)) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) { otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { @@ -602,9 +603,11 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } else if (path.equals(convertObjectIdToVersionedId(SW_RESULT_ID, registration))) { otaService.onCurrentSoftwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); } - this.updateAttrTelemetry(registration, Collections.singleton(path)); + if (ResponseCode.BAD_REQUEST.getCode() > code) { + this.updateAttrTelemetry(registration, Collections.singleton(path)); + } } else { - log.error("Fail update Resource [{}]", lwM2mResource); + log.error("Fail update path [{}] Resource [{}]", path, lwM2mResource); } } @@ -710,7 +713,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl String resourceName = names.get(pathIdVer); if (resourceName != null && !resourceName.isEmpty()) { try { - LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); + LwM2mResource resourceValue = LwM2MTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); if (resourceValue != null) { ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); @@ -721,7 +724,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl Gson gson = new GsonBuilder().create(); ResourceModel.Type finalCurrentType = currentType; resourceValue.getInstances().forEach((k, v) -> { - Object val = this.converter.convertValue(v, finalCurrentType, expectedType, + Object val = this.converter.convertValue(v.getValue(), finalCurrentType, expectedType, new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); JsonElement element = gson.toJsonTree(val, val.getClass()); ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); @@ -747,23 +750,40 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } @Override - public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { + public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request, int code) { if (request.getNode() instanceof LwM2mResource) { - this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path); - clientContext.update(client); + this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE, code); } else if (request.getNode() instanceof LwM2mObjectInstance) { ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { - this.updateResourcesValue(client, resource, path + "/" + resId); + this.updateResourcesValue(client, resource, path + "/" + resId, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE, code); }); + } + if (request.getNode() instanceof LwM2mResource || request.getNode() instanceof LwM2mObjectInstance) { clientContext.update(client); } } @Override - public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) { + public void onCreateResponseOk(LwM2mClient client, String path, CreateRequest request) { + if (request.getObjectInstances() != null && request.getObjectInstances().size() > 0) { + request.getObjectInstances().forEach(instance -> + instance.getResources() + ); + clientContext.update(client); + } + } + + @Override + public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request, int code) { log.trace("ReadCompositeResponse: [{}]", request.getNodes()); request.getNodes().forEach((k, v) -> { - this.updateResourcesValue(client, (LwM2mResource) v, k.toString()); + if (v instanceof LwM2mSingleResource) { + this.updateResourcesValue(client, (LwM2mResource) v, k.toString(), Mode.REPLACE, code); + } else { + LwM2mResourceInstance resourceInstance = (LwM2mResourceInstance) v; + LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(v.getId(), resourceInstance.getType(), resourceInstance); + this.updateResourcesValue(client, multipleResource, k.toString(), Mode.REPLACE, code); + } }); } @@ -928,14 +948,14 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * * @param lwM2MClient - LwM2M Client */ - public void initAttributes(LwM2mClient lwM2MClient) { + public void initAttributes(LwM2mClient lwM2MClient, boolean logFailedUpdateOfNonChangedValue) { Map keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); if (!keyNamesMap.isEmpty()) { Set keysToFetch = new HashSet<>(keyNamesMap.values()); keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS); DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch), - v -> attributesService.onAttributesUpdate(lwM2MClient, v), + v -> attributesService.onAttributesUpdate(lwM2MClient, v, logFailedUpdateOfNonChangedValue), t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t), executor); } @@ -950,7 +970,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl return this.config; } - private void reportActivitySubscription(TransportProtos.SessionInfoProto sessionInfo) { + private void reportActivitySubscription(SessionInfoProto sessionInfo) { transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(true) .setRpcSubscription(true) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index 24045451f8..ca706c8138 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.uplink; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadCompositeResponse; @@ -57,9 +58,11 @@ public interface LwM2mUplinkMsgHandler { void onAwakeDev(Registration registration); - void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request); + void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request, int code); - void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request); + void onCreateResponseOk(LwM2mClient client, String path, CreateRequest request); + + void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request, int code); void onToTransportUpdateCredentials(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java similarity index 84% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index c3451c0900..1a05ce7b65 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -13,20 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.server; +package org.thingsboard.server.transport.lwm2m.utils; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; @@ -35,25 +39,29 @@ import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.ota.OtaPackageKey; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; +import org.thingsboard.server.transport.lwm2m.server.downlink.HasVersionedId; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; @@ -77,7 +85,7 @@ import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaU import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_STATE_ID; @Slf4j -public class LwM2mTransportUtil { +public class LwM2MTransportUtil { public static final String LWM2M_OBJECT_VERSION_DEFAULT = "1.0"; @@ -142,7 +150,7 @@ public class LwM2mTransportUtil { if (path != null) { if (FW_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(FirmwareUpdateState.fromStateFwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(FirmwareUpdateState.fromStateFwByCode(((Long) value).intValue()).getType()); return lwM2mOtaConvert; } else if (FW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); @@ -150,11 +158,11 @@ public class LwM2mTransportUtil { return lwM2mOtaConvert; } else if (SW_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(SoftwareUpdateState.fromUpdateStateSwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(SoftwareUpdateState.fromUpdateStateSwByCode(((Long) value).intValue()).getType()); return lwM2mOtaConvert; } else if (SW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(SoftwareUpdateResult.fromUpdateResultSwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(SoftwareUpdateResult.fromUpdateResultSwByCode(((Long) value).intValue()).getType()); return lwM2mOtaConvert; } } @@ -173,7 +181,7 @@ public class LwM2mTransportUtil { } } - public static BootstrapConfiguration getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { + public static List getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { return toLwM2MClientProfile(deviceProfile).getBootstrap(); } @@ -204,7 +212,7 @@ public class LwM2mTransportUtil { String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); if (keyArray.length > 1) { String[] keyArrayVer = keyArray[1].split(LWM2M_SEPARATOR_KEY); - return keyArrayVer.length == 2 ? keyArrayVer[1] : null; + return keyArrayVer.length == 2 ? keyArrayVer[1] : LWM2M_OBJECT_VERSION_DEFAULT; } } catch (Exception e) { return null; @@ -281,14 +289,15 @@ public class LwM2mTransportUtil { * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); * Attribute [] attrs = {gt, st}; */ - public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, DefaultLwM2MUplinkMsgHandler serviceImpl) { + public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, DefaultLwM2mUplinkMsgHandler serviceImpl) { AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; } - private static Attribute[] createWriteAttributes(Object params, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { + private static Attribute[] createWriteAttributes(Object params, DefaultLwM2mUplinkMsgHandler serviceImpl, String target) { List attributeLists = new ArrayList<>(); - Map map = JacksonUtil.convertValue(params, new TypeReference<>() {}); + Map map = JacksonUtil.convertValue(params, new TypeReference<>() { + }); map.forEach((k, v) -> { if (StringUtils.trimToNull(v.toString()) != null) { Object attrValue = convertWriteAttributes(k, v, serviceImpl, target); @@ -303,6 +312,12 @@ public class LwM2mTransportUtil { return attributeLists.toArray(Attribute[]::new); } + /** + * "UNSIGNED_INTEGER": // Number -> Integer Example: + * Alarm Timestamp [32-bit unsigned integer] + * Short Server ID, Object ID, Object Instance ID, Resource ID, Resource Instance ID + * "CORELINK": // String used in Attribute + */ public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { switch (value.getClass().getSimpleName()) { case "Double": @@ -324,7 +339,34 @@ public class LwM2mTransportUtil { } } - public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { + public static void validateVersionedId(LwM2mClient client, HasVersionedId request) { + String msgExceptionStr = ""; + if (request.getObjectId() == null) { + msgExceptionStr = "Specified object id is null!"; + } else { + msgExceptionStr = client.isValidObjectVersion(request.getVersionedId()); + } + if (!msgExceptionStr.isEmpty()) { + throw new IllegalArgumentException(msgExceptionStr); + } + } + + public static Map convertMultiResourceValuesFromRpcBody(Object value, ResourceModel.Type type, String versionedId) throws Exception { + String valueJsonStr = JacksonUtil.toString(value); + JsonElement element = JsonUtils.parse(valueJsonStr); + return convertMultiResourceValuesFromJson(element, type, versionedId); + } + + public static Map convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { + Map newValues = new HashMap<>(); + newValProto.getAsJsonObject().entrySet().forEach((obj) -> { + newValues.put(Integer.valueOf(obj.getKey()), LwM2mValueConverterImpl.getInstance().convertValue(obj.getValue().getAsString(), + STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId)))); + }); + return newValues; + } + + public static Object convertWriteAttributes(String type, Object value, DefaultLwM2mUplinkMsgHandler serviceImpl, String target) { switch (type) { /** Integer [0:255]; */ case DIMENSION: @@ -452,4 +494,30 @@ public class LwM2mTransportUtil { return opaque.length() > 1024 ? opaque.substring(0, 1024) : opaque; } + public static LwM2mModel createModelsDefault() { + return new StaticModel(ObjectLoader.loadDefault()); + } + + public static boolean compareAttNameKeyOta(String attrName) { + for (OtaPackageKey value : OtaPackageKey.values()) { + if (attrName.contains(value.getValue())) return true; + } + return false; + } + + public static boolean valueEquals(Object newValue, Object oldValue) { + String newValueStr; + String oldValueStr; + if (oldValue instanceof byte[]) { + oldValueStr = Hex.encodeHexString((byte[]) oldValue); + } else { + oldValueStr = oldValue.toString(); + } + if (newValue instanceof byte[]) { + newValueStr = Hex.encodeHexString((byte[]) newValue); + } else { + newValueStr = newValue.toString(); + } + return newValueStr.equals(oldValueStr); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index 12a256ebed..0427caa3a6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.utils; +import com.google.api.client.util.Base64; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mPath; @@ -166,13 +167,16 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { case OPAQUE: if (currentType == Type.STRING) { /** let's assume we received an hexadecimal string */ - log.debug("Trying to convert hexadecimal string [{}] to byte array", value); - // TODO check if we shouldn't instead assume that the string contains Base64 encoded data + log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value); try { return Hex.decodeHex(((String)value).toCharArray()); } catch (IllegalArgumentException e) { - throw new CodecException("Unable to convert hexastring [%s] to byte array for resource %s", value, - resourcePath); + try { + return Base64.decodeBase64(((String) value).getBytes()); + } catch (IllegalArgumentException ea) { + throw new CodecException("Unable to convert hexastring or base64 [%s] to byte array for resource %s", + value, resourcePath); + } } } break; diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh index dcf38b4f10..f68ca30005 100644 --- a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh +++ b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh @@ -321,7 +321,6 @@ cert_end_point() { } if [ "$client_start" -lt "$client_finish" ]; then - echo "Файл содержит, как минимум, одно слово Bash." echo echo "==Start Client==" while [ "$CLIENT_NUMBER" -lt "$client_finish" ]; do diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java index 792d46b475..01b7242805 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java @@ -31,9 +31,11 @@ import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; +import java.util.Optional; import java.util.Set; public abstract class AbstractSslCredentials implements SslCredentials { @@ -127,6 +129,14 @@ public abstract class AbstractSslCredentials implements SslCredentials { return kmf; } + @Override + public String getValueFromSubjectNameByKey(String subjectName, String key) { + String[] dns = subjectName.split(","); + Optional cn = (Arrays.stream(dns).filter(dn -> dn.contains(key + "="))).findFirst(); + String value = cn.isPresent() ? cn.get().replace(key + "=", "") : null; + return StringUtils.isNotEmpty(value) ? value : null; + } + protected abstract boolean canUse(); protected abstract KeyStore loadKeyStore(boolean isPrivateKeyRequired, char[] keyPasswordArray) throws IOException, GeneralSecurityException; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java index c6207b877d..49152960fc 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java @@ -49,4 +49,5 @@ public interface SslCredentials { KeyManagerFactory createKeyManagerFactory() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException; + String getValueFromSubjectNameByKey(String subjectName, String key); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 52e52c30f1..36e1a68daa 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -907,7 +907,7 @@ public class DefaultTransportService implements TransportService { String resourceId = msg.getResourceKey(); transportResourceCache.update(tenantId, resourceType, resourceId); sessions.forEach((id, mdRez) -> { - log.warn("ResourceUpdate - [{}] [{}]", id, mdRez); + log.trace("ResourceUpdate - [{}] [{}]", id, mdRez); transportCallbackExecutor.submit(() -> mdRez.getListener().onResourceUpdate(msg)); }); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java index 29817ad350..63383b5e33 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java @@ -15,15 +15,18 @@ */ package org.thingsboard.server.common.transport.util; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; + import java.util.List; public class JsonUtils { private static final JsonParser jsonParser = new JsonParser(); + private static final ObjectMapper json = new ObjectMapper(); public static JsonObject getJsonObject(List tsKv) { JsonObject json = new JsonObject(); @@ -52,4 +55,5 @@ public class JsonUtils { public static JsonElement parse(String params) { return jsonParser.parse(params); } + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index 77e4045655..2a17a4791d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -33,6 +33,6 @@ public class SslUtil { public static String getCertificateString(Certificate cert) throws CertificateEncodingException { - return EncryptionUtil.trimNewLines(Base64Utils.encodeToString(cert.getEncoded())); + return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index 6e8e5610eb..f4734d0c5c 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -23,6 +23,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; /** * Created by Valerii Sosliuk on 5/12/2017. diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 14539066ff..e131dd34a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.device; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Base64; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; @@ -28,16 +28,16 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MServerCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKServerCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKServerCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ServerCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509BootstrapClientCredential; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -47,7 +47,6 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.service.DataValidator; - import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -118,7 +117,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen formatSimpleMqttCredentials(deviceCredentials); break; case LWM2M_CREDENTIALS: - formatSimpleLwm2mCredentials(deviceCredentials); + formatAndValidateSimpleLwm2mCredentials(deviceCredentials); break; } } @@ -155,13 +154,13 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } private void formatCertData(DeviceCredentials deviceCredentials) { - String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); deviceCredentials.setCredentialsId(sha3Hash); deviceCredentials.setCredentialsValue(cert); } - private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { + private void formatAndValidateSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { LwM2MDeviceCredentials lwM2MCredentials; try { lwM2MCredentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), LwM2MDeviceCredentials.class); @@ -171,27 +170,24 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } String credentialsId = null; - LwM2MClientCredentials clientCredentials = lwM2MCredentials.getClient(); - + LwM2MClientCredential clientCredentials = lwM2MCredentials.getClient(); switch (clientCredentials.getSecurityConfigClientMode()) { case NO_SEC: case RPK: + deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials)); credentialsId = clientCredentials.getEndpoint(); break; case PSK: - credentialsId = ((PSKClientCredentials) clientCredentials).getIdentity(); + credentialsId = ((PSKClientCredential) clientCredentials).getIdentity(); break; case X509: - X509ClientCredentials x509Config = (X509ClientCredentials) clientCredentials; - if (x509Config.getCert() != null) { - String cert = EncryptionUtil.trimNewLines(x509Config.getCert()); - String sha3Hash = EncryptionUtil.getSha3Hash(cert); - x509Config.setCert(cert); - ((X509ClientCredentials) clientCredentials).setCert(cert); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials)); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials)); + X509ClientCredential x509ClientConfig = (X509ClientCredential) clientCredentials; + if ((StringUtils.isNotBlank(x509ClientConfig.getCert()))) { + String sha3Hash = EncryptionUtil.getSha3Hash(x509ClientConfig.getCert()); credentialsId = sha3Hash; } else { - credentialsId = x509Config.getEndpoint(); + credentialsId = x509ClientConfig.getEndpoint(); } break; } @@ -203,151 +199,170 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen private void validateLwM2MDeviceCredentials(LwM2MDeviceCredentials lwM2MCredentials) { if (lwM2MCredentials == null) { - throw new DeviceCredentialsValidationException("LwM2M credentials should be specified!"); + throw new DeviceCredentialsValidationException("LwM2M credentials must be specified!"); } - LwM2MClientCredentials clientCredentials = lwM2MCredentials.getClient(); + LwM2MClientCredential clientCredentials = lwM2MCredentials.getClient(); if (clientCredentials == null) { - throw new DeviceCredentialsValidationException("LwM2M client credentials should be specified!"); + throw new DeviceCredentialsValidationException("LwM2M client credentials must be specified!"); } validateLwM2MClientCredentials(clientCredentials); - LwM2MBootstrapCredentials bootstrapCredentials = lwM2MCredentials.getBootstrap(); + LwM2MBootstrapClientCredentials bootstrapCredentials = lwM2MCredentials.getBootstrap(); if (bootstrapCredentials == null) { - throw new DeviceCredentialsValidationException("LwM2M bootstrap credentials should be specified!"); + throw new DeviceCredentialsValidationException("LwM2M bootstrap credentials must be specified!"); } - LwM2MServerCredentials bootstrapServerCredentials = bootstrapCredentials.getBootstrapServer(); + LwM2MBootstrapClientCredential bootstrapServerCredentials = bootstrapCredentials.getBootstrapServer(); if (bootstrapServerCredentials == null) { - throw new DeviceCredentialsValidationException("LwM2M bootstrap server credentials should be specified!"); + throw new DeviceCredentialsValidationException("LwM2M bootstrap server credentials must be specified!"); } validateServerCredentials(bootstrapServerCredentials, "Bootstrap server"); - LwM2MServerCredentials lwm2mServerCredentials = bootstrapCredentials.getLwm2mServer(); - if (lwm2mServerCredentials == null) { - throw new DeviceCredentialsValidationException("LwM2M lwm2m server credentials should be specified!"); + LwM2MBootstrapClientCredential lwm2MBootstrapClientCredential = bootstrapCredentials.getLwm2mServer(); + if (lwm2MBootstrapClientCredential == null) { + throw new DeviceCredentialsValidationException("LwM2M lwm2m server credentials must be specified!"); } - validateServerCredentials(lwm2mServerCredentials, "LwM2M server"); + validateServerCredentials(lwm2MBootstrapClientCredential, "LwM2M server"); } - private void validateLwM2MClientCredentials(LwM2MClientCredentials clientCredentials) { - if (StringUtils.isEmpty(clientCredentials.getEndpoint())) { - throw new DeviceCredentialsValidationException("LwM2M client endpoint should be specified!"); + private void validateLwM2MClientCredentials(LwM2MClientCredential clientCredentials) { + if (StringUtils.isBlank(clientCredentials.getEndpoint())) { + throw new DeviceCredentialsValidationException("LwM2M client endpoint must be specified!"); } switch (clientCredentials.getSecurityConfigClientMode()) { case NO_SEC: break; case PSK: - PSKClientCredentials pskCredentials = (PSKClientCredentials) clientCredentials; - if (StringUtils.isEmpty(pskCredentials.getIdentity())) { - throw new DeviceCredentialsValidationException("LwM2M client PSK identity should be specified!"); + PSKClientCredential pskCredentials = (PSKClientCredential) clientCredentials; + if (StringUtils.isBlank(pskCredentials.getIdentity())) { + throw new DeviceCredentialsValidationException("LwM2M client PSK identity must be specified and must be an utf8 string!"); + } + // SecurityMode.NO_SEC.toString() == "NO_SEC"; + if (pskCredentials.getIdentity().equals(SecurityMode.NO_SEC.toString())) { + throw new DeviceCredentialsValidationException("The PSK ID of the LwM2M client must not be '" + SecurityMode.NO_SEC + "'!"); } String pskKey = pskCredentials.getKey(); - if (StringUtils.isEmpty(pskKey)) { - throw new DeviceCredentialsValidationException("LwM2M client PSK key should be specified!"); + if (StringUtils.isBlank(pskKey)) { + throw new DeviceCredentialsValidationException("LwM2M client PSK key must be specified!"); } if (!pskKey.matches("-?[0-9a-fA-F]+")) { - throw new DeviceCredentialsValidationException("LwM2M client PSK key should be HexDecimal format!"); + throw new DeviceCredentialsValidationException("LwM2M client PSK key must be random sequence in hex encoding!"); } - if (pskKey.length() % 32 != 0 || pskKey.length() > 128) { - throw new DeviceCredentialsValidationException("LwM2M client PSK key must be 32, 64, 128 characters!"); + if (pskKey.length()% 32 != 0 || pskKey.length() > 128) { + throw new DeviceCredentialsValidationException("LwM2M client PSK key length = " + pskKey.length() + ". Key must be HexDec format: 32, 64, 128 characters!"); } + break; case RPK: - RPKClientCredentials rpkCredentials = (RPKClientCredentials) clientCredentials; - - if (StringUtils.isEmpty(rpkCredentials.getKey())) { - throw new DeviceCredentialsValidationException("LwM2M client RPK key should be specified!"); + RPKClientCredential rpkCredentials = (RPKClientCredential) clientCredentials; + if (StringUtils.isBlank(rpkCredentials.getKey())) { + throw new DeviceCredentialsValidationException("LwM2M client RPK key must be specified!"); } try { - SecurityUtil.publicKey.decode(rpkCredentials.getDecodedKey()); + String pubkClient = EncryptionUtil.pubkTrimNewLines(rpkCredentials.getKey()); + rpkCredentials.setKey(pubkClient); + SecurityUtil.publicKey.decode(rpkCredentials.getDecoded()); } catch (Exception e) { - throw new DeviceCredentialsValidationException("LwM2M client RPK key should be in RFC7250 standard!"); + throw new DeviceCredentialsValidationException("LwM2M client RPK key must be in standard [RFC7250] and support only EC algorithm and then encoded to Base64 format!"); } break; case X509: - X509ClientCredentials x509CCredentials = (X509ClientCredentials) clientCredentials; + X509ClientCredential x509CCredentials = (X509ClientCredential) clientCredentials; if (StringUtils.isNotEmpty(x509CCredentials.getCert())) { try { - SecurityUtil.certificate.decode(Base64.decodeBase64(x509CCredentials.getCert())); + String certClient = EncryptionUtil.certTrimNewLines(x509CCredentials.getCert()); + x509CCredentials.setCert(certClient); + SecurityUtil.certificate.decode(x509CCredentials.getDecoded()); } catch (Exception e) { - throw new DeviceCredentialsValidationException("LwM2M client X509 certificate should be in DER-encoded X.509 format!"); + throw new DeviceCredentialsValidationException("LwM2M client X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"); } } break; } } - private void validateServerCredentials(LwM2MServerCredentials serverCredentials, String server) { + private void validateServerCredentials(LwM2MBootstrapClientCredential serverCredentials, String server) { switch (serverCredentials.getSecurityMode()) { case NO_SEC: break; case PSK: - PSKServerCredentials pskCredentials = (PSKServerCredentials) serverCredentials; - if (StringUtils.isEmpty(pskCredentials.getClientPublicKeyOrId())) { - throw new DeviceCredentialsValidationException(server + " client PSK public key or id should be specified!"); + PSKBootstrapClientCredential pskCredentials = (PSKBootstrapClientCredential) serverCredentials; + if (StringUtils.isBlank(pskCredentials.getClientPublicKeyOrId())) { + throw new DeviceCredentialsValidationException(server + " client PSK public key or id must be specified and must be an utf8 string!"); + } + + // SecurityMode.NO_SEC.toString() == "NO_SEC"; + if (pskCredentials.getClientPublicKeyOrId().equals(SecurityMode.NO_SEC.toString())) { + throw new DeviceCredentialsValidationException(server + " client PSK public key or id must not be '" + SecurityMode.NO_SEC + "'!"); } String pskKey = pskCredentials.getClientSecretKey(); - if (StringUtils.isEmpty(pskKey)) { - throw new DeviceCredentialsValidationException(server + " client PSK key should be specified!"); + if (StringUtils.isBlank(pskKey)) { + throw new DeviceCredentialsValidationException(server + " client PSK key must be specified!"); } if (!pskKey.matches("-?[0-9a-fA-F]+")) { - throw new DeviceCredentialsValidationException(server + " client PSK key should be HexDecimal format!"); + throw new DeviceCredentialsValidationException(server + " client PSK key must be random sequence in hex encoding!"); } if (pskKey.length() % 32 != 0 || pskKey.length() > 128) { - throw new DeviceCredentialsValidationException(server + " client PSK key must be 32, 64, 128 characters!"); + throw new DeviceCredentialsValidationException(server + " client PSK key length = " + pskKey.length() + ". Key must be HexDec format: 32, 64, 128 characters!"); } break; case RPK: - RPKServerCredentials rpkCredentials = (RPKServerCredentials) serverCredentials; - - if (StringUtils.isEmpty(rpkCredentials.getClientPublicKeyOrId())) { - throw new DeviceCredentialsValidationException(server + " client RPK public key or id should be specified!"); + RPKBootstrapClientCredential rpkServerCredentials = (RPKBootstrapClientCredential) serverCredentials; + if (StringUtils.isEmpty(rpkServerCredentials.getClientPublicKeyOrId())) { + throw new DeviceCredentialsValidationException(server + " client RPK public key or id must be specified!"); } - try { - SecurityUtil.publicKey.decode(rpkCredentials.getDecodedClientPublicKeyOrId()); + String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getClientPublicKeyOrId()); + rpkServerCredentials.setClientPublicKeyOrId(pubkRpkSever); + SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedClientPublicKeyOrId()); } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " client RPK public key or id should be in RFC7250 standard!"); + throw new DeviceCredentialsValidationException(server + " client RPK public key or id must be in standard [RFC7250 ] and then encoded to Base64 format!"); } - if (StringUtils.isEmpty(rpkCredentials.getClientSecretKey())) { - throw new DeviceCredentialsValidationException(server + " client RPK secret key should be specified!"); + if (StringUtils.isEmpty(rpkServerCredentials.getClientSecretKey())) { + throw new DeviceCredentialsValidationException(server + " client RPK secret key must be specified!"); } try { - SecurityUtil.privateKey.decode(rpkCredentials.getDecodedClientSecretKey()); + String prikRpkSever = EncryptionUtil.prikTrimNewLines(rpkServerCredentials.getClientSecretKey()); + rpkServerCredentials.setClientSecretKey(prikRpkSever); + SecurityUtil.privateKey.decode(rpkServerCredentials.getDecodedClientSecretKey()); } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " client RPK secret key should be in RFC5958 standard!"); + throw new DeviceCredentialsValidationException(server + " client RPK secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!"); } break; case X509: - X509ServerCredentials x509CCredentials = (X509ServerCredentials) serverCredentials; - if (StringUtils.isEmpty(x509CCredentials.getClientPublicKeyOrId())) { - throw new DeviceCredentialsValidationException(server + " client X509 public key or id should be specified!"); + X509BootstrapClientCredential x509ServerCredentials = (X509BootstrapClientCredential) serverCredentials; + if (StringUtils.isBlank(x509ServerCredentials.getClientPublicKeyOrId())) { + throw new DeviceCredentialsValidationException(server + " client X509 public key or id must be specified!"); } try { - SecurityUtil.certificate.decode(x509CCredentials.getDecodedClientPublicKeyOrId()); + String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getClientPublicKeyOrId()); + x509ServerCredentials.setClientPublicKeyOrId(certServer); + SecurityUtil.certificate.decode(x509ServerCredentials.getDecodedClientPublicKeyOrId()); } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " client X509 public key or id should be in DER-encoded X.509 format!"); + throw new DeviceCredentialsValidationException(server + " client X509 public key or id must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"); } - if (StringUtils.isEmpty(x509CCredentials.getClientSecretKey())) { - throw new DeviceCredentialsValidationException(server + " client X509 secret key should be specified!"); + if (StringUtils.isBlank(x509ServerCredentials.getClientSecretKey())) { + throw new DeviceCredentialsValidationException(server + " client X509 secret key must be specified!"); } try { - SecurityUtil.privateKey.decode(x509CCredentials.getDecodedClientSecretKey()); + String prikX509Sever = EncryptionUtil.prikTrimNewLines(x509ServerCredentials.getClientSecretKey()); + x509ServerCredentials.setClientSecretKey(prikX509Sever); + SecurityUtil.privateKey.decode(x509ServerCredentials.getDecodedClientSecretKey()); } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " client X509 secret key should be in RFC5958 standard!"); + throw new DeviceCredentialsValidationException(server + " client X509 secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!"); } break; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 105352c8a5..18a8a1ecde 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -28,6 +28,7 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.util.SecurityUtil; import org.thingsboard.server.common.data.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; @@ -54,19 +56,26 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; @@ -409,6 +418,13 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); } } + } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) { + List lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap(); + validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations, + ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable()); + for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { + validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential); + } } List profileAlarms = deviceProfile.getProfileData().getAlarms(); @@ -692,6 +708,77 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } + private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) { + Set uris = new HashSet<>(); + Set shortServerIds = new HashSet<>(); + for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { + AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential; + if (!isBootstrapServerUpdateEnable && serverConfig.isBootstrapServerIs()) { + throw new DeviceCredentialsValidationException("Bootstrap config must not include \"Bootstrap Server\". \"Include Bootstrap Server updates\" is " + isBootstrapServerUpdateEnable + "." ); + } + String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":"; + if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) { + throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!"); + } + if (!shortServerIds.add(serverConfig.getShortServerId())){ + throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!"); + }; + String uri = serverConfig.getHost() + ":" + serverConfig.getPort(); + if (!uris.add(uri)){ + throw new DeviceCredentialsValidationException(server + " \"Host + port\" value = " + uri + ". This value must be a unique value for all servers!"); + }; + Integer port; + if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) { + port = serverConfig.isBootstrapServerIs() ? 5687 : 5685; + } + else { + port = serverConfig.isBootstrapServerIs() ? 5688 : 5686; + } + if (serverConfig.getPort() == null || serverConfig.getPort().intValue() != port) { + throw new DeviceCredentialsValidationException(server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!"); + + } + } + } + + private void validateLwm2mServersCredentialOfBootstrapForClient(LwM2MBootstrapServerCredential bootstrapServerConfig) { + String server; + switch (bootstrapServerConfig.getSecurityMode()) { + case NO_SEC: + case PSK: + break; + case RPK: + RPKLwM2MBootstrapServerCredential rpkServerCredentials = (RPKLwM2MBootstrapServerCredential) bootstrapServerConfig; + server = rpkServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; + if (StringUtils.isEmpty(rpkServerCredentials.getServerPublicKey())) { + throw new DeviceCredentialsValidationException(server + " RPK public key must be specified!"); + } + try { + String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getServerPublicKey()); + rpkServerCredentials.setServerPublicKey(pubkRpkSever); + SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedCServerPublicKey()); + } catch (Exception e) { + throw new DeviceCredentialsValidationException(server + " RPK public key must be in standard [RFC7250] and then encoded to Base64 format!"); + } + break; + case X509: + X509LwM2MBootstrapServerCredential x509ServerCredentials = (X509LwM2MBootstrapServerCredential) bootstrapServerConfig; + server = x509ServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; + if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) { + throw new DeviceCredentialsValidationException(server + " X509 public key must be specified!"); + } + + try { + String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey()); + x509ServerCredentials.setServerPublicKey(certServer); + SecurityUtil.publicKey.decode(x509ServerCredentials.getDecodedCServerPublicKey()); + } catch (Exception e) { + throw new DeviceCredentialsValidationException(server + " X509 public key must be in standard [RFC7250] and then encoded to Base64 format!"); + } + break; + } + } + private PaginatedRemover tenantDeviceProfilesRemover = new PaginatedRemover() { diff --git a/pom.xml b/pom.xml index d8ae55bf2e..c06960c6ca 100755 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ 1.3.4 2.2.6 2.6.1 - 2.0.0-M3 + 2.0.0-M4 2.6.2 2.3.30 1.6.2 diff --git a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh index dcf38b4f10..d623bfad42 100755 --- a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh +++ b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh @@ -321,7 +321,7 @@ cert_end_point() { } if [ "$client_start" -lt "$client_finish" ]; then - echo "Файл содержит, как минимум, одно слово Bash." + echo echo "==Start Client==" while [ "$CLIENT_NUMBER" -lt "$client_finish" ]; do diff --git a/transport/lwm2m/src/main/data/lwm2mserver.jks b/transport/lwm2m/src/main/data/lwm2mserver.jks index 5fab824aa1..301f4e2c3a 100644 Binary files a/transport/lwm2m/src/main/data/lwm2mserver.jks and b/transport/lwm2m/src/main/data/lwm2mserver.jks differ diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index b5a6b2075c..631968ad35 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -97,10 +97,10 @@ transport: timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" - # Local LwM2M transport parameters log: enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}" + # Local LwM2M transport parameters lwm2m: # Enable/disable lvm2m transport protocol. enabled: "${LWM2M_ENABLED:true}" @@ -140,7 +140,7 @@ transport: # Only Certificate_x509: skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: - enable: "${LWM2M_ENABLED_BS:true}" + enabled: "${LWM2M_ENABLED_BS:true}" id: "${LWM2M_SERVER_ID_BS:111}" bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_BS_BIND_PORT:5687}" diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index 26e68a2e88..19aac62204 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -24,13 +24,13 @@ import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/m import { deepClone, isDefinedAndNotNull, isEmptyStr } from '@core/utils'; import { ObjectLwM2M, - securityConfigMode, ServerSecurityConfig, ServerSecurityConfigInfo } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; import { SortOrder } from '@shared/models/page/sort-order'; import { OtaPackageService } from '@core/http/ota-package.service'; import { map, mergeMap, tap } from 'rxjs/operators'; +import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models'; @Injectable({ providedIn: 'root' @@ -79,25 +79,27 @@ export class DeviceProfileService { } } - public getLwm2mBootstrapSecurityInfoBySecurityType(isBootstrapServer: boolean, securityMode = securityConfigMode.NO_SEC, + public getLwm2mBootstrapSecurityInfoBySecurityType(isBootstrapServer: boolean, securityMode = Lwm2mSecurityType.NO_SEC, config?: RequestConfig): Observable { return this.getLwm2mBootstrapSecurityInfo(isBootstrapServer, config).pipe( map(securityConfig => { const serverSecurityConfigInfo = deepClone(securityConfig); - switch (securityMode) { - case securityConfigMode.PSK: - serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; - serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; - serverSecurityConfigInfo.serverPublicKey = ''; - break; - case securityConfigMode.RPK: - case securityConfigMode.X509: - serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; - serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; - break; - case securityConfigMode.NO_SEC: - serverSecurityConfigInfo.serverPublicKey = ''; - break; + if (serverSecurityConfigInfo) { + switch (securityMode) { + case Lwm2mSecurityType.PSK: + serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; + serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; + serverSecurityConfigInfo.serverPublicKey = ''; + break; + case Lwm2mSecurityType.RPK: + case Lwm2mSecurityType.X509: + serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; + serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; + break; + case Lwm2mSecurityType.NO_SEC: + serverSecurityConfigInfo.serverPublicKey = ''; + break; + } } return serverSecurityConfigInfo; }) diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html index 429a13ddb2..9694faa9ca 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html @@ -27,6 +27,8 @@

{{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} + help - - {{clientSecretKey.value?.length || 0}}/{{lengthClientSecretKey}} - {{ 'device.lwm2m-security-config.client-secret-key-required' | translate }} - - {{ 'device.lwm2m-security-config.client-secret-key-pattern' | translate }} - - - {{ 'device.lwm2m-security-config.client-secret-key-length' | translate: { - count: allowLengthKey.join(', ') - } }} -
diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.ts index 7c6ad485f3..9967280a60 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.ts @@ -16,7 +16,6 @@ import { Component, forwardRef, OnDestroy } from '@angular/core'; import { - AbstractControl, ControlValueAccessor, FormBuilder, FormGroup, @@ -27,9 +26,8 @@ import { Validators } from '@angular/forms'; import { - KEY_REGEXP_HEX_DEC, - LEN_MAX_PRIVATE_KEY, - LEN_MAX_PSK, + Lwm2mClientSecretKeyTooltipTranslationsMap, + Lwm2mPublicKeyOrIdTooltipTranslationsMap, Lwm2mSecurityType, Lwm2mSecurityTypeTranslationMap, ServerSecurityConfig @@ -61,8 +59,8 @@ export class DeviceCredentialsLwm2mServerComponent implements OnDestroy, Control securityConfigLwM2MType = Lwm2mSecurityType; securityConfigLwM2MTypes = Object.values(Lwm2mSecurityType); lwm2mSecurityTypeTranslationMap = Lwm2mSecurityTypeTranslationMap; - lengthClientSecretKey = LEN_MAX_PRIVATE_KEY; - allowLengthKey = [32, 64, LEN_MAX_PSK]; + publicKeyOrIdTooltipNamesMap = Lwm2mPublicKeyOrIdTooltipTranslationsMap; + clientSecretKeyTooltipNamesMap = Lwm2mClientSecretKeyTooltipTranslationsMap; private destroy$ = new Subject(); private propagateChange = (v: any) => {}; @@ -132,30 +130,18 @@ export class DeviceCredentialsLwm2mServerComponent implements OnDestroy, Control this.serverFormGroup.get('clientSecretKey').disable(); break; case Lwm2mSecurityType.PSK: - this.lengthClientSecretKey = LEN_MAX_PSK; - this.setValidatorsSecurity(securityMode); - break; case Lwm2mSecurityType.RPK: - this.lengthClientSecretKey = null; - this.setValidatorsSecurity(securityMode); - break; case Lwm2mSecurityType.X509: - this.lengthClientSecretKey = null; - this.setValidatorsSecurity(securityMode); + this.setValidatorsSecurity(); break; } this.serverFormGroup.get('clientPublicKeyOrId').updateValueAndValidity({emitEvent: false}); this.serverFormGroup.get('clientSecretKey').updateValueAndValidity({emitEvent: !initValue}); } - private setValidatorsSecurity = (securityMode: Lwm2mSecurityType): void => { - const clientSecretKeyValidators = [Validators.required, Validators.pattern(KEY_REGEXP_HEX_DEC)]; + private setValidatorsSecurity = (): void => { + const clientSecretKeyValidators = [Validators.required]; const clientPublicKeyOrIdValidators = [Validators.required]; - if (securityMode === Lwm2mSecurityType.PSK) { - clientSecretKeyValidators.push(this.maxLength(this.allowLengthKey)); - } else { - clientPublicKeyOrIdValidators.push(Validators.pattern(KEY_REGEXP_HEX_DEC)); - } this.serverFormGroup.get('clientPublicKeyOrId').setValidators(clientPublicKeyOrIdValidators); this.serverFormGroup.get('clientSecretKey').setValidators(clientSecretKeyValidators); @@ -163,14 +149,4 @@ export class DeviceCredentialsLwm2mServerComponent implements OnDestroy, Control this.serverFormGroup.get('clientPublicKeyOrId').enable({emitEvent: false}); this.serverFormGroup.get('clientSecretKey').enable(); } - - private maxLength(keyLengths: number[]) { - return (control: AbstractControl): ValidationErrors | null => { - const value = control.value; - if (keyLengths.some(len => value.length === len)) { - return null; - } - return {length: true}; - }; - } } diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html index b588f1af72..8ffa4ab1e6 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html @@ -34,8 +34,11 @@ - + {{ 'device.lwm2m-security-config.identity' | translate }} + help {{ 'device.lwm2m-security-config.identity-required' | translate }} @@ -44,31 +47,23 @@ {{ 'device.lwm2m-security-config.client-key' | translate }} + help - {{key.value?.length || 0}}/{{lenMaxKeyClient}} {{ 'device.lwm2m-security-config.client-key-required' | translate }} - - {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} - - - {{ 'device.lwm2m-security-config.client-key-length' | translate: { - count: allowLengthKey.join(', ') - } }} - device.lwm2m-security-config.client-public-key + help - - {{ 'device-profile.lwm2m.server-public-key-required' | translate }} - - - {{ 'device-profile.lwm2m.server-public-key-pattern' | translate }} - - - - + {{ 'device-profile.lwm2m.account-after-timeout-pattern' | translate }} + + + +
+ + {{ 'device-profile.lwm2m.server-public-key' | translate }} + help + + + {{ 'device-profile.lwm2m.server-public-key-required' | translate }} + + +
+
+ + {{ 'device-profile.lwm2m.lifetime' | translate }} + + + {{ 'device-profile.lwm2m.lifetime-required' | translate }} + + + {{ 'device-profile.lwm2m.lifetime-pattern' | translate }} + + + + {{ 'device-profile.lwm2m.default-min-period' | translate }} + help + + + {{ 'device-profile.lwm2m.default-min-period-required' | translate }} + + + {{ 'device-profile.lwm2m.default-min-period-pattern' | translate }} + + +
+ + {{ 'device-profile.lwm2m.binding' | translate }} + help + + + {{ bindingModeTypeNamesMap.get(bindingMode) | translate }} + + + + + {{ 'device-profile.lwm2m.notification-storing' | translate }} + + + + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index 63cde444ba..fa6d31153c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -26,17 +26,18 @@ import { Validators } from '@angular/forms'; import { - DEFAULT_PORT_BOOTSTRAP_NO_SEC, - DEFAULT_PORT_SERVER_NO_SEC, - KEY_REGEXP_HEX_DEC, - securityConfigMode, - securityConfigModeNames, + BingingMode, BingingModeTranslationsMap, ServerSecurityConfig } from './lwm2m-profile-config.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { Subject } from 'rxjs'; import { mergeMap, takeUntil, tap } from 'rxjs/operators'; import { Observable } from 'rxjs/internal/Observable'; +import { + Lwm2mPublicKeyOrIdTooltipTranslationsMap, + Lwm2mSecurityType, + Lwm2mSecurityTypeTranslationMap +} from '@shared/models/lwm2m-security-config.models'; @Component({ selector: 'tb-profile-lwm2m-device-config-server', @@ -57,19 +58,26 @@ import { Observable } from 'rxjs/internal/Observable'; export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAccessor, Validator, OnDestroy { - private disabled = false; + public disabled = false; private destroy$ = new Subject(); private isDataLoadedIntoCache = false; serverFormGroup: FormGroup; - securityConfigLwM2MType = securityConfigMode; - securityConfigLwM2MTypes = Object.keys(securityConfigMode); - credentialTypeLwM2MNamesMap = securityConfigModeNames; + bindingModeTypes = Object.values(BingingMode); + bindingModeTypeNamesMap = BingingModeTranslationsMap; + securityConfigLwM2MType = Lwm2mSecurityType; + securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); + credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; + publicKeyOrIdTooltipNamesMap = Lwm2mPublicKeyOrIdTooltipTranslationsMap; currentSecurityMode = null; + bootstrapDisabled = false; + + @Output() + removeServer = new EventEmitter(); - @Input() - isBootstrapServer = false; + @Output() + isTransportWasRunWithBootstrapChange = new EventEmitter(); private propagateChange = (v: any) => { }; @@ -80,19 +88,29 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc ngOnInit(): void { this.serverFormGroup = this.fb.group({ host: ['', Validators.required], - port: [this.isBootstrapServer ? DEFAULT_PORT_BOOTSTRAP_NO_SEC : DEFAULT_PORT_SERVER_NO_SEC, - [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]], - securityMode: [securityConfigMode.NO_SEC], + port: ['', [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]], + securityMode: [Lwm2mSecurityType.NO_SEC], serverPublicKey: [''], clientHoldOffTime: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - serverId: ['', [Validators.required, Validators.min(1), Validators.max(65534), Validators.pattern('[0-9]*')]], + shortServerId: ['', [Validators.required, Validators.min(1), Validators.max(65534), Validators.pattern('[0-9]*')]], bootstrapServerAccountTimeout: ['', [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + binding: [''], + lifetime: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + notifIfDisabled: [], + defaultMinPeriod: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + bootstrapServerIs: [] }); this.serverFormGroup.get('securityMode').valueChanges.pipe( - tap(securityMode => this.updateValidate(securityMode)), + tap((securityMode) => { + this.currentSecurityMode = securityMode; + this.updateValidate(securityMode); + }), mergeMap(securityMode => this.getLwm2mBootstrapSecurityInfo(securityMode)), takeUntil(this.destroy$) ).subscribe(serverSecurityConfig => { + if (this.currentSecurityMode !== Lwm2mSecurityType.NO_SEC) { + this.changeSecurityHostPortFields(serverSecurityConfig); + } this.serverFormGroup.patchValue(serverSecurityConfig, {emitEvent: false}); }); this.serverFormGroup.valueChanges.pipe( @@ -111,12 +129,22 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc if (serverData) { this.serverFormGroup.patchValue(serverData, {emitEvent: false}); this.updateValidate(serverData.securityMode); + if (serverData.securityHost && serverData.securityPort) { + delete serverData.securityHost; + delete serverData.securityPort; + this.propagateChangeState(this.serverFormGroup.getRawValue()); + } } - if (!this.isDataLoadedIntoCache){ + if (!this.isDataLoadedIntoCache) { this.getLwm2mBootstrapSecurityInfo().subscribe(value => { if (!serverData) { this.serverFormGroup.patchValue(value); } + if (!value && this.serverFormGroup.get('bootstrapServerIs').value === true) { + this.isTransportWasRunWithBootstrapChange.emit(false); + this.bootstrapDisabled = true; + this.serverFormGroup.get('securityMode').disable({emitEvent: false}); + } }); } } @@ -131,22 +159,25 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc this.serverFormGroup.disable({emitEvent: false}); } else { this.serverFormGroup.enable({emitEvent: false}); + if (this.bootstrapDisabled) { + this.serverFormGroup.get('securityMode').disable({emitEvent: false}); + } } } registerOnTouched(fn: any): void { } - private updateValidate(securityMode: securityConfigMode): void { + private updateValidate(securityMode: Lwm2mSecurityType): void { switch (securityMode) { - case securityConfigMode.NO_SEC: - case securityConfigMode.PSK: + case Lwm2mSecurityType.NO_SEC: + case Lwm2mSecurityType.PSK: this.clearValidators(); break; - case securityConfigMode.RPK: + case Lwm2mSecurityType.RPK: this.setValidators(); break; - case securityConfigMode.X509: + case Lwm2mSecurityType.X509: this.setValidators(); break; } @@ -158,10 +189,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc } private setValidators(): void { - this.serverFormGroup.get('serverPublicKey').setValidators([ - Validators.required, - Validators.pattern(KEY_REGEXP_HEX_DEC) - ]); + this.serverFormGroup.get('serverPublicKey').setValidators([Validators.required]); } private propagateChangeState = (value: ServerSecurityConfig): void => { @@ -170,12 +198,18 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc } } - private getLwm2mBootstrapSecurityInfo(securityMode = securityConfigMode.NO_SEC): Observable { - return this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(this.isBootstrapServer, securityMode).pipe( + private getLwm2mBootstrapSecurityInfo(securityMode = Lwm2mSecurityType.NO_SEC): Observable { + return this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType( + this.serverFormGroup.get('bootstrapServerIs').value, securityMode).pipe( tap(() => this.isDataLoadedIntoCache = true) ); } + private changeSecurityHostPortFields(serverData: ServerSecurityConfig): void { + serverData.port = serverData.securityPort; + serverData.host = serverData.securityHost; + } + validate(): ValidationErrors | null { return this.serverFormGroup.valid ? null : { serverFormGroup: true diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html index 5e444f8ee2..4133600ba8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html @@ -31,89 +31,20 @@ - +
-
- - - - {{ 'device-profile.lwm2m.servers' | translate }} - - -
- - {{ 'device-profile.lwm2m.short-id' | translate }} - - - {{ 'device-profile.lwm2m.short-id-required' | translate }} - - - {{ 'device-profile.lwm2m.short-id-range' | translate }} - - - {{ 'device-profile.lwm2m.short-id-pattern' | translate }} - - - - {{ 'device-profile.lwm2m.lifetime' | translate }} - - - {{ 'device-profile.lwm2m.lifetime-required' | translate }} - - - {{ 'device-profile.lwm2m.lifetime-pattern' | translate }} - - - - {{ 'device-profile.lwm2m.default-min-period' | translate }} - - - {{ 'device-profile.lwm2m.default-min-period-required' | translate }} - - - {{ 'device-profile.lwm2m.default-min-period-pattern' | translate }} - - -
- - {{ 'device-profile.lwm2m.binding' | translate }} - - - {{ bindingModeTypeNamesMap.get(bindingMode) | translate }} - - - - - {{ 'device-profile.lwm2m.notification-storing' | translate }} - -
-
- - - {{ 'device-profile.lwm2m.bootstrap-server' | translate }} - - - - - - - - - {{ 'device-profile.lwm2m.lwm2m-server' | translate }} - - - - - - -
+
+ warning + LwM2M transport was run without bootstrap server +
+ + {{ 'device-profile.lwm2m.include-bootstrap-server' | translate }} + +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index 2495a16eff..7da250fa4e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -28,20 +28,11 @@ import { import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ATTRIBUTE, - BingingMode, - BingingModeTranslationsMap, - DEFAULT_BINDING, DEFAULT_EDRX_CYCLE, DEFAULT_FW_UPDATE_RESOURCE, - DEFAULT_ID_SERVER, - DEFAULT_LIFE_TIME, - DEFAULT_MIN_PERIOD, - DEFAULT_NOTIF_IF_DESIBLED, DEFAULT_PAGING_TRANSMISSION_WINDOW, DEFAULT_PSM_ACTIVITY_TIMER, DEFAULT_SW_UPDATE_RESOURCE, - getDefaultBootstrapServerSecurityConfig, - getDefaultLwM2MServerSecurityConfig, Instance, INSTANCES, KEY_NAME, @@ -60,6 +51,9 @@ import { Direction } from '@shared/models/page/sort-order'; import _ from 'lodash'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-profile-lwm2m-device-transport-configuration', @@ -79,12 +73,12 @@ import { takeUntil } from 'rxjs/operators'; }) export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validator, OnDestroy { + public disabled = false; + public isTransportWasRunWithBootstrap = true; + public isBootstrapServerUpdateEnable: boolean; private requiredValue: boolean; - private disabled = false; private destroy$ = new Subject(); - bindingModeTypes = Object.values(BingingMode); - bindingModeTypeNamesMap = BingingModeTranslationsMap; lwm2mDeviceProfileFormGroup: FormGroup; configurationValue: Lwm2mProfileConfigModels; sortFunction: (key: string, value: object) => object; @@ -98,25 +92,22 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.requiredValue = coerceBooleanProperty(value); } + @Input() + isAdd: boolean; + private propagateChange = (v: any) => { } - constructor(private fb: FormBuilder, + constructor(public translate: TranslateService, + private fb: FormBuilder, + private cd: ChangeDetectorRef, + private dialogService: DialogService, private deviceProfileService: DeviceProfileService) { this.lwm2mDeviceProfileFormGroup = this.fb.group({ objectIds: [null], observeAttrTelemetry: [null], - bootstrap: this.fb.group({ - servers: this.fb.group({ - binding: [DEFAULT_BINDING], - shortId: [DEFAULT_ID_SERVER, [Validators.required, Validators.min(1), Validators.max(65534), Validators.pattern('[0-9]*')]], - lifetime: [DEFAULT_LIFE_TIME, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - notifIfDisabled: [DEFAULT_NOTIF_IF_DESIBLED, []], - defaultMinPeriod: [DEFAULT_MIN_PERIOD, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - }), - bootstrapServer: [null, Validators.required], - lwm2mServer: [null, Validators.required] - }), + bootstrapServerUpdateEnable: [false], + bootstrap: [[]], clientLwM2mSettings: this.fb.group({ clientOnlyObserveAfterConnect: [1, []], fwUpdateStrategy: [1, []], @@ -130,6 +121,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro compositeOperationsSupport: [false] }) }); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((fwStrategy) => { @@ -141,6 +133,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro .reset(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); } }); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((swStrategy) => { @@ -152,6 +145,39 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro .reset(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); } }); + + this.lwm2mDeviceProfileFormGroup.get('bootstrapServerUpdateEnable').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (!value) { + const bootstrap = this.lwm2mDeviceProfileFormGroup.get('bootstrap').value; + const bootstrapResultArray = bootstrap.filter(server => server.bootstrapServerIs === true); + if (bootstrapResultArray.length) { + this.dialogService.confirm( + this.translate.instant('device-profile.lwm2m.bootstrap-update-title'), + this.translate.instant('device-profile.lwm2m.bootstrap-update-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + ).pipe( + takeUntil(this.destroy$) + ).subscribe((result) => { + if (result) { + this.isBootstrapServerUpdateEnable = value; + } else { + this.lwm2mDeviceProfileFormGroup.patchValue({ + bootstrapServerUpdateEnable: true + }, {emitEvent: true}); + } + this.cd.markForCheck(); + }); + } else { + this.isBootstrapServerUpdateEnable = value; + } + } else { + this.isBootstrapServerUpdateEnable = value; + } + }); + this.lwm2mDeviceProfileFormGroup.valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((value) => { @@ -187,9 +213,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro async writeValue(value: Lwm2mProfileConfigModels | null) { if (isDefinedAndNotNull(value) && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { this.configurationValue = value; - const defaultFormSettings = value.clientLwM2mSettings.fwUpdateStrategy === 1 && - isUndefined(value.clientLwM2mSettings.fwUpdateResource); - if (defaultFormSettings) { + if (this.isAdd) { await this.defaultProfileConfig(); } this.initWriteValue(); @@ -203,23 +227,20 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private async defaultProfileConfig(): Promise { - let bootstrap: ServerSecurityConfig; let lwm2m: ServerSecurityConfig; - try { - [bootstrap, lwm2m] = await Promise.all([ - this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(true).toPromise(), - this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(false).toPromise() - ]); - } catch (e) { - bootstrap = getDefaultBootstrapServerSecurityConfig(); - lwm2m = getDefaultLwM2MServerSecurityConfig(); + let bootstrap: ServerSecurityConfig; + [bootstrap, lwm2m] = await Promise.all([ + this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(true).toPromise(), + this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(false).toPromise(), + ]); + if (lwm2m) { + lwm2m.securityMode = Lwm2mSecurityType.NO_SEC; } - - this.configurationValue.bootstrap.bootstrapServer = bootstrap; - this.configurationValue.bootstrap.lwm2mServer = lwm2m; + this.isTransportWasRunWithBootstrap = !!bootstrap; + this.configurationValue.bootstrap = [lwm2m]; this.lwm2mDeviceProfileFormGroup.patchValue({ bootstrap: this.configurationValue.bootstrap - }, {emitEvent: false}); + }, {emitEvent: true}); } private initWriteValue = (): void => { @@ -244,6 +265,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro objectIds: value, observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value), bootstrap: this.configurationValue.bootstrap, + bootstrapServerUpdateEnable: this.configurationValue.bootstrapServerUpdateEnable || false, clientLwM2mSettings: { clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, @@ -259,11 +281,13 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } }, {emitEvent: false}); + this.isBootstrapServerUpdateEnable = this.configurationValue.bootstrapServerUpdateEnable || false; if (!this.disabled) { this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.powerMode').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').updateValueAndValidity({onlySelf: true}); } + this.cd.markForCheck(); } private updateModel = (): void => { @@ -282,13 +306,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private updateDeviceProfileValue(config): void { - if (this.lwm2mDeviceProfileFormGroup.valid) { + if (this.lwm2mDeviceProfileFormGroup.valid && config.observeAttrTelemetry) { this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry); } - this.configurationValue.bootstrap.bootstrapServer = config.bootstrap.bootstrapServer; - this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer; - this.configurationValue.bootstrap.servers = config.bootstrap.servers; + this.configurationValue.bootstrap = config.bootstrap; this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings; + this.configurationValue.bootstrapServerUpdateEnable = config.bootstrapServerUpdateEnable; this.updateModel(); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts index 64a95d0513..47d7e5d35b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts @@ -185,13 +185,13 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, * If the Object field “Mandatory” is “Mandatory” and the Object field “Instances” is “Single” then - * the number of Object Instance MUST be 1. * 1. == Multiple (true), == Optional (false) - - * Object Instance ID MIN_ID=0 MAX_ID=65535 (может ни одного не быть) + * Object Instance ID MIN_ID=0 MAX_ID=65535 (there may be none) * 2. == Multiple (true), == Mandatory (true) - - * Object Instance ID MIN_ID=0 MAX_ID=65535 (min один обязательный) + * Object Instance ID MIN_ID=0 MAX_ID=65535 (min one mandatory) * 3. == Single (false), == Optional (false) - - * Object Instance ID cnt_max = 1 cnt_min = 0 (может ни одного не быть) + * Object Instance ID cnt_max = 1 cnt_min = 0 (there may be none ) * 4. == Single (false), == Mandatory (true) - - * Object Instance ID cnt_max = cnt_min = 1 (всегда есть один) + * Object Instance ID cnt_max = cnt_min = 1 (there must always be one) */ addInstances = ($event: Event, control: AbstractControl): void => { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts index e1c8e9a14c..667cb6ad51 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts @@ -29,6 +29,8 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import { Lwm2mObserveAttrTelemetryInstancesComponent } from '@home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component'; import { DeviceProfileCommonModule } from '@home/components/profile/device/common/device-profile-common.module'; +import { Lwm2mBootstrapConfigServersComponent } from '@home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component'; +import { Lwm2mBootstrapAddConfigServerDialogComponent } from '@home/components/profile/device/lwm2m/lwm2m-bootstrap-add-config-server-dialog.component'; @NgModule({ declarations: @@ -40,7 +42,9 @@ import { DeviceProfileCommonModule } from '@home/components/profile/device/commo Lwm2mAttributesDialogComponent, Lwm2mAttributesComponent, Lwm2mAttributesKeyListComponent, + Lwm2mBootstrapConfigServersComponent, Lwm2mDeviceConfigServerComponent, + Lwm2mBootstrapAddConfigServerDialogComponent, Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesListComponent, Lwm2mObserveAttrTelemetryInstancesComponent @@ -58,7 +62,9 @@ import { DeviceProfileCommonModule } from '@home/components/profile/device/commo Lwm2mAttributesDialogComponent, Lwm2mAttributesComponent, Lwm2mAttributesKeyListComponent, + Lwm2mBootstrapConfigServersComponent, Lwm2mDeviceConfigServerComponent, + Lwm2mBootstrapAddConfigServerDialogComponent, Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesListComponent, Lwm2mObserveAttrTelemetryInstancesComponent diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 3be58c7f7f..56ae00c5a9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -15,6 +15,7 @@ /// import { ValidatorFn, Validators } from '@angular/forms'; +import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models'; export const PAGE_SIZE_LIMIT = 50; export const INSTANCES = 'instances'; @@ -32,9 +33,8 @@ export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1; export const DEFAULT_LIFE_TIME = 300; export const DEFAULT_MIN_PERIOD = 1; export const DEFAULT_NOTIF_IF_DESIBLED = true; -export const DEFAULT_BINDING = 'UQ'; +export const DEFAULT_BINDING = 'U'; export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0; -export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MAX = 65535; export const DEFAULT_OTA_UPDATE_PROTOCOL = 'coap://'; @@ -46,28 +46,30 @@ export const DEFAULT_PAGING_TRANSMISSION_WINDOW = 10000; export enum BingingMode { U = 'U', - UQ = 'UQ', + M = 'M', + H = 'H', T = 'T', - TQ = 'TQ', S = 'S', - SQ = 'SQ', - US = 'US', - TS = 'TS', + N = 'N', + UQ = 'UQ', UQS = 'UQS', - TQS = 'TQS' + TQ = 'TQ', + TQS = 'TQS', + SQ = 'SQ' } export const BingingModeTranslationsMap = new Map( [ [BingingMode.U, 'device-profile.lwm2m.binding-type.u'], + [BingingMode.M, 'device-profile.lwm2m.binding-type.m'], + [BingingMode.H, 'device-profile.lwm2m.binding-type.h'], + [BingingMode.T, 'device-profile.lwm2m.binding-type.t'], + [BingingMode.S, 'device-profile.lwm2m.binding-type.s'], + [BingingMode.N, 'device-profile.lwm2m.binding-type.n'], [BingingMode.UQ, 'device-profile.lwm2m.binding-type.uq'], - [BingingMode.US, 'device-profile.lwm2m.binding-type.us'], [BingingMode.UQS, 'device-profile.lwm2m.binding-type.uqs'], - [BingingMode.T, 'device-profile.lwm2m.binding-type.t'], [BingingMode.TQ, 'device-profile.lwm2m.binding-type.tq'], - [BingingMode.TS, 'device-profile.lwm2m.binding-type.ts'], [BingingMode.TQS, 'device-profile.lwm2m.binding-type.tqs'], - [BingingMode.S, 'device-profile.lwm2m.binding-type.s'], [BingingMode.SQ, 'device-profile.lwm2m.binding-type.sq'] ] ); @@ -94,19 +96,15 @@ export const AttributeNameTranslationMap = new Map( ] ); -export enum securityConfigMode { - PSK = 'PSK', - RPK = 'RPK', - X509 = 'X509', - NO_SEC = 'NO_SEC' +export enum ServerConfigType { + LWM2M = 'LWM2M', + BOOTSTRAP = 'BOOTSTRAP' } -export const securityConfigModeNames = new Map( +export const ServerConfigTypeTranslationMap = new Map( [ - [securityConfigMode.PSK, 'Pre-Shared Key'], - [securityConfigMode.RPK, 'Raw Public Key'], - [securityConfigMode.X509, 'X.509 Certificate'], - [securityConfigMode.NO_SEC, 'No Security'] + [ServerConfigType.LWM2M, 'device-profile.lwm2m.lwm2m-server'], + [ServerConfigType.BOOTSTRAP, 'device-profile.lwm2m.bootstrap-server'] ] ); @@ -124,22 +122,21 @@ export const PowerModeTranslationMap = new Map( ] ); -export interface BootstrapServersSecurityConfig { - shortId: number; - lifetime: number; - defaultMinPeriod: number; - notifIfDisabled: boolean; - binding: string; -} - export interface ServerSecurityConfig { host?: string; port?: number; - securityMode: securityConfigMode; + securityMode: Lwm2mSecurityType; + securityHost?: string; + securityPort?: number; serverPublicKey?: string; clientHoldOffTime?: number; - serverId?: number; + shortServerId?: number; bootstrapServerAccountTimeout: number; + lifetime: number; + defaultMinPeriod: number; + notifIfDisabled: boolean; + binding: string; + bootstrapServerIs: boolean; } export interface ServerSecurityConfigInfo extends ServerSecurityConfig { @@ -148,16 +145,11 @@ export interface ServerSecurityConfigInfo extends ServerSecurityConfig { bootstrapServerIs: boolean; } -interface BootstrapSecurityConfig { - servers: BootstrapServersSecurityConfig; - bootstrapServer: ServerSecurityConfig; - lwm2mServer: ServerSecurityConfig; -} - export interface Lwm2mProfileConfigModels { clientLwM2mSettings: ClientLwM2mSettings; observeAttr: ObservableAttributes; - bootstrap: BootstrapSecurityConfig; + bootstrapServerUpdateEnable: boolean; + bootstrap: Array; } export interface ClientLwM2mSettings { @@ -181,35 +173,6 @@ export interface ObservableAttributes { attributeLwm2m: AttributesNameValueMap; } -export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { - return { - shortId: DEFAULT_ID_SERVER, - lifetime: DEFAULT_LIFE_TIME, - defaultMinPeriod: DEFAULT_MIN_PERIOD, - notifIfDisabled: DEFAULT_NOTIF_IF_DESIBLED, - binding: DEFAULT_BINDING - }; -} - -export function getDefaultBootstrapServerSecurityConfig(): ServerSecurityConfig { - return { - bootstrapServerAccountTimeout: DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT, - clientHoldOffTime: DEFAULT_CLIENT_HOLD_OFF_TIME, - host: DEFAULT_LOCAL_HOST_NAME, - port: DEFAULT_PORT_BOOTSTRAP_NO_SEC, - securityMode: securityConfigMode.NO_SEC, - serverId: DEFAULT_ID_BOOTSTRAP, - serverPublicKey: '' - }; -} - -export function getDefaultLwM2MServerSecurityConfig(): ServerSecurityConfig { - const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(); - DefaultLwM2MServerSecurityConfig.port = DEFAULT_PORT_SERVER_NO_SEC; - DefaultLwM2MServerSecurityConfig.serverId = DEFAULT_ID_SERVER; - return DefaultLwM2MServerSecurityConfig; -} - export function getDefaultProfileObserveAttrConfig(): ObservableAttributes { return { observe: [], diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index ae0be158d5..6a84e626d9 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -133,6 +133,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index fdbc25107f..b40130719a 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -252,6 +252,14 @@ export class DeviceWizardDialogComponent extends private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { this.transportConfigFormGroup.patchValue( {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); + const setCredentialBox = this.credentialsFormGroup.get('setCredential'); + if (deviceTransportType === DeviceTransportType.LWM2M) { + setCredentialBox.patchValue(true); + setCredentialBox.disable(); + } else { + setCredentialBox.patchValue(false); + setCredentialBox.enable(); + } } add(): void { diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index f1a7af1e46..a0ce99f3db 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -35,6 +35,7 @@
diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts index 2c53f04986..f1afa2559a 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts @@ -38,12 +38,17 @@ export class DeviceProfileTabsComponent extends EntityTabsComponent) { super(store); } ngOnInit() { super.ngOnInit(); + this.detailsForm.get('transportType').valueChanges.subscribe(() => { + this.isTransportTypeChanged = true; + }); } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 7ebf7bde47..7fc3d70f78 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -31,9 +31,6 @@ import { OtaPackageId } from '@shared/models/id/ota-package-id'; import { DashboardId } from '@shared/models/id/dashboard-id'; import { DataType } from '@shared/models/constants'; import { - getDefaultBootstrapServerSecurityConfig, - getDefaultBootstrapServersSecurityConfig, - getDefaultLwM2MServerSecurityConfig, getDefaultProfileClientLwM2mSettingsConfig, getDefaultProfileObserveAttrConfig, PowerMode @@ -384,11 +381,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT case DeviceTransportType.LWM2M: const lwm2mTransportConfiguration: Lwm2mDeviceProfileTransportConfiguration = { observeAttr: getDefaultProfileObserveAttrConfig(), - bootstrap: { - servers: getDefaultBootstrapServersSecurityConfig(), - bootstrapServer: getDefaultBootstrapServerSecurityConfig(), - lwm2mServer: getDefaultLwM2MServerSecurityConfig() - }, + bootstrap: [], clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig() }; transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M}; diff --git a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts index d18ea56644..3187f0661c 100644 --- a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts +++ b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts @@ -14,10 +14,6 @@ /// limitations under the License. /// -export const LEN_MAX_PSK = 128; -export const LEN_MAX_PRIVATE_KEY = 134; -export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; - export enum Lwm2mSecurityType { PSK = 'PSK', RPK = 'RPK', @@ -34,6 +30,29 @@ export const Lwm2mSecurityTypeTranslationMap = new Map( + [ + [Lwm2mSecurityType.PSK, 'device.lwm2m-security-config.client-publicKey-or-id-tooltip-psk'], + [Lwm2mSecurityType.RPK, 'device.lwm2m-security-config.client-publicKey-or-id-tooltip-rpk'], + [Lwm2mSecurityType.X509, 'device.lwm2m-security-config.client-publicKey-or-id-tooltip-x509'] + ] +); + +export const Lwm2mClientSecretKeyTooltipTranslationsMap = new Map( + [ + [Lwm2mSecurityType.PSK, 'device.lwm2m-security-config.client-secret-key-tooltip-psk'], + [Lwm2mSecurityType.RPK, 'device.lwm2m-security-config.client-secret-key-tooltip-prk'], + [Lwm2mSecurityType.X509, 'device.lwm2m-security-config.client-secret-key-tooltip-x509'] + ] +); + +export const Lwm2mClientKeyTooltipTranslationsMap = new Map( + [ + [Lwm2mSecurityType.PSK, 'device.lwm2m-security-config.client-secret-key-tooltip-psk'], + [Lwm2mSecurityType.RPK, 'device.lwm2m-security-config.client-secret-key-tooltip-prk'] + ] +); + export interface ClientSecurityConfig { securityConfigClientMode: Lwm2mSecurityType; endpoint: string; @@ -48,14 +67,9 @@ export interface ServerSecurityConfig { clientSecretKey?: string; } -interface BootstrapSecurityConfig { - bootstrapServer: ServerSecurityConfig; - lwm2mServer: ServerSecurityConfig; -} - export interface Lwm2mSecurityConfigModels { client: ClientSecurityConfig; - bootstrap: BootstrapSecurityConfig; + bootstrap: Array; } @@ -65,10 +79,9 @@ export function getLwm2mSecurityConfigModelsDefault(): Lwm2mSecurityConfigModels securityConfigClientMode: Lwm2mSecurityType.NO_SEC, endpoint: '' }, - bootstrap: { - bootstrapServer: getDefaultServerSecurityConfig(), - lwm2mServer: getDefaultServerSecurityConfig() - } + bootstrap: [ + getDefaultServerSecurityConfig() + ] }; } diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index e8742cc721..903afc2c9d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -959,8 +959,6 @@ "identity-required": "Identita klienta je povinná.", "client-key": "Klíč klienta", "client-key-required": "Klíč klienta je povinný.", - "client-key-pattern": "Klíč klienta musí být v hexadecimálním formátu.", - "client-key-length": "Klíč klienta musí mít {{ count }} znaků.", "endpoint": "Název endpointu klienta", "endpoint-required": "Název endpointu klienta je povinný.", "mode": "Režim konfigurace bezpečnosti", @@ -971,11 +969,8 @@ "lwm2m-server": "LwM2M server", "client-publicKey-or-id": "Veřejný klíč nebo Id klienta", "client-publicKey-or-id-required": "Veřejný klíč nebo Id klienta jsou povinné.", - "client-publicKey-or-id-pattern": "Veřejný klíč klienta nebo Id musí být v hexadecimálním formátu.", "client-secret-key": "Tajný klíč klienta", "client-secret-key-required": "Tajný klíč klienta je povinný.", - "client-secret-key-pattern": "Tajný klíč klienta musí být v hexadecimálním formátu.", - "client-secret-key-length": "Tajný klíč klienta musí mít {{ count }} znaků.", "client-public-key": "Veřejný klíč klienta", "client-public-key-hint": "Jestliže je veřejný klíč klienta prázdný, bude použit důvěryhodný certifikát" }, @@ -1261,7 +1256,6 @@ "view-attribute": "Zobrazit atribut", "remove-attribute": "Odebrat atribut", "mode": "Režim konfigurace bezpečnosti", - "servers": "Servery", "short-id": "Krátké ID", "short-id-required": "Krátké ID je povinné.", "short-id-range": "Krátké ID by mělo být v rozsahu od 1 do 65534.", @@ -1274,21 +1268,7 @@ "default-min-period-pattern": "Minimální interval musí být kladné celé číslo.", "notification-storing": "Ukládání notifikací v případě nedostupnosti", "binding": "Binding", - "binding-type": { - "u": "U: UDP spojení ve standardním režimu", - "uq": "UQ: UDP spojení v queue režimu", - "us": "US: UDP i SMS spojení aktivní, obojí ve standardním režimu", - "uqs": "UQS: UDP i SMS spojení aktivní; UDP v queue režimu, SMS ve standardním režimu", - "t": "T: TCP spojení ve standardním režimu", - "tq": "TQ: TCP spojení v queue režimu", - "ts": "TS: TCP i SMS spojení aktivní, obojí ve standardním režimu", - "tqs": "TQS: TCP i SMS spojení aktivní; TCP v queue režimu, SMS ve standardním režimu", - "s": "S: SMS spojení ve standardním režimu", - "sq": "SQ: SMS spojení v queue režimu" - }, "bootstrap-tab": "Bootstrap", - "bootstrap-server": "Bootstrap server", - "lwm2m-server": "LwM2M server", "server-host": "Host", "server-host-required": "Host je povinný.", "server-port": "Port", @@ -1297,7 +1277,6 @@ "server-port-range": "Port by měl být v rozsahu od 1 do 65535.", "server-public-key": "Veřejný klíč serveru", "server-public-key-required": "Veřejný klíč serveru je povinný.", - "server-public-key-pattern": "Veřejný klíč serveru musí být v hexadecimálním formátu.", "client-hold-off-time": "Čas odložení", "client-hold-off-time-required": "Čas odloženíje povinný.", "client-hold-off-time-pattern": "Čas odložení musí být kladné celé číslo.", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 807f3781a9..5c665e8ffe 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -988,12 +988,16 @@ "lwm2m-security-config": { "identity": "Client Identity", "identity-required": "Client Identity is required.", + "identity-tooltip": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", "client-key": "Client Key", "client-key-required": "Client Key is required.", - "client-key-pattern": "Client Key must be hexadecimal format.", - "client-key-length": "Client Key must be {{ count }} characters.", + "client-key-tooltip-prk": "RPK public key or id must be in the standard [RFC7250] and encoded to Base64 format!", + "client-key-tooltip-psk": "PSK key must be in the standard [RFC4279] and HexDec format: 32, 64, 128 characters!", "endpoint": "Endpoint Client Name", "endpoint-required": "Endpoint Client Name is required.", + "client-public-key": "Client public key", + "client-public-key-hint": "If client public key is empty, the trusted certificate will be used", + "client-public-key-tooltip": "X509 public key must be in DER-encoded X509v3 format and support exclusively EC algorithm and then encoded to Base64 format!", "mode": "Security config mode", "client-tab": "Client Security Config", "client-certificate": "Client certificate", @@ -1002,13 +1006,14 @@ "lwm2m-server": "LwM2M Server", "client-publicKey-or-id": "Client Public Key or Id", "client-publicKey-or-id-required": "Client Public Key or Id is required.", - "client-publicKey-or-id-pattern": "Client Public Key or Id must be hexadecimal format.", + "client-publicKey-or-id-tooltip-psk": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", + "client-publicKey-or-id-tooltip-rpk": "RPK public key or id must be in the standard [RFC7250] and encoded to Base64 format!", + "client-publicKey-or-id-tooltip-x509": "X509 public key must be in DER-encoded X509v3 format and support exclusively EC algorithm and then encoded to Base64 format", "client-secret-key": "Client Secret Key", "client-secret-key-required": "Client Secret Key is required.", - "client-secret-key-pattern": "Client Secret Key must be hexadecimal format.", - "client-secret-key-length": "Client Secret Key must be {{ count }} characters.", - "client-public-key": "Client public key", - "client-public-key-hint": "If client public key is empty, the trusted certificate will be used" + "client-secret-key-tooltip-psk": "PSK key must be in the standard [RFC4279] and HexDec format: 32, 64, 128 characters!", + "client-secret-key-tooltip-prk": "RPK secret key must be in PKCS_8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!", + "client-secret-key-tooltip-x509": "X509 secret key must be in PKCS_8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!" }, "client-id": "Client ID", "client-id-pattern": "Contains invalid character.", @@ -1309,35 +1314,46 @@ "edit-attribute": "Edit attribute", "view-attribute": "View attribute", "remove-attribute": "Remove attribute", + "delete-server-text": "Be careful, after the confirmation the server configuration will become unrecoverable.", + "delete-server-title": "Are you sure you want to delete the server?", "mode": "Security config mode", - "servers": "Servers", - "short-id": "Short ID", - "short-id-required": "Short ID is required.", - "short-id-range": "Short ID should be in a range from 1 to 65534.", - "short-id-pattern": "Short ID must be a positive integer.", + "bootstrap-tab": "Bootstrap", + "bootstrap-server-legend": "Bootstrap Server (ShortId...)", + "lwm2m-server-legend": "LwM2M Server (ShortId...)", + "server": "Server", + "short-id": "Short server ID", + "short-id-tooltip": "Server short Id. Used as link to associate server Object Instance.\nThis identifier uniquely identifies each LwM2M Server configured for the LwM2M Client.\nResource MUST be set when the Bootstrap-Server Resource has a value of 'false'.\nThe values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", + "short-id-required": "Short server ID is required.", + "short-id-range": "Short server ID should be in a range from 1 to 65534.", + "short-id-pattern": "Short server ID must be a positive integer.", "lifetime": "Client registration lifetime", "lifetime-required": "Client registration lifetime is required.", "lifetime-pattern": "Client registration lifetime must be a positive integer.", - "default-min-period": "Minimum period between two notifications (s)", + "default-min-period": "Min period between two notifications (s)", + "default-min-period-tooltip": "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation.", "default-min-period-required": "Minimum period is required.", "default-min-period-pattern": "Minimum period must be a positive integer.", "notification-storing": "Notification storing when disabled or offline", "binding": "Binding", "binding-type": { - "u": "U: UDP connection in standard mode", - "uq": "UQ: UDP connection in queue mode", - "us": "US: both UDP and SMS connections active, both in standard mode", - "uqs": "UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode", - "t": "T: TCP connection in standard mode", - "tq": "TQ: TCP connection in queue mode", - "ts": "TS: both TCP and SMS connections active, both in standard mode", - "tqs": "TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode", - "s": "S: SMS connection in standard mode", - "sq": "SQ: SMS connection in queue mode" + "u": "U: Client is reachable via the UDP binding at any time.", + "m": "M: Client is reachable via the MQTT binding at any time.", + "h": "H: Client is reachable via the HTTP binding at any time.", + "t": "T: Client is reachable via the TCP binding at any time.", + "s": "S: Client is reachable via the SMS binding at any time.", + "n": "N: Client MUST send the response to such a request over the Non-IP binding (is supported since LWM2M 1.1).", + "uq": "UQ: UDP connection in queue mode (is not supported since LWM2M 1.1)", + "uqs": "UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode (is not supported since LWM2M 1.1)", + "tq": "TQ: TCP connection in queue mode (is not supported since LWM2M 1.1)", + "tqs": "TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode (is not supported since LWM2M 1.1)", + "sq": "SQ: SMS connection in queue mode (is not supported since LWM2M 1.1)" }, - "bootstrap-tab": "Bootstrap", + "binding-tooltip": "This is the list in the\"binding\" resource of the LwM2M server object - /1/x/7.\nIndicates the supported binding modes in the LwM2M Client.\nThis value SHOULD be the same as the value in the “Supported Binding and Modes” resource in the Device Object (/3/0/16).\nWhile multiple transports are supported, only one transport binding can be used during the entire Transport Session.\nAs an example, when UDP and SMS are both supported, the LwM2M Client and the LwM2M Server can choose to communicate either over UDP or SMS during the entire Transport Session.", "bootstrap-server": "Bootstrap Server", "lwm2m-server": "LwM2M Server", + "include-bootstrap-server": "Include Bootstrap Server updates", + "bootstrap-update-title": "You already have configured Bootstrap Server. Are you sure you want to exclude the updates?", + "bootstrap-update-text": "Be careful, after the confirmation the Bootstrap Server configuration data will become unrecoverable.", "server-host": "Host", "server-host-required": "Host is required.", "server-port": "Port", @@ -1346,7 +1362,6 @@ "server-port-range": "Port should be in a range from 1 to 65535.", "server-public-key": "Server Public Key", "server-public-key-required": "Server Public Key is required.", - "server-public-key-pattern": "Server Public Key must be hex decimal format.", "client-hold-off-time": "Hold Off Time", "client-hold-off-time-required": "Hold Off Time is required.", "client-hold-off-time-pattern": "Hold Off Time must be a positive integer.", @@ -1355,6 +1370,11 @@ "account-after-timeout-required": "Account after the timeout is required.", "account-after-timeout-pattern": "Account after the timeout must be a positive integer.", "account-after-timeout-tooltip": "Bootstrap-Server Account after the timeout value given by this resource.", + "server-type": "Server type", + "add-new-server-title": "Add new server config", + "add-server-config": "Add server config", + "add-lwm2m-server-config": "Add LwM2M server", + "no-config-servers": "No servers configured", "others-tab": "Other settings", "client-strategy": "Client strategy when connecting", "client-strategy-label": "Strategy", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index 4c84bdca24..5ecc3ed23a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -962,8 +962,6 @@ "identity-required": "İstemci Kimliği gerekli.", "client-key": "İstemci Anahtarı", "client-key-required": "İstemci Anahtarı gerekli.", - "client-key-pattern": "İstemci Anahtarı onaltılık biçimde olmalıdır.", - "client-key-length": "İstemci Anahtarı {{ sayı }} karakter olmalıdır.", "endpoint": "Uç Nokta İstemci Adı", "endpoint-required": "Uç Nokta İstemci Adı gerekli.", "mode": "Güvenlik yapılandırma modu", @@ -974,11 +972,8 @@ "lwm2m-server": "LwM2M Sunucusu", "client-publicKey-or-id": "İstemci Genel Anahtarı veya Kimliği", "client-publicKey-or-id-required": "İstemci Genel Anahtarı veya Kimliği gerekli.", - "client-publicKey-or-id-pattern": "İstemci Genel Anahtarı veya Kimliği onaltılık biçimde olmalıdır.", "client-secret-key": "İstemci Gizli Anahtarı", "client-secret-key-required": "İstemci Gizli Anahtarı gerekli.", - "client-secret-key-pattern": "İstemci Gizli Anahtarı onaltılık biçimde olmalıdır.", - "client-secret-key-length": "İstemci Gizli Anahtarı {{ sayı }} karakter olmalıdır.", "client-public-key": "İstemci açık anahtarı", "client-public-key-hint": "İstemci açık anahtarı boşsa, güvenilen sertifika kullanılacaktır." }, @@ -1274,7 +1269,6 @@ "view-attribute": "Öznitelik görüntüle", "remove-attribute": "Öznitelik kaldır", "mode": "Güvenlik yapılandırma modu", - "servers": "Sunucular", "short-id": "Kısa ID", "short-id-required": "Kısa ID gerekli.", "short-id-range": "Kısa ID 1 ile 65534 aralığında olmalıdır.", @@ -1287,18 +1281,6 @@ "default-min-period-pattern": "Minimum süre pozitif bir tam sayı olmalıdır.", "notification-storing": "Devre dışı bırakıldığında veya çevrimdışı olduğunda bildirim depolama", "binding": "Bağlama", - "binding-type": { - "u": "U: UDP connection in standard mode", - "uq": "UQ: UDP connection in queue mode", - "us": "US: both UDP and SMS connections active, both in standard mode", - "uqs": "UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode", - "t": "T: TCP connection in standard mode", - "tq": "TQ: TCP connection in queue mode", - "ts": "TS: both TCP and SMS connections active, both in standard mode", - "tqs": "TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode", - "s": "S: SMS connection in standard mode", - "sq": "SQ: SMS connection in queue mode" - }, "bootstrap-tab": "Bootstrap", "bootstrap-server": "Bootstrap Sunucusu", "lwm2m-server": "LwM2M Sunucusu", @@ -1310,7 +1292,6 @@ "server-port-range": "Port 1 ila 65535 aralığında olmalıdır.", "server-public-key": "Sunucu Açık Anahtarı", "server-public-key-required": "Sunucu Açık Anahtarı gerekli.", - "server-public-key-pattern": "Sunucu Açık Anahtarı onaltılık biçimde olmalıdır.", "client-hold-off-time": "Bekleme Süresi", "client-hold-off-time-required": "Bekleme Süresi gerekli.", "client-hold-off-time-pattern": "Bekleme Süresi pozitif bir tam sayı olmalıdır.",