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 f320b8eafa..64100363e4 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,7 @@ 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.lwm2m.LwM2MServiceImpl; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; @@ -185,6 +185,9 @@ public abstract class BaseController { @Autowired protected DeviceProfileService deviceProfileService; + @Autowired + protected LwM2MServiceImpl lwM2MService; + @Autowired protected AssetService assetService; @@ -263,9 +266,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 3a68551773..4f74676be5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -157,7 +157,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 64537d9f5f..fb71d4ea91 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -46,7 +46,7 @@ public class Lwm2mController extends BaseController { @ResponseBody public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("isBootstrapServer") boolean bootstrapServer) throws ThingsboardException { try { - return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer); + return lwM2MService.getServerSecurityInfo(bootstrapServer); } catch (Exception e) { throw handleException(e); } 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..7d1a69d270 --- /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.lwm2m.ServerSecurityConfig; + +public interface LwM2MService { + + ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java similarity index 82% rename from application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java rename to application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java index b4ac3d30fd..3a85a94f2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.lwm2m; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.Hex; @@ -25,34 +24,19 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; 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.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.KeySpec; @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 { +public class LwM2MServiceImpl implements LwM2MService { private final LwM2MTransportServerConfig serverConfig; private final LwM2MTransportBootstrapConfig bootstrapConfig; + @Override public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); result.setBootstrapServerIs(bootstrapServer); @@ -83,6 +67,5 @@ public class LwM2MServerSecurityInfoRepository { } return ""; } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java similarity index 50% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 9d2c9acb64..225ae056e9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -13,20 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.security; +package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; -import org.eclipse.californium.core.CoapClient; 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; @@ -35,12 +30,12 @@ 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.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecServerCredentials; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -62,41 +57,18 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; 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; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; @DaoSqlTest public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { - protected final String TRANSPORT_CONFIGURATION = "{\n" + + protected String transportConfiguration = "{\n" + " \"type\": \"LWM2M\",\n" + " \"observeAttr\": {\n" + " \"keyName\": {\n" + @@ -148,135 +120,23 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest 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 String ENDPOINT = "deviceAEndpoint"; - - protected CoapClient client; - + protected LwM2MTestClient client; private final LwM2MBootstrapCredentials defaultBootstrapCredentials; + private String[] resources; + private 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(); - + this.defaultBootstrapCredentials = new LwM2MBootstrapCredentials(); NoSecServerCredentials serverCredentials = new NoSecServerCredentials(); - - defaultBootstrapCredentials.setBootstrapServer(serverCredentials); - defaultBootstrapCredentials.setLwm2mServer(serverCredentials); + 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); @@ -291,9 +151,57 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest wsClient = buildAndConnectWebSocketClient(); } + @Before + public void beforeTest() throws Exception { + this.init(); + } + + @After + public void after() { + executor.shutdownNow(); + wsClient.close(); + clientDestroy(); + } + + public void basicTestConnectionObserveTelemetry(Security security, + LwM2MClientCredentials 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); + 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); @@ -325,103 +233,39 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); - credentials.setClient(clientCredentials); credentials.setBootstrap(defaultBootstrapCredentials); deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + Thread.sleep(1000); 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 NoSecClientCredentials createNoSecClientCredentials(String endpoint) { + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + 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) throws Exception { + clientDestroy(); + client = new LwM2MTestClient(this.executor, this.endpoint); + int clientPort = SocketUtils.findAvailableTcpPort(); + client.init(security, coapConfig, clientPort); } - 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); - int clientPort = SocketUtils.findAvailableTcpPort(); - client.init(security, coapConfig, clientPort); - 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 index 37c126df21..232332e554 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -45,17 +45,22 @@ public class Lwm2mTestHelper { 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; - protected static final int resourceId_1 = 1; - protected static final int resourceId_2 = 2; - protected static final int resourceId_3 = 3; + 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; - protected static final int resourceId_11 = 11; + public static final int resourceId_11 = 11; public static final int resourceId_14 = 14; - protected static final int resourceId_15= 15; + 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"; 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 3081b194a3..3fc83625ba 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 @@ -44,7 +44,6 @@ 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.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.junit.Assert; import org.junit.Assert; @@ -62,12 +61,12 @@ 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.resources; 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 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/security/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java similarity index 58% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/OtaLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index a8a23707c5..de7c432564 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/OtaLwM2MIntegrationTest.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.security.sql; +package org.thingsboard.server.transport.lwm2m.ota.sql; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; -import org.springframework.util.SocketUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; 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.client.LwM2MTestClient; -import org.thingsboard.server.transport.lwm2m.security.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest; import java.util.Arrays; import java.util.Collections; @@ -52,7 +50,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; @Slf4j -public class OtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { +public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { public static final int TIMEOUT = 30; private final String OTA_TRANSPORT_CONFIGURATION = "{\n" + @@ -133,76 +131,64 @@ public class OtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { "}"; @Test - public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { - LwM2MTestClient client = null; + public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception { String endpoint = "WithoutFirmwareInfoDevice"; - try { - createDeviceProfile(TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(endpoint); - Device device = createDevice(clientCredentials); + setEndpoint(endpoint); + createDeviceProfile(transportConfiguration); + NoSecClientCredentials credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + createNewClient(SECURITY, COAP_CONFIG); - client = new LwM2MTestClient(executor, endpoint); - int clientPort = SocketUtils.findAvailableTcpPort(); - client.init(SECURITY, COAP_CONFIG, clientPort); + 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); + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() { - })); + 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); - 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; String endpoint = "Ota5_Device"; - List expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); - try { - createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(endpoint); - Device device = createDevice(clientCredentials); - - device.setFirmwareId(createFirmware().getId()); - Device saveDevice = doPost("/api/device", device, Device.class); - Thread.sleep(1000); - - client = new LwM2MTestClient(executor, endpoint); - int clientPort = SocketUtils.findAvailableTcpPort(); - client.init(SECURITY, COAP_CONFIG, clientPort); - - List ts = await("await on timeseries") - .atMost(30, TimeUnit.SECONDS) - .until(()-> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + saveDevice.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); - } finally { - if (client != null) { - client.destroy(); - } - } + setEndpoint(endpoint); + createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); + NoSecClientCredentials credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + createNewClient(SECURITY, COAP_CONFIG); + + Thread.sleep(1000); + + device.setFirmwareId(createFirmware().getId()); + final Device savedDevice = doPost("/api/device", device, Device.class); + + Thread.sleep(1000); + + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); + + 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); } /** @@ -212,47 +198,40 @@ public class OtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { * */ @Test public void testSoftwareUpdateByObject9() throws Exception { - //given - final List expectedStatuses = List.of( - QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); - LwM2MTestClient client = null; String endpoint = "Ota9_Device"; - try { - createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(endpoint); - final Device device = createDevice(clientCredentials); - device.setSoftwareId(createSoftware().getId()); + setEndpoint(endpoint); + createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); + NoSecClientCredentials credentials = createNoSecClientCredentials(endpoint); + final Device device = createDevice(credentials); + createNewClient(SECURITY, COAP_CONFIG); - final Device savedDevice = doPost("/api/device", device, Device.class); //sync call - assertThat(savedDevice).as("saved device").isNotNull(); - assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); + Thread.sleep(1000); - //when - log.warn("Init the client..."); - client = new LwM2MTestClient(executor, endpoint); - int clientPort = SocketUtils.findAvailableTcpPort(); - client.init(SECURITY, COAP_CONFIG, clientPort); + device.setSoftwareId(createSoftware().getId()); + final Device savedDevice = doPost("/api/device", device, Device.class); //sync call - 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) - .until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), hasSize(expectedStatuses.size())); - log.warn("Got the ts: {}", ts); + Thread.sleep(1000); - ts.sort(Comparator.comparingLong(TsKvEntry::getTs)); - log.warn("Ts ordered: {}", ts); - ts.forEach((x) -> log.warn("ts: {} ", x)); + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - List statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); - log.warn("Converted ts to statuses: {}", statuses); - - assertThat(statuses).isEqualTo(expectedStatuses); - } finally { - if (client != null) { - client.destroy(); - } - } + 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) + .until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), hasSize(expectedStatuses.size())); + log.warn("Got the ts: {}", ts); + + ts.sort(Comparator.comparingLong(TsKvEntry::getTs)); + log.warn("Ts ordered: {}", ts); + 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); } private Device getDeviceFromAPI(UUID deviceId) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/RpcAbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java similarity index 51% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/RpcAbstractLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 36158b2640..83bb46e934 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/RpcAbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -15,43 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.rpc; -import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.io.IOUtils; -import org.junit.After; -import org.junit.Assert; import org.junit.Before; -import org.springframework.util.SocketUtils; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.Device; -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.ResourceType; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; -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; -import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.controller.AbstractWebsocketTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapServers; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MServerBootstrap; -import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.security.AbstractLwM2MIntegrationTest; - -import java.util.Base64; +import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Predicate; @@ -59,20 +30,13 @@ 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.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +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.HOST_BS; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.PORT_BS; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_PORT; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_PORT_BS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.HOST; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.PORT; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID_BS; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; -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.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; @@ -84,7 +48,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; @DaoSqlTest -public class RpcAbstractLwM2MIntegrationTest extends AbstractWebsocketTest { +public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { protected final String RPC_TRANSPORT_CONFIGURATION = "{\n" + " \"type\": \"LWM2M\",\n" + @@ -147,10 +111,6 @@ public class RpcAbstractLwM2MIntegrationTest extends AbstractWebsocketTest { protected static final String ENDPOINT_RPC = "deviceEndpointRpc"; protected ScheduledExecutorService executor; protected TbTestWebSocketClient wsClient; - protected DeviceProfile deviceProfile; - - protected NoSecClientCredentials clientCredentials; - protected LwM2MTestClient client; protected String deviceId; public Set expectedObjects; public Set expectedObjectIdVers; @@ -158,41 +118,36 @@ public class RpcAbstractLwM2MIntegrationTest extends AbstractWebsocketTest { 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; - public RpcAbstractLwM2MIntegrationTest(){ } + public AbstractRpcLwM2MIntegrationTest(){ + setResources(resources); + setEndpoint(ENDPOINT_RPC); + } @Before public void beforeTest() throws Exception { - executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-rpc-scheduled")); - loginTenantAdmin(); - wsClient = buildAndConnectWebSocketClient(); + init(); createDeviceProfile(RPC_TRANSPORT_CONFIGURATION); - clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(ENDPOINT_RPC); - Device device = createDevice(clientCredentials); + + Thread.sleep(1000); + + NoSecClientCredentials credentials = createNoSecClientCredentials(ENDPOINT_RPC); + final Device device = createDevice(credentials); deviceId = device.getId().getId().toString(); - client = new LwM2MTestClient(executor, ENDPOINT_RPC); - int clientPort = SocketUtils.findAvailableTcpPort(); - client.init(SECURITY, COAP_CONFIG, clientPort); - for (String resourceName : resources) { - TbResource lwModel = new TbResource(); - lwModel.setResourceType(ResourceType.LWM2M_MODEL); - lwModel.setTitle(resourceName); - lwModel.setFileName(resourceName); - lwModel.setTenantId(tenantId); - byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); - lwModel.setData(Base64.getEncoder().encodeToString(bytes)); - lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() { - }); - Assert.assertNotNull(lwModel); - } - wsClient = buildAndConnectWebSocketClient(); + createNewClient (SECURITY, COAP_CONFIG); + + Thread.sleep(1000); + expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); @@ -212,96 +167,29 @@ public class RpcAbstractLwM2MIntegrationTest extends AbstractWebsocketTest { }); } }); - objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get(); + 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) 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(); - objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); - } - - protected void createDeviceProfile(String transportConfiguration) throws Exception { - deviceProfile = new DeviceProfile(); - - deviceProfile.setName("LwM2M_RPC"); - deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setTenantId(tenantId); - deviceProfile.setTransportType(DeviceTransportType.LWM2M); - deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); - deviceProfile.setDescription(deviceProfile.getName()); - - DeviceProfileData deviceProfileData = new DeviceProfileData(); - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); - deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); - deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class)); - deviceProfile.setProfileData(deviceProfileData); - - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Assert.assertNotNull(deviceProfile); - } - - - protected Device createDevice(LwM2MClientCredentials clientCredentials) throws Exception { - Device device = new Device(); - device.setName("Device A"); - device.setDeviceProfileId(deviceProfile.getId()); - device.setTenantId(tenantId); - device = doPost("/api/device", device, Device.class); - Assert.assertNotNull(device); - DeviceCredentials deviceCredentials = - doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); - Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); - deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - LwM2MCredentials credentials = new LwM2MCredentials(); - credentials.setClient(clientCredentials); - credentials.setBootstrap(createBootstrapConfig()); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); - doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); - return device; - } - - protected LwM2MBootstrapConfig createBootstrapConfig() { - LwM2MBootstrapConfig bootstrap = new LwM2MBootstrapConfig(); - LwM2MBootstrapServers servers = new LwM2MBootstrapServers(); - servers.setShortId(SHORT_SERVER_ID); - bootstrap.setServers(servers); - LwM2MServerBootstrap server = new LwM2MServerBootstrap(); - server.setHost(HOST); - server.setPort(PORT); - server.setSecurityHost(HOST); - server.setSecurityPort(SECURE_PORT); - server.setServerId(servers.getShortId()); - server.setBootstrapServerIs(false); - bootstrap.setLwm2mServer(server); - LwM2MServerBootstrap serverBS = new LwM2MServerBootstrap(); - serverBS.setHost(HOST_BS); - serverBS.setPort(PORT_BS); - serverBS.setSecurityHost(HOST_BS); - serverBS.setSecurityPort(SECURE_PORT_BS); - serverBS.setServerId(SHORT_SERVER_ID_BS); - serverBS.setBootstrapServerIs(true); - bootstrap.setBootstrapServer(serverBS); - return bootstrap; - } - - protected String objectIdVerToObjectId(String objectIdVer) { - return objectIdVer.contains("_") ? objectIdVer.split("_")[0] : objectIdVer; - } - - protected String objectInstanceIdVerToObjectInstanceId(String objectInstanceIdVer) { - String[] objectIdVer = objectInstanceIdVer.split("/"); - return objectIdVer[1].contains("_") ? objectIdVer[1].split("_")[0] + "/" + objectIdVer[2] : objectInstanceIdVer; + objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); } - @After - public void after() { - if (client != null) { - client.destroy(); - } - executor.shutdownNow(); - if (wsClient != null) { - wsClient.close(); + 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 index a5672be133..4ab0d68794 100644 --- 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 @@ -17,19 +17,38 @@ 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.RpcAbstractLwM2MIntegrationTest; +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 RpcAbstractLwM2MIntegrationTest { +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}} @@ -38,10 +57,9 @@ public class RpcLwm2mIntegrationCreateTest extends RpcAbstractLwM2MIntegrationTe * 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 testCreateObjectInstanceWithInstanceIdAlreadyExistsByIdKey_Result_BAD_REQUEST() throws Exception { + 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); @@ -54,20 +72,58 @@ public class RpcLwm2mIntegrationCreateTest extends RpcAbstractLwM2MIntegrationTe /** * failed: cannot_create_mandatory_single_object - * Create {"id":"/3/2","value":{"0":"00AC"}} + * 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":{"0":"00AC"}} + * 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/2","value":{"0":"00AC"}} + * 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 + " }}"; 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..3bc9c075f1 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java @@ -0,0 +1,353 @@ +/** + * 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)); + } + + /** + * id + * WriteReplace {"id":"/19/0/0","value":"0081"}.. + */ + @Test + public void testWriteReplaceValueMultipleResourceAsSingleResource_Result_BAD_REQUEST_Value_Multi_Instance_Resource_must_be_in_Json_format() throws Exception { + String objectInstanceIdVer_19 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); + String expectedPath = objectInstanceIdVer_19 + "/" + resourceId_0; + String expectedValue = "0081"; + String actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String actualValues = rpcActualResult.get("error").asText(); + String expected = "Resource value is: String. Value of Multi-Instance Resource must be in Json format!"; + 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..491c39a9f9 --- /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.LwM2MBootstrapCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecServerCredentials; +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 LwM2MBootstrapCredentials 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] = rootCAX509Cert; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + + defaultBootstrapCredentials = new LwM2MBootstrapCredentials(); + + NoSecServerCredentials serverCredentials = new NoSecServerCredentials(); + + 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 index af13dd62c4..1d278354b5 100644 --- 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 @@ -18,18 +18,17 @@ 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.NoSecClientCredentials; -import org.thingsboard.server.transport.lwm2m.security.AbstractLwM2MIntegrationTest; +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 AbstractLwM2MIntegrationTest { +public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { @Test public void testConnectAndObserveTelemetry() throws Exception { - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(ENDPOINT); + NoSecClientCredentials clientCredentials = createNoSecClientCredentials(ENDPOINT); super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index cd30ff52e8..23b60482f0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -19,7 +19,7 @@ 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.security.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import java.nio.charset.StandardCharsets; @@ -28,12 +28,10 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP 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(); clientCredentials.setEndpoint(ENDPOINT); clientCredentials.setKey(pskKey); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index 02d57d6782..fdfa56957c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -19,14 +19,14 @@ 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.security.AbstractLwM2MIntegrationTest; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; 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 { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java new file mode 100644 index 0000000000..f6e08f0e3d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -0,0 +1,44 @@ +/** + * 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.X509ClientCredentials; +import org.thingsboard.server.common.transport.util.SslUtil; +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_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { + + @Test + public void testConnectWithCertAndObserveTelemetry() throws Exception { + X509ClientCredentials credentials = new X509ClientCredentials(); + credentials.setEndpoint(ENDPOINT); + credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); + Security security = x509(SECURE_URI, + SHORT_SERVER_ID, + clientX509CertNotTrusted.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/security/sql/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java similarity index 66% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509LwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java index cd172bda46..28289b32bf 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java @@ -18,14 +18,14 @@ 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.X509ClientCredentials; -import org.thingsboard.server.transport.lwm2m.security.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_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { @Test public void testConnectAndObserveTelemetry() throws Exception { @@ -39,18 +39,4 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); } -// @Ignore //See LwM2mClientContextImpl.unregister -// @Test -// public void testConnectWithCertAndObserveTelemetry() throws Exception { -// X509ClientCredentials credentials = new X509ClientCredentials(); -// credentials.setEndpoint(ENDPOINT); -// credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); -// Security security = x509(SECURE_URI, -// 123, -// clientX509CertNotTrusted.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..fdccc2e933 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/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java index 29572bf51a..a57d39776f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.dao.device; +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; +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.security.DeviceCredentials; public interface DeviceCredentialsService { @@ -32,4 +34,5 @@ public interface DeviceCredentialsService { void formatCredentials(DeviceCredentials deviceCredentials); void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); + } 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/data/lwm2m/OtherConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java index 4722246b4f..cf841d6204 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/data/lwm2m/OtherConfiguration.java @@ -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/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index 337e6cf349..4ccf07f2b0 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 @@ -15,6 +15,9 @@ */ package org.thingsboard.server.common.data.security; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.DeviceCredentialsId; @@ -86,5 +89,4 @@ public class DeviceCredentials extends BaseData implements + credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id=" + id + "]"; } - } 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/secure/LwM2MBootstrapSecurityStore.java index fb6149a796..c59f7467a9 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/secure/LwM2MBootstrapSecurityStore.java @@ -43,10 +43,10 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -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.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_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP; @Slf4j @@ -160,7 +160,15 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { 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); + if (SecurityMode.NO_SEC != profileServerBootstrap.getSecurityMode() && profileServerBootstrap != null) { + profileServerBootstrap.setSecurityHost( profileServerBootstrap.getHost()); + profileServerBootstrap.setSecurityPort( profileServerBootstrap.getPort()); + } LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); + if (SecurityMode.NO_SEC != profileLwm2mServer.getSecurityMode() && profileLwm2mServer != null) { + profileLwm2mServer.setSecurityHost(profileLwm2mServer.getHost()); + profileLwm2mServer.setSecurityPort(profileLwm2mServer.getPort()); + } UUID sessionUUiD = UUID.randomUUID(); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); bsSessions.put(store.getEndpoint(), sessionInfo); 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 2e41d17635..da5e8db695 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 @@ -39,7 +39,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -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 LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { @@ -218,7 +218,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession } private void sendLogs (String endpointName, String logMsg) { - log.trace(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/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 898a1ad8b2..8c8624fcf3 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, 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 3d27621c00..dfe5146017 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 @@ -33,7 +33,7 @@ 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.utils.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; @@ -56,10 +56,19 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; 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.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertMultiResourceValuesFromJson; -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; @Slf4j @Service @@ -137,22 +146,24 @@ 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); + 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); } @@ -198,7 +209,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { } } } catch (IllegalArgumentException e){ - log.error("Failed update resource [{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), e.getMessage()); + 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); @@ -211,7 +222,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { Object newValProto = getValueFromKvProto(tsKvProto.getKv()); Object oldResourceValue = this.getResourceValueFormatKv(lwM2MClient, pathIdVer); if (!resourceModel.multiple || !(newValProto instanceof JsonElement)) { - this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, logFailedUpdateOfNonChangedValue); + this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, logFailedUpdateOfNonChangedValue, resourceModel.type); } else { pushUpdateMultiToClientIfNeeded(lwM2MClient, resourceModel, (JsonElement) newValProto, (Map) oldResourceValue, pathIdVer); @@ -220,53 +231,51 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { } private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, - String versionedId, boolean logFailedUpdateOfNonChangedValue) { + String versionedId, boolean logFailedUpdateOfNonChangedValue, ResourceModel.Type type) { 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, newValue); - } else if ((valueOld != null && newValue.toString().equals(valueOld.toString()))){ - if (logFailedUpdateOfNonChangedValue) { - String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed", - LOG_LWM2M_INFO, versionedId, newValue); - logService.log(lwM2MClient, logMsg); - log.info("Failed update resource [{}] [{}]. Value is not changed", versionedId, newValue); - } - } else { + log.error("Failed update resource [{}] [{}]", versionedId, "null"); + } else if ((valueOld == null) || + ((OPAQUE.equals(type) && !Arrays.equals((byte[]) newValue, (byte[]) valueOld)) || + !newValue.equals(valueOld))) { TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); + } 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.warn("Didn't update resource [{}] [{}]. Value is not changed", versionedId, newValue); } } private void pushUpdateMultiToClientIfNeeded(LwM2mClient client, ResourceModel resourceModel, JsonElement newValProto, Map valueOld, String versionedId) { - Map newValues = convertMultiResourceValuesFromJson((JsonObject)newValProto, resourceModel.type, versionedId); - if (newValues.size() > 0) { - if (valueOld != null && valueOld.size() > 0) { - valueOld.values().stream().forEach((v) -> { - if (newValues.containsKey(v.getId())) { - boolean isValueEquals; - if (OPAQUE.equals(v.getType())) { - isValueEquals = Arrays.equals((byte [])newValues.get(v.getId()), (byte[]) v.getValue()); - } else { - isValueEquals = newValues.get(v.getId()).toString().equals(v.getValue().toString()); - } - if (isValueEquals) { - newValues.remove(v.getId()); - } + Map newValues = convertMultiResourceValuesFromJson((JsonObject) newValProto, resourceModel.type, versionedId); + if (newValues.size() > 0 && valueOld != null && valueOld.size() > 0) { + valueOld.values().stream().forEach((v) -> { + if (newValues.containsKey(v.getId())) { + boolean isValueEquals; + if (OPAQUE.equals(v.getType())) { + isValueEquals = Arrays.equals((byte[]) newValues.get(v.getId()), (byte[]) v.getValue()); + } else { + isValueEquals = newValues.get(v.getId()).toString().equals(v.getValue().toString()); } - }); - } + if (isValueEquals) { + 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 { - log.error("Failed update resource [{}] [{}]", versionedId, newValProto); - String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", - LOG_LWM2M_ERROR, versionedId, newValProto); + 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); } } 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 ff2757ba3b..048bc046aa 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 @@ -62,12 +62,12 @@ 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.convertMultiResourceValuesFromRpcBody; -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.LWM2M_OBJECT_VERSION_DEFAULT; +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"}) @@ -363,7 +363,7 @@ public class LwM2mClient implements Serializable { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); if (verSupportedObject == null) { - return String.format("Specified resource id %s is missing from the client!", path); + 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)) || 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 ebf9c11155..71f4a0058a 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 @@ -38,7 +38,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; 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.utils.LwM2mTransportUtil; 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; @@ -57,8 +57,8 @@ 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.getMsgException; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2mTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2mTransportUtil.getMsgException; @Slf4j @Service 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..c99a5ea2a1 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_WARN; @Slf4j public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRequestCallback { 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 d1ed852ac3..309d456663 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 @@ -19,8 +19,10 @@ 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.ResponseCode; 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; @@ -94,9 +96,11 @@ import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.STEP; 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.server.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateVersionedId; +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 @@ -195,9 +199,20 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback) { try { + validateVersionedId(client, request); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(request.getVersionedId())); ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - if (resourceModelExecute != null) { - validateVersionedId(client, request); + 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()))); @@ -206,6 +221,9 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } sendSimpleRequest(client, downlink, request.getTimeout(), callback); } + else { + callback.onValidationError(request.toString(), "Resource with " + request.getVersionedId() + " is not executable."); + } } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); } @@ -328,38 +346,48 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { try { - validateVersionedId(client, request); - WriteRequest downlink = null; LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider()); - 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(), this.config.getModelProvider(), this.converter); - if (resources.size() > 0) { - downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); - } else { - callback.onValidationError(toString(request), "No resources to update!"); - } - } else if (resultIds.isResource()) { - ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - if (resourceModelWrite.multiple) { - if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) { - Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) request.getValue(), resourceModelWrite.type, request.getObjectId()); - downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), - value, resourceModelWrite.type); + if (resultIds.isObjectInstance() || resultIds.isResource()) { + validateVersionedId(client, request); + WriteRequest downlink = null; + ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider()); + 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(), this.config.getModelProvider(), this.converter); + if (resources.size() > 0) { + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); + } else { + callback.onValidationError(toString(request), "No resources to update!"); + } + } + else if (resultIds.isResource()) { + ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); + if (resourceModelWrite != null) { + if (resourceModelWrite.multiple) { + if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) { + Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) request.getValue(), resourceModelWrite.type, request.getObjectId()); + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), + value, resourceModelWrite.type); + } else { + callback.onValidationError(toString(request), "Resource value is bad. Format: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!"); + } + } + } else { + callback.onValidationError(toString(request), "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 value is bad. Format: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!"); + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for ObjectInstance or Multi-Instance Resource !"); } - } } - 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 !"); + else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for ObjectInstance or Resource (multiple)"); } } catch (Exception e) { callback.onValidationError(toString(request), e.getMessage()); @@ -567,7 +595,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } else if (pathIds.isResource()) { ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - if (!resourceModel.multiple) { + if (resourceModel!= null && !resourceModel.multiple) { if (OBJLNK.equals(resourceModel.type)) { return ContentFormat.LINK; } else if (OPAQUE.equals(resourceModel.type)) { 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..5e427fcb31 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,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; public interface HasVersionedId { 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..a24789a410 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; 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..d59ab9f16d 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/TbLwM2MCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index b663215078..85accc5166 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 @@ -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 TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { 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 92992d299c..c14527e3b1 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,7 +16,9 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.util.Hex; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -24,7 +26,8 @@ import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogServic 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 { @@ -49,11 +52,22 @@ public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MReque //TODO convert camelCase to "camel case" using .split("(?= ResponseCode.BAD_REQUEST.getCode()) { + 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)); } - 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)); } private String responseToString (ReadResponse response) { 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/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 4e59eb5b44..ceaa163b63 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 @@ -22,7 +22,7 @@ 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 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/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 8335057caa..6a9fc54971 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 @@ -74,8 +74,8 @@ 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.utils.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2mTransportUtil.convertObjectIdToVersionedId; @Slf4j @Service 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 57217290b5..3d032081ee 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 @@ -21,14 +21,10 @@ 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.core.request.ReadCompositeRequest; -import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -68,15 +64,14 @@ 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.HashSet; 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.server.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2mTransportUtil.fromVersionedIdToObjectId; @Slf4j @Service 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..de033548fb 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/composite/RpcReadResponseCompositeCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java index 11f6849410..ea10ca90ca 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/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 2c4f911db6..b6f8095b60 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,6 +21,7 @@ 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; @@ -61,7 +62,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.attributes.LwM2MAttributesService; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientStateException; @@ -86,6 +86,7 @@ import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; 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.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; @@ -106,12 +107,6 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; 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; @@ -123,6 +118,12 @@ 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 @@ -316,13 +317,13 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl if (objectModelVersion != null) { if (response.getContent() instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); - this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path); + this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path, response.getCode().getCode()); } else if (response.getContent() instanceof LwM2mObjectInstance) { LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent(); - this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path); + this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path, response.getCode().getCode()); } else if (response.getContent() instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); - this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE); + this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE, response.getCode().getCode()); } } if (clientContext.awake(lwM2MClient)) { @@ -341,11 +342,11 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl response.getContent().forEach((k, v) -> { if (v != null) { if (v instanceof LwM2mObject) { - this.updateObjectResourceValue(lwM2MClient, (LwM2mObject) v, k.toString()); + this.updateObjectResourceValue(lwM2MClient, (LwM2mObject) v, k.toString(), response.getCode().getCode()); } else if (v instanceof LwM2mObjectInstance) { - this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString()); + this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString(), response.getCode().getCode()); } else if (v instanceof LwM2mResource) { - this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString(), Mode.UPDATE); + this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString(), Mode.UPDATE, response.getCode().getCode()); } } }); @@ -418,8 +419,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. * @@ -513,19 +512,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, Mode.UPDATE); + this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code); }); } @@ -540,7 +539,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * @param path - resource * @param mode - Replace, Update */ - private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode) { + private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode, int code) { Registration registration = lwM2MClient.getRegistration(); if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider(), mode)) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) { @@ -566,7 +565,9 @@ 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 path [{}] Resource [{}]", path, lwM2mResource); } @@ -710,38 +711,39 @@ 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, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE); - 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, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE); + 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 onCreateResponseOk(LwM2mClient client, String path, CreateRequest request) { if (request.getObjectInstances() != null && request.getObjectInstances().size() > 0) { - request.getObjectInstances().forEach( instance -> + request.getObjectInstances().forEach(instance -> instance.getResources() ); -// this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE); clientContext.update(client); } } @Override - public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) { + public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request, int code) { log.trace("ReadCompositeResponse: [{}]", request.getNodes()); request.getNodes().forEach((k, v) -> { if (v instanceof LwM2mSingleResource) { - this.updateResourcesValue(client, (LwM2mResource) v, k.toString(), Mode.REPLACE); + 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); + this.updateResourcesValue(client, multipleResource, k.toString(), Mode.REPLACE, code); } }); } 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 0749ab34f6..af0d349687 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 @@ -55,11 +55,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 onCreateResponseOk(LwM2mClient client, String path, CreateRequest request); - void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest 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 95% 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 52d73137e4..69cee060ab 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,7 +13,7 @@ * 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; @@ -24,8 +24,11 @@ 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; @@ -42,7 +45,10 @@ 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.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.ota.OtaPackageKey; +import org.thingsboard.server.common.data.ota.OtaPackageType; 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; @@ -80,6 +86,7 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; @@ -297,7 +304,8 @@ public class LwM2mTransportUtil { 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); @@ -313,9 +321,9 @@ public class LwM2mTransportUtil { } /** - * "UNSIGNED_INTEGER": // Number -> Integer Example: - * Alarm Timestamp [32-bit unsigned integer] - * Short Server ID, Object ID, Object Instance ID, Resource ID, Resource Instance ID + * "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) { @@ -346,7 +354,7 @@ public class LwM2mTransportUtil { } else { msgExceptionStr = client.isValidObjectVersion(request.getVersionedId()); } - if (!msgExceptionStr.isEmpty() ) { + if (!msgExceptionStr.isEmpty()) { throw new IllegalArgumentException(msgExceptionStr); } } @@ -524,4 +532,15 @@ 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 (getAttributeKey(OtaPackageType.FIRMWARE, value).equals(attrName)) return true; + if (getAttributeKey(OtaPackageType.SOFTWARE, value).equals(attrName)) return true; + } + return false; + } } 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/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index bdc03d767c..dbe42cf9f4 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 @@ -899,7 +899,6 @@ public class DefaultTransportService implements TransportService { String resourceId = msg.getResourceKey(); transportResourceCache.update(tenantId, resourceType, resourceId); sessions.forEach((id, mdRez) -> { - log.warn("ResourceUpdate - [{}] [{}]", id, mdRez); transportCallbackExecutor.submit(() -> mdRez.getListener().onResourceUpdate(Optional.ofNullable(msg))); }); 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 da50963bd4..a71be5fe5f 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.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Created by Valerii Sosliuk on 5/12/2017. @@ -123,4 +125,25 @@ public class JacksonUtil { return OBJECT_MAPPER.treeToValue(tree, type); } + + public static String validateFieldsToTree(JsonNode nodeCredentialsValue, String[] fields, String delimiter) { + try { + Set msgSet = ConcurrentHashMap.newKeySet(); + for (String field : fields) { + String[] keys = field.split(delimiter); + JsonNode nodeVal = nodeCredentialsValue; + for (String key : keys) { + if (!nodeVal.hasNonNull(key)) { + msgSet.add(keys[keys.length - 1]); + break; + } else { + nodeVal = nodeVal.get(key); + } + } + } + return String.join(", ", msgSet); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } } 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 f62d25dcec..969745ae31 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 @@ -183,7 +183,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen break; case X509: X509ClientCredentials x509Config = (X509ClientCredentials) clientCredentials; - if (x509Config.getCert() != null) { + if ((StringUtils.isNotBlank(x509Config.getCert()))) { String cert = EncryptionUtil.trimNewLines(x509Config.getCert()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); x509Config.setCert(cert); @@ -272,7 +272,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen break; case X509: X509ClientCredentials x509CCredentials = (X509ClientCredentials) clientCredentials; - if (x509CCredentials.getCert() != null) { + if (StringUtils.isNotBlank(x509CCredentials.getCert())) { try { SecurityUtil.certificate.decode(Base64.decodeBase64(x509CCredentials.getCert())); } catch (Exception e) { 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 720d50ad69..adbaff5389 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 @@ -54,6 +54,7 @@ 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; @@ -132,6 +133,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @Autowired private DashboardService dashboardService; + @Autowired + private DeviceCredentialsService deviceCredentialsService; + private final Lock findOrCreateLock = new ReentrantLock(); @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") @@ -409,6 +413,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); } } + } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) { +// deviceCredentialsService.verifyLwm2mSecurityKeyDeviceProfile((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration); } List profileAlarms = deviceProfile.getProfileData().getAlarms(); 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 9f6748f8fd..5fab824aa1 100644 Binary files a/transport/lwm2m/src/main/data/lwm2mserver.jks and b/transport/lwm2m/src/main/data/lwm2mserver.jks differ 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 => {