From 92719c2ac2f5aad06836915718ad0b18e33995b5 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 11 May 2021 14:14:00 +0300 Subject: [PATCH 1/3] added lwm2m x509 test --- .../transport/TransportSqlTestSuite.java | 14 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 133 +++++++++++- .../lwm2m/NoSecLwM2MIntegrationTest.java | 9 +- .../lwm2m/X509LwM2MIntegrationTest.java | 205 ++++++++++++++++++ .../lwm2m/client/LwM2MTestClient.java | 11 +- .../resources/application-test.properties | 2 + .../lwm2m/credentials/clientKeyStore.jks | Bin 0 -> 3180 bytes .../lwm2m/credentials/serverKeyStore.jks | Bin 0 -> 3120 bytes .../lwm2m/secure/TbLwM2MAuthorizer.java | 2 +- .../X509ClientCredentialsConfig.java | 5 +- .../DefaultLwM2MTransportMsgHandler.java | 6 +- .../server/client/LwM2mClientContextImpl.java | 1 + .../TbL2M2MDtlsSessionInMemoryStore.java | 5 + .../server/store/TbLwM2MDtlsSessionStore.java | 3 + .../device/DeviceCredentialsServiceImpl.java | 19 +- 15 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java create mode 100644 application/src/test/resources/application-test.properties create mode 100644 application/src/test/resources/lwm2m/credentials/clientKeyStore.jks create mode 100644 application/src/test/resources/lwm2m/credentials/serverKeyStore.jks diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 25df3bee00..d059ea1449 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.transport.*.rpc.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", - "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", - "org.thingsboard.server.transport.*.attributes.request.sql.*Test", - "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test", +// "org.thingsboard.server.transport.*.rpc.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", +// "org.thingsboard.server.transport.*.claim.sql.*Test", +// "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 97f7bfbc3c..91b2afc8c6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; +import org.eclipse.leshan.core.util.Hex; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -35,6 +36,23 @@ import org.thingsboard.server.controller.AbstractWebsocketTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; +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.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -46,6 +64,114 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { 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]; + + 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); + } + } + @Before public void beforeTest() throws Exception { executor = Executors.newScheduledThreadPool(10); @@ -60,7 +186,8 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { 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<>(){}); + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() { + }); Assert.assertNotNull(lwModel); } wsClient = buildAndConnectWebSocketClient(); @@ -69,7 +196,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { protected void createDeviceProfile(String transportConfiguration) throws Exception { deviceProfile = new DeviceProfile(); - deviceProfile.setName("LwM2M No Security"); + deviceProfile.setName("LwM2M"); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTenantId(tenantId); deviceProfile.setTransportType(DeviceTransportType.LWM2M); @@ -87,7 +214,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { } @After - public void after() { + public void after() throws InterruptedException { executor.shutdownNow(); wsClient.close(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 809f1f609b..f4a7f7f9e2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Test; @@ -39,6 +41,7 @@ import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCred import java.util.Collections; import java.util.List; +import static org.eclipse.leshan.client.object.Security.noSec; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -91,6 +94,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { " }\n" + "}"; + private final int port = 5685; + private final Security security = noSec("coap://localhost:" + port, 123); + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); + @NotNull private Device createDevice(String deviceAEndpoint) throws Exception { Device device = new Device(); @@ -138,7 +145,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { wsClient.registerWaitForUpdate(); LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); - client.init(); + client.init(security, coapConfig); String msg = wsClient.waitForUpdate(); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java new file mode 100644 index 0000000000..06541e5c56 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -0,0 +1,205 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m; + +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +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 org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; + +import java.util.Collections; +import java.util.List; + +import static org.eclipse.leshan.client.object.Security.x509; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"UQ\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\n" + + " },\n" + + " \"lwm2mServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5686,\n" + + " \"serverId\": 123,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " \"bootstrapServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5687,\n" + + " \"serverId\": 111,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + + + private final int port = 5686; + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); + private final String endpoint = "deviceAEndpoint"; + private final String serverUri = "coaps://localhost:" + port; + + @NotNull + private Device createDevice(String credentialsId, X509ClientCredentialsConfig credentialsConfig) 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); + + deviceCredentials.setCredentialsId(credentialsId); + + LwM2MCredentials X509Credentials = new LwM2MCredentials(); + + X509Credentials.setClient(credentialsConfig); + + deviceCredentials.setCredentialsValue(JacksonUtil.toString(X509Credentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + Device device = createDevice(endpoint, new X509ClientCredentialsConfig(null, null)); + + 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(); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); + client.init(security, coapConfig); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + client.destroy(); + } + + @Test + public void testConnectWithCertAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + Device device = createDevice(null, new X509ClientCredentialsConfig(SslUtil.getCertificateString(clientX509CertNotTrusted), endpoint)); + + 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(); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + + Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); + + client.init(security, coapConfig); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + client.destroy(); + } + +} 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 6061813bcf..8a17b6e3c9 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 @@ -32,6 +32,7 @@ import org.eclipse.californium.scandium.dtls.SessionAdapter; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.ObjectsInitializer; @@ -54,7 +55,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; -import static org.eclipse.leshan.client.object.Security.noSec; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; @@ -67,7 +67,7 @@ public class LwM2MTestClient { private final String endpoint; private LeshanClient client; - public void init() { + public void init(Security security, NetworkConfig coapConfig) { String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; List models = new ArrayList<>(); for (String resourceName : resources) { @@ -75,13 +75,10 @@ public class LwM2MTestClient { } LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); - initializer.setInstancesForObject(SECURITY, noSec("coap://localhost:5685", 123)); + initializer.setInstancesForObject(SECURITY, security); initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); - NetworkConfig coapConfig = new NetworkConfig(); - coapConfig.setString("COAP_PORT", Integer.toString(5685)); - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setRecommendedCipherSuitesOnly(true); @@ -256,7 +253,7 @@ public class LwM2MTestClient { } public void destroy() { - client.stop(false); + client.destroy(true); } } diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties new file mode 100644 index 0000000000..6638504b2f --- /dev/null +++ b/application/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks +transport.lwm2m.security.key_store_password=server \ No newline at end of file diff --git a/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks b/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..7cc58589b72517c57a1086144275218ad04b75de GIT binary patch literal 3180 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 MoMV8L095h60D(LA`2YX_ literal 0 HcmV?d00001 diff --git a/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks b/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..f1f03005e129985fd17b09e8dd6e0e2caa2d366f GIT binary patch literal 3120 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 qL;D3%%-d?jak~%bVSeI3r^Om(r)};qh1!z#p-K Date: Tue, 11 May 2021 14:48:32 +0300 Subject: [PATCH 2/3] refactored --- .../server/transport/TransportSqlTestSuite.java | 14 +++++++------- .../lwm2m/AbstractLwM2MIntegrationTest.java | 2 +- .../transport/lwm2m/NoSecLwM2MIntegrationTest.java | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d059ea1449..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ -// "org.thingsboard.server.transport.*.rpc.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", -// "org.thingsboard.server.transport.*.claim.sql.*Test", -// "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.*.rpc.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", + "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", + "org.thingsboard.server.transport.*.attributes.request.sql.*Test", + "org.thingsboard.server.transport.*.claim.sql.*Test", + "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 91b2afc8c6..46ad3c1c34 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -214,7 +214,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { } @After - public void after() throws InterruptedException { + public void after() { executor.shutdownNow(); wsClient.close(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index f4a7f7f9e2..72f4d041f3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -95,7 +95,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { "}"; private final int port = 5685; - private final Security security = noSec("coap://localhost:" + port, 123); + private final Security security = noSec("coap://localhost:" + port, 123); private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); @NotNull From b59c846885764bded676deab7949365728ad74c4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 12 May 2021 18:38:12 +0300 Subject: [PATCH 3/3] Improvements to data converter --- .../transport/adaptor/JsonConverter.java | 32 +++++++++++++------ .../src/test/java/JsonConverterTest.java | 8 +++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index a4e8c60841..4db4aa9bd8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -226,19 +226,29 @@ public class JsonConverter { } private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { - if (value.getAsString().contains(".")) { - return KeyValueProto.newBuilder() - .setKey(key) - .setType(KeyValueType.DOUBLE_V) - .setDoubleV(value.getAsDouble()) - .build(); + String valueAsString = value.getAsString(); + KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key); + if (valueAsString.contains("e") || valueAsString.contains("E")) { + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + var bd = new BigDecimal(valueAsString); + if (bd.stripTrailingZeros().scale() <= 0) { + try { + return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); + } catch (ArithmeticException e) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } + } else { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } + } else if (valueAsString.contains(".")) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(value.getAsDouble()).build(); } else { try { long longValue = Long.parseLong(value.getAsString()); - return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V) - .setLongV(longValue).build(); + return builder.setType(KeyValueType.LONG_V).setLongV(longValue).build(); } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(new BigDecimal(valueAsString).doubleValue()).build(); } } } @@ -252,6 +262,7 @@ public class JsonConverter { String valueAsString = value.getAsString(); String key = valueEntry.getKey(); if (valueAsString.contains("e") || valueAsString.contains("E")) { + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String var bd = new BigDecimal(valueAsString); if (bd.stripTrailingZeros().scale() <= 0) { try { @@ -269,7 +280,8 @@ public class JsonConverter { long longValue = Long.parseLong(value.getAsString()); result.add(new LongDataEntry(key, longValue)); } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + result.add(new DoubleDataEntry(key, new BigDecimal(valueAsString).doubleValue())); } } } diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java index cedbef50c9..dc28b268f5 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -21,6 +21,8 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import java.util.ArrayList; + @RunWith(MockitoJUnitRunner.class) public class JsonConverterTest { @@ -38,6 +40,12 @@ public class JsonConverterTest { Assert.assertEquals(10.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); } + @Test + public void testParseAttributesBigDecimalAsLong() { + var result = new ArrayList<>(JsonConverter.convertToAttributes(JSON_PARSER.parse("{\"meterReadingDelta\": 1E1}"))); + Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue()); + } + @Test public void testParseAsDouble() { var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L);