Browse Source

Merge pull request #5171 from thingsboard/lwm2m_tests_with_cherry

[3.3.2]Lwm2m tests with cherry
pull/5268/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
ceb2a05068
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 1
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  3. 2
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  4. 24
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java
  5. 21
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java
  6. 310
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  7. 15
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  8. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  9. 76
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java
  10. 175
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  11. 194
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  12. 74
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java
  13. 96
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDeleteTest.java
  14. 165
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  15. 186
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java
  16. 190
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  17. 226
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java
  18. 63
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java
  19. 353
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java
  20. 176
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  21. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java
  22. 6
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  23. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java
  24. 44
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java
  25. 18
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java
  26. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelperTest.java
  27. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
  28. 8
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  29. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java
  30. 4
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java
  31. 16
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  32. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  33. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  34. 113
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java
  35. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  36. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  37. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java
  38. 96
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  39. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java
  40. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedIds.java
  41. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java
  42. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java
  43. 20
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java
  44. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java
  45. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MWriteResponseCompositeCallback.java
  46. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java
  47. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  48. 9
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  49. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java
  50. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java
  51. 62
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  52. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
  53. 31
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mTransportUtil.java
  54. 1
      common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
  55. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  56. 23
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  57. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  58. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  59. 2
      transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh
  60. BIN
      transport/lwm2m/src/main/data/lwm2mserver.jks
  61. 8
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts

8
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;

1
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);

2
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);
}

24
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);
}

21
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java → 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 "";
}
}

310
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java → 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<EntityData> 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<EntityData> 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();
}
}
}

15
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";

3
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

76
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);
}
}

175
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/OtaLwM2MIntegrationTest.java → 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<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() {
}));
List<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" +
savedDevice.getId().getId() + "/values/timeseries?keys=fw_state", new TypeReference<>() {}));
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
List<OtaPackageUpdateStatus> expectedStatuses = Collections.singletonList(FAILED);
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
List<OtaPackageUpdateStatus> 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<OtaPackageUpdateStatus> 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<TsKvEntry> 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<OtaPackageUpdateStatus> 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<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
List<TsKvEntry> 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<OtaPackageUpdateStatus> 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<OtaPackageUpdateStatus> 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<TsKvEntry> by API with list size {}...", TIMEOUT, expectedStatuses.size());
List<TsKvEntry> 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<OtaPackageUpdateStatus> 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<OtaPackageUpdateStatus> expectedStatuses = List.of(
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED);
log.warn("AWAIT atMost {} SECONDS on timeseries List<TsKvEntry> by API with list size {}...", TIMEOUT, expectedStatuses.size());
List<TsKvEntry> 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<OtaPackageUpdateStatus> 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 {

194
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/RpcAbstractLwM2MIntegrationTest.java → 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;
}
}

74
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 + " }}";

96
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());
}
}

165
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":"</3/0>,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>,</3/0/4>,</3/0/5>,</3/0/6>,</3/0/7>,</3/0/8>,</3/0/9>,</3/0/10>,</3/0/11>,</3/0/12>,</3/0/13>,</3/0/14>,</3/0/15>,</3/0/16>,</3/0/1
* 7>,</3/0/18>,</3/0/19>,</3/0/20>,</3/0/21>,</3/0/22>"}
* If WriteAttributes implemented and WriteAttributes saved
* Discover {"id":"19/0"}
* {"result":"CONTENT","value":"[</19/0>;dim=2;pmin=10;pmax=60;gt=50;lt=42.2,</19/0/0>;pmax=120, </19/0/1>, </19/0/2>, </19/0/3>, </19/0/4>, </19/0/5>;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":"</3/0/14>;pmax=100, "pmin":10, "ver"=1.0"}
* If WriteAttributes not implemented:
* {"result":"CONTENT","value":"</3/0/14>"}
* Discover {"id":"19_1.1/0/0"}
* If WriteAttributes implemented:
* {"result":"CONTENT","value":"</19/0/0>;pmax=100, "pmin":10, "ver"=1.1"}
* If WriteAttributes not implemented:
* {"result":"CONTENT","value":"</19/0/0>"}
*/
@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());
}
}

186
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());
}
}

190
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());
}
}

226
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());
}
}

63
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());
}
}

353
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());
}
}

176
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);
}
}

7
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);
}

6
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);

4
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 {

44
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);
}
}

18
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509LwM2MIntegrationTest.java → 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);
// }
}

2
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 {

3
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);
}

8
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();
}
}

2
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;
}

4
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<DeviceCredentialsId> implements
+ credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id="
+ id + "]";
}
}

16
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);

4
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);
}
}

8
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<Observation> observations, boolean expired,

113
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<TransportProtos.TsKvProto> 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<Integer, LwM2mResourceInstance>) 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<Integer, LwM2mResourceInstance> 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);
}
}

14
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)) ||

6
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

2
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<R, T> implements DownlinkRequestCallback<R, T> {

96
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<ExecuteRequest, ExecuteResponse> 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<WriteRequest, WriteResponse> 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<LwM2mResource> 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<LwM2mResource> 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)) {

2
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 {

2
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;

2
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<TbLwM2MCancelAllRequest, Integer> {

2
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<TbLwM2MCancelObserveRequest, Integer> {

20
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<R, T> extends AbstractTbLwM2MRequestCallback<R, T> {
@ -49,11 +52,22 @@ public abstract class TbLwM2MTargetedCallback<R, T> extends AbstractTbLwM2MReque
//TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")
String requestName = request.getClass().getSimpleName();
String responseStr = response.toString();
int code = 0;
if (response instanceof ReadResponse) {
responseStr = responseToString ((ReadResponse) response);
code = ((ReadResponse) response).getCode().getCode();
}
else if (response instanceof LwM2mResponse) {
code = ((LwM2mResponse) response).getCode().getCode();
}
if (code >= 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) {

4
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<WriteRequest, WriteResponse> {
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());
}
}

2
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());
}
}

2
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

4
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

9
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

2
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<R extends LwM2mRequest<T>, T extends ReadResponse> extends RpcLwM2MDownlinkCallback<R, T> {

2
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<R extends LwM2mRequest<T>, T extends ReadCompositeResponse> extends RpcLwM2MDownlinkCallback<R, T> {

62
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);
}
});
}

4
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);

31
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java → 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<Attribute> attributeLists = new ArrayList<>();
Map<String, Object> map = JacksonUtil.convertValue(params, new TypeReference<>() {});
Map<String, Object> 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;
}
}

1
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

1
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)));
});

23
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);
}
}
}

4
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) {

6
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<DeviceProfileAlarm> profileAlarms = deviceProfile.getProfileData().getAlarms();

2
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

BIN
transport/lwm2m/src/main/data/lwm2mserver.jks

Binary file not shown.

8
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. <MultipleInstances> == Multiple (true), <Mandatory> == 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. <MultipleInstances> == Multiple (true), <Mandatory> == 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. <MultipleInstances> == Single (false), <Mandatory> == 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. <MultipleInstances> == Single (false), <Mandatory> == 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 => {

Loading…
Cancel
Save