Browse Source

Merge pull request #5156 from thingsboard/feature/lwm2m-improvements5

Lwm2m: tests5
pull/5169/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
a9460fa22a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
  2. 10
      application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
  3. 4
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java
  4. 3
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java
  5. 2
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java
  6. 2
      application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java
  7. 2
      application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java
  8. 2
      application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java
  9. 2
      application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java
  10. 4
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java
  11. 2
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java
  12. 4
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java
  13. 65
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  14. 5
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java
  15. 41
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java
  16. 75
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  17. 231
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java
  18. 150
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java
  19. 131
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java
  20. 10
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  21. 6
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java
  22. 307
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/RpcAbstractLwM2MIntegrationTest.java
  23. 77
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java
  24. 17
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java
  25. 36
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java
  26. 120
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/OtaLwM2MIntegrationTest.java
  27. 9
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  28. 9
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java
  29. 37
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509LwM2MIntegrationTest.java
  30. 144
      application/src/test/resources/lwm2m/19.xml
  31. 103
      application/src/test/resources/lwm2m/3303.xml
  32. 143
      application/src/test/resources/lwm2m/6.xml
  33. 16
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  34. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java
  35. 19
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  36. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java
  37. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java
  38. 178
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  39. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  40. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java
  41. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  42. 15
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java
  43. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  44. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  45. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  46. 72
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java
  47. 113
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java
  48. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java
  49. 121
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  50. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  51. 34
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  52. 44
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java
  53. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java
  54. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java
  55. 423
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  56. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java
  57. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java
  58. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java
  59. 50
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java
  60. 36
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java
  61. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java
  62. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java
  63. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java
  64. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java
  65. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java
  66. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java
  67. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java
  68. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java
  69. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java
  70. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java
  71. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  72. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java
  73. 103
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  74. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java
  75. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java
  76. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java
  77. 18
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java
  78. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java
  79. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java
  80. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java
  81. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java
  82. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java
  83. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java
  84. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java
  85. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java
  86. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  87. 60
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  88. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
  89. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java
  90. 2
      pom.xml

2
application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java

@ -33,7 +33,7 @@ import java.util.Arrays;
"org.thingsboard.server.transport.*.attributes.request.sql.*Test", "org.thingsboard.server.transport.*.attributes.request.sql.*Test",
"org.thingsboard.server.transport.*.claim.sql.*Test", "org.thingsboard.server.transport.*.claim.sql.*Test",
"org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.*.provision.sql.*Test",
"org.thingsboard.server.transport.lwm2m.sql.*Test" "org.thingsboard.server.transport.lwm2m.*.sql.*Test"
}) })
public class TransportSqlTestSuite { public class TransportSqlTestSuite {

10
application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java

@ -52,6 +52,16 @@ import static org.junit.Assert.assertNotNull;
@Slf4j @Slf4j
public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest { public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest {
protected CoapClient client;
@Override
protected void processAfterTest() throws Exception {
if (client != null) {
client.shutdown();
}
super.processAfterTest();
}
protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception {
this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
} }

4
application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java

@ -75,7 +75,7 @@ public abstract class AbstractCoapAttributesRequestIntegrationTest extends Abstr
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
String featureTokenUrl = getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + keys + "&sharedKeys=" + keys; String featureTokenUrl = getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + keys + "&sharedKeys=" + keys;
CoapClient client = getCoapClient(featureTokenUrl); client = getCoapClient(featureTokenUrl);
CoapResponse getAttributesResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); CoapResponse getAttributesResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).get();
validateResponse(getAttributesResponse); validateResponse(getAttributesResponse);
@ -83,7 +83,7 @@ public abstract class AbstractCoapAttributesRequestIntegrationTest extends Abstr
protected void postAttributes() throws Exception { protected void postAttributes() throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); client = getCoapClient(FeatureType.ATTRIBUTES);
CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(POST_ATTRIBUTES_PAYLOAD.getBytes(), MediaTypeRegistry.APPLICATION_JSON); CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(POST_ATTRIBUTES_PAYLOAD.getBytes(), MediaTypeRegistry.APPLICATION_JSON);
assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode());
} }

3
application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java

@ -21,7 +21,6 @@ import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.MediaTypeRegistry;
@ -124,7 +123,7 @@ public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends
.setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject)
.build(); .build();
byte[] payload = postAttributesMsg.toByteArray(); byte[] payload = postAttributesMsg.toByteArray();
CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); client = getCoapClient(FeatureType.ATTRIBUTES);
CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(payload, MediaTypeRegistry.APPLICATION_JSON); CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(payload, MediaTypeRegistry.APPLICATION_JSON);
assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode());
} }

2
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java

@ -71,7 +71,7 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
if (!emptyCurrentStateNotification) { if (!emptyCurrentStateNotification) {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION, String.class, status().isOk());
} }
CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); client = getCoapClient(FeatureType.ATTRIBUTES);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
TestCoapCallback callback = new TestCoapCallback(latch); TestCoapCallback callback = new TestCoapCallback(latch);

2
application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java

@ -90,7 +90,7 @@ public abstract class AbstractCoapClaimDeviceTest extends AbstractCoapIntegratio
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
log.warn("[testClaimingDevice] Device: {}, Transport type: {}", savedDevice.getName(), savedDevice.getType()); log.warn("[testClaimingDevice] Device: {}, Transport type: {}", savedDevice.getName(), savedDevice.getType());
CoapClient client = getCoapClient(FeatureType.CLAIM); client = getCoapClient(FeatureType.CLAIM);
byte[] payloadBytes; byte[] payloadBytes;
byte[] failurePayloadBytes; byte[] failurePayloadBytes;
if (emptyPayload) { if (emptyPayload) {

2
application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java

@ -49,7 +49,7 @@ public abstract class AbstractCoapClaimProtoDeviceTest extends AbstractCoapClaim
@Override @Override
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
CoapClient client = getCoapClient(FeatureType.CLAIM); client = getCoapClient(FeatureType.CLAIM);
byte[] payloadBytes; byte[] payloadBytes;
if (emptyPayload) { if (emptyPayload) {
TransportApiProtos.ClaimDevice claimDevice = getClaimDevice(0, emptyPayload); TransportApiProtos.ClaimDevice claimDevice = getClaimDevice(0, emptyPayload);

2
application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java

@ -180,7 +180,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
private CoapResponse createCoapClientAndPublish(String deviceCredentials) throws Exception { private CoapResponse createCoapClientAndPublish(String deviceCredentials) throws Exception {
String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); String provisionRequestMsg = createTestProvisionMessage(deviceCredentials);
CoapClient client = getCoapClient(FeatureType.PROVISION); client = getCoapClient(FeatureType.PROVISION);
return postProvision(client, provisionRequestMsg.getBytes()); return postProvision(client, provisionRequestMsg.getBytes());
} }

2
application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java

@ -176,7 +176,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
} }
private CoapResponse createCoapClientAndPublish(byte[] provisionRequestMsg) throws Exception { private CoapResponse createCoapClientAndPublish(byte[] provisionRequestMsg) throws Exception {
CoapClient client = getCoapClient(FeatureType.PROVISION); client = getCoapClient(FeatureType.PROVISION);
return postProvision(client, provisionRequestMsg); return postProvision(client, provisionRequestMsg);
} }

4
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java

@ -54,7 +54,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
} }
protected void processOneWayRpcTest() throws Exception { protected void processOneWayRpcTest() throws Exception {
CoapClient client = getCoapClient(FeatureType.RPC); client = getCoapClient(FeatureType.RPC);
client.useCONs(); client.useCONs();
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
@ -82,7 +82,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
} }
protected void processTwoWayRpcTest(String expectedResponseResult) throws Exception { protected void processTwoWayRpcTest(String expectedResponseResult) throws Exception {
CoapClient client = getCoapClient(FeatureType.RPC); client = getCoapClient(FeatureType.RPC);
client.useCONs(); client.useCONs();
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);

2
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java

@ -69,7 +69,7 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap
} }
protected void processAttributesTest(List<String> expectedKeys, byte[] payload, boolean presenceFieldsTest) throws Exception { protected void processAttributesTest(List<String> expectedKeys, byte[] payload, boolean presenceFieldsTest) throws Exception {
CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); client = getCoapClient(FeatureType.ATTRIBUTES);
postAttributes(client, payload); postAttributes(client, payload);

4
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java

@ -71,8 +71,8 @@ public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoap
} }
protected void processTestPostTelemetry(byte[] payloadBytes, List<String> expectedKeys, boolean withTs, boolean presenceFieldsTest) throws Exception { protected void processTestPostTelemetry(byte[] payloadBytes, List<String> expectedKeys, boolean withTs, boolean presenceFieldsTest) throws Exception {
CoapClient coapClient = getCoapClient(FeatureType.TELEMETRY); client = getCoapClient(FeatureType.TELEMETRY);
postTelemetry(coapClient, payloadBytes); postTelemetry(client, payloadBytes);
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();

65
application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java

@ -0,0 +1,65 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import static org.eclipse.leshan.client.object.Security.noSec;
public class Lwm2mTestHelper {
// Server
public static final int SECURE_PORT = 5686;
public static final int SECURE_PORT_BS = 5688;
public static final String HOST = "localhost";
public static final String HOST_BS = "localhost";
public static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT));
public static final String ENDPOINT_SECURITY = "deviceAEndpoint";
public static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT;
public static final int PORT = 5685;
public static final int PORT_BS = 5687;
public static final int SHORT_SERVER_ID = 123;
public static final int SHORT_SERVER_ID_BS = 111;
public static final Security SECURITY = noSec("coap://localhost:" + PORT, SHORT_SERVER_ID);
public static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
// Models
public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"};
public static final int BINARY_APP_DATA_CONTAINER = 19;
public static final int TEMPERATURE_SENSOR = 3303;
// Ids in Client
public static final int objectInstanceId_0 = 0;
public static final int objectInstanceId_1 = 1;
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_9 = 9;
protected static final int resourceId_11 = 11;
public static final int resourceId_14 = 14;
protected static final int resourceId_15= 15;
public static final String resourceIdName_3_9 = "batteryLevel";
public static final String resourceIdName_3_14 = "UtfOffset";
public static final String resourceIdName_19_0_0 = "dataRead";
public static final String resourceIdName_19_1_0 = "dataWrite";
}

5
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java

@ -23,6 +23,7 @@ import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.response.WriteResponse;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.security.auth.Destroyable; import javax.security.auth.Destroyable;
import java.util.Arrays; import java.util.Arrays;
@ -37,7 +38,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 5, 6, 7, 9); private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 5, 6, 7, 9);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope"));
private final AtomicInteger state = new AtomicInteger(0); private final AtomicInteger state = new AtomicInteger(0);
@ -82,7 +83,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
@Override @Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) {
log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); log.warn("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
switch (resourceId) { switch (resourceId) {
case 0: case 0:

41
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MLocationParams.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
public class LwM2MLocationParams {
private Float latitude;
private Float longitude;
private Float scaleFactor = 1.0F;
private final String locationPos = "50.4501:30.5234";
private final Float locationScaleFactor = 1.0F;
protected void getPos() {
this.latitude = null;
this.longitude = null;
int c = locationPos.indexOf(':');
this.latitude = Float.valueOf(locationPos.substring(0, c));
this.longitude = Float.valueOf(locationPos.substring(c + 1));
}
}

75
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -31,7 +31,6 @@ import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.LwM2mId;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.californium.EndpointFactory;
import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.InvalidDDFFileException;
@ -39,12 +38,15 @@ import org.eclipse.leshan.core.model.LwM2mModel;
import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest; 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 java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -52,11 +54,20 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.DEVICE;
import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
import static org.eclipse.leshan.core.LwM2mId.LOCATION;
import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; 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;
@Slf4j @Slf4j
@Data @Data
@ -66,20 +77,35 @@ public class LwM2MTestClient {
private final String endpoint; private final String endpoint;
private LeshanClient client; private LeshanClient client;
public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException { private Server lwm2mServer;
String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; private SimpleLwM2MDevice lwM2MDevice;
private FwLwM2MDevice fwLwM2MDevice;
private SwLwM2MDevice swLwM2MDevice;
private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer;
private LwM2MLocationParams locationParams;
private LwM2mTemperatureSensor lwM2MTemperatureSensor;
public void init(Security security, NetworkConfig coapConfig, int port) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", client);
List<ObjectModel> models = new ArrayList<>(); List<ObjectModel> models = new ArrayList<>();
for (String resourceName : resources) { for (String resourceName : resources) {
models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
} }
LwM2mModel model = new StaticModel(models); LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = new ObjectsInitializer(model); ObjectsInitializer initializer = new ObjectsInitializer(model);
initializer.setInstancesForObject(SECURITY, security); initializer.setInstancesForObject(SECURITY, security);
initializer.setInstancesForObject(SERVER, new Server(123, 300)); initializer.setInstancesForObject(SERVER, lwm2mServer = new Server(123, 300));
initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice());
initializer.setInstancesForObject(FIRMWARE, new FwLwM2MDevice()); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, new SwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice());
initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
initializer.setInstancesForObject(BINARY_APP_DATA_CONTAINER, lwM2MBinaryAppDataContainer = new LwM2mBinaryAppDataContainer(executor, objectInstanceId_0),
new LwM2mBinaryAppDataContainer(executor, objectInstanceId_1));
locationParams = new LwM2MLocationParams();
locationParams.getPos();
initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, objectInstanceId_0));
initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2MTemperatureSensor = new LwM2mTemperatureSensor(executor, objectInstanceId_0), new LwM2mTemperatureSensor(executor, objectInstanceId_12));
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedCipherSuitesOnly(true); dtlsConfig.setRecommendedCipherSuitesOnly(true);
@ -123,15 +149,16 @@ public class LwM2MTestClient {
LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
builder.setLocalAddress("0.0.0.0", 11000); builder.setLocalAddress("0.0.0.0", port);
builder.setObjects(initializer.createAll()); builder.setObjects(initializer.createAll());
builder.setCoapConfig(coapConfig); builder.setCoapConfig(coapConfig);
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
builder.setRegistrationEngineFactory(engineFactory); builder.setRegistrationEngineFactory(engineFactory);
builder.setEndpointFactory(endpointFactory); builder.setEndpointFactory(endpointFactory);
builder.setSharedExecutor(executor); builder.setSharedExecutor(executor);
builder.setDecoder(new DefaultLwM2mNodeDecoder(true)); builder.setDecoder(new DefaultLwM2mDecoder(false));
builder.setEncoder(new DefaultLwM2mNodeEncoder(true));
builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false));
client = builder.build(); client = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() { LwM2mClientObserver observer = new LwM2mClientObserver() {
@ -228,7 +255,27 @@ public class LwM2MTestClient {
} }
public void destroy() { public void destroy() {
client.destroy(true); if (client != null) {
client.destroy(true);
}
if (lwm2mServer != null) {
lwm2mServer = null;
}
if (lwM2MDevice != null) {
lwM2MDevice.destroy();
}
if (fwLwM2MDevice != null) {
fwLwM2MDevice.destroy();
}
if (swLwM2MDevice != null) {
swLwM2MDevice.destroy();
}
if (lwM2MBinaryAppDataContainer != null) {
lwM2MBinaryAppDataContainer.destroy();
}
if (lwM2MTemperatureSensor != null) {
lwM2MTemperatureSensor.destroy();
}
} }
} }

231
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java

@ -0,0 +1,231 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import javax.security.auth.Destroyable;
import java.sql.Time;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable {
/**
* id = 0
* Multiple
* base64
*/
/**
* Example1:
* InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka
* W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo=
* "serviceId":"Meter",
* "serviceData":{
* "currentReading":"46.3",
* "signalStrength":16,
* "dailyActivityTime":5706
*/
/**
* Example2:
* InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX
* 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K
* "serviceId":"WaterMeter",
* "cmd":"SET_TEMPERATURE_READ_PERIOD",
* "paras":{
* "value":4
* },
*/
// private String data = "InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFkaW5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo=";
// private byte[] data;
Map<Integer, byte[]> data;
private Integer priority = 0;
private Time timestamp;
private String description;
private String dataFormat;
private Integer appID = -1;
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5);
public LwM2mBinaryAppDataContainer() {
}
public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
executorService.scheduleWithFixedDelay(() ->
// fireResourcesChange(0, 2), 5000, 5000, TimeUnit.MILLISECONDS);
fireResourcesChange(0, 2), 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceId) {
// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
try {
switch (resourceId) {
case 0:
// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE);
// log.warn("Response [{}]", response);
return response;
case 1:
return ReadResponse.success(resourceId, getPriority());
case 2:
return ReadResponse.success(resourceId, getTimestamp());
case 3:
return ReadResponse.success(resourceId, getDescription());
case 4:
return ReadResponse.success(resourceId, getDataFormat());
case 5:
return ReadResponse.success(resourceId, getAppID());
default:
return super.read(identity, resourceId);
}
} catch (Exception e) {
return ReadResponse.badRequest(e.getMessage());
}
}
@Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) {
log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 0:
if (setData(value, replace)) {
return WriteResponse.success();
} else {
WriteResponse.badRequest("Invalidate value ...");
}
case 1:
setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue()));
fireResourcesChange(resourceId);
return WriteResponse.success();
case 2:
setTimestamp(((Date) value.getValue()).getTime());
fireResourcesChange(resourceId);
return WriteResponse.success();
case 3:
setDescription((String) value.getValue());
fireResourcesChange(resourceId);
return WriteResponse.success();
case 4:
setDataFormat((String) value.getValue());
fireResourcesChange(resourceId);
return WriteResponse.success();
case 5:
setAppID((Integer) value.getValue());
fireResourcesChange(resourceId);
return WriteResponse.success();
default:
return super.write(identity, replace, resourceId, value);
}
}
private Integer getAppID() {
return this.appID;
}
private void setAppID(Integer appId) {
this.appID = appId;
}
private void setDataFormat(String value) {
this.dataFormat = value;
}
private String getDataFormat() {
// return this.dataFormat == null ? "base64" : this.dataFormat;
return this.dataFormat == null ? "OPAQUE" : this.dataFormat;
}
private void setDescription(String value) {
this.description = value;
}
private String getDescription() {
return this.description == null ? "meter reading" : this.description;
}
private void setTimestamp(long time) {
this.timestamp = new Time(time);
}
private Time getTimestamp() {
return this.timestamp != null ? this.timestamp : new Time(new Date().getTime());
}
// fireResourcesChange(resourceId);
private boolean setData(LwM2mResource value, boolean replace) {
try {
if (value instanceof LwM2mMultipleResource) {
if (replace || this.data == null) {
this.data = new HashMap<>();
}
value.getInstances().values().forEach(v -> {
this.data.put(v.getId(), (byte[]) v.getValue());
});
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
private Map<Integer, byte[]> getData() {
// this.data.put(23, new byte[]{0,0, 2,3});
return data;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
@Override
public void destroy() {
}
private int getPriority() {
return this.priority;
}
private void setPriority(int value) {
this.priority = value;
}
}

150
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java

@ -0,0 +1,150 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.response.ReadResponse;
import javax.security.auth.Destroyable;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable {
private float latitude;
private float longitude;
private float scaleFactor;
private Date timestamp;
protected static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 5);
public LwM2mLocation() {
this(null, null, 1.0f);
}
public LwM2mLocation(Float latitude, Float longitude, float scaleFactor) {
if (latitude != null) {
this.latitude = latitude + 90f;
} else {
this.latitude = RANDOM.nextInt(180);
}
if (longitude != null) {
this.longitude = longitude + 180f;
} else {
this.longitude = RANDOM.nextInt(360);
}
this.scaleFactor = scaleFactor;
timestamp = new Date();
}
public LwM2mLocation(Float latitude, Float longitude, float scaleFactor, ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
if (latitude != null) {
this.latitude = latitude + 90f;
} else {
this.latitude = RANDOM.nextInt(180);
}
if (longitude != null) {
this.longitude = longitude + 180f;
} else {
this.longitude = RANDOM.nextInt(360);
}
this.scaleFactor = scaleFactor;
timestamp = new Date();
executorService.scheduleWithFixedDelay(() ->
fireResourcesChange(0, 1), 10000, 10000, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceId) {
log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 0:
return ReadResponse.success(resourceId, getLatitude());
case 1:
return ReadResponse.success(resourceId, getLongitude());
case 5:
return ReadResponse.success(resourceId, getTimestamp());
default:
return super.read(identity, resourceId);
}
}
public void moveLocation(String nextMove) {
switch (nextMove.charAt(0)) {
case 'w':
moveLatitude(1.0f);
// log.info("Move to North [{}]/[{}]", getLatitude(), getLongitude());
break;
case 'a':
moveLongitude(-1.0f);
// log.info("Move to East [{}]/[{}]", getLatitude(), getLongitude());
break;
case 's':
moveLatitude(-1.0f);
// log.info("Move to South [{}]/[{}]", getLatitude(), getLongitude());
break;
case 'd':
moveLongitude(1.0f);
// log.info("Move to West [{}]/[{}]", getLatitude(), getLongitude());
break;
}
}
private void moveLatitude(float delta) {
latitude = latitude + delta * scaleFactor;
timestamp = new Date();
fireResourcesChange(0, 5);
}
private void moveLongitude(float delta) {
longitude = longitude + delta * scaleFactor;
timestamp = new Date();
fireResourcesChange(1, 5);
}
public float getLatitude() {
return latitude - 90.0f;
}
public float getLongitude() {
return longitude - 180.f;
}
public Date getTimestamp() {
return timestamp;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
}

131
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java

@ -0,0 +1,131 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import javax.security.auth.Destroyable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable {
private static final String UNIT_CELSIUS = "cel";
private double currentTemp = 20d;
private double minMeasuredValue = currentTemp;
private double maxMeasuredValue = currentTemp;
protected static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(5601, 5602, 5700, 5701);
public LwM2mTemperatureSensor() {
}
public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public synchronized ReadResponse read(ServerIdentity identity, int resourceId) {
log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 5601:
return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue));
case 5602:
return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue));
case 5700:
return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp));
case 5701:
return ReadResponse.success(resourceId, UNIT_CELSIUS);
default:
return super.read(identity, resourceId);
}
}
@Override
public synchronized ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) {
log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
switch (resourceId) {
case 5605:
resetMinMaxMeasuredValues();
return ExecuteResponse.success();
default:
return super.execute(identity, resourceId, params);
}
}
private double getTwoDigitValue(double value) {
BigDecimal toBeTruncated = BigDecimal.valueOf(value);
return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue();
}
private void adjustTemperature() {
float delta = (RANDOM.nextInt(20) - 10) / 10f;
currentTemp += delta;
Integer changedResource = adjustMinMaxMeasuredValue(currentTemp);
if (changedResource != null) {
fireResourcesChange(5700, changedResource);
} else {
fireResourcesChange(5700);
}
}
private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) {
if (newTemperature > maxMeasuredValue) {
maxMeasuredValue = newTemperature;
return 5602;
} else if (newTemperature < minMeasuredValue) {
minMeasuredValue = newTemperature;
return 5601;
} else {
return null;
}
}
private void resetMinMaxMeasuredValues() {
minMeasuredValue = currentTemp;
maxMeasuredValue = currentTemp;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
@Override
public void destroy() {
}
}

10
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -40,9 +40,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3 private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21
);
@Override @Override
public ReadResponse read(ServerIdentity identity, int resourceId) { public ReadResponse read(ServerIdentity identity, int resourceId) {
@ -117,7 +115,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
} }
private String getManufacturer() { private String getManufacturer() {
return "Leshan Demo Device"; return "Thingsboard Demo Lwm2mDevice";
} }
private String getModelNumber() { private String getModelNumber() {
@ -125,11 +123,11 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
} }
private String getSerialNumber() { private String getSerialNumber() {
return "LT-500-000-0001"; return "Thingsboard-500-000-0001";
} }
private String getFirmwareVersion() { private String getFirmwareVersion() {
return "1.0.0"; return "1.0.2";
} }
private long getErrorCode() { private long getErrorCode() {

6
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.transport.lwm2m.client; package org.thingsboard.server.transport.lwm2m.client;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.client.servers.ServerIdentity;
@ -24,6 +23,7 @@ import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.response.WriteResponse;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.security.auth.Destroyable; import javax.security.auth.Destroyable;
import java.util.Arrays; import java.util.Arrays;
@ -38,7 +38,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 4, 6, 7, 9); private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 4, 6, 7, 9);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope"));
private final AtomicInteger state = new AtomicInteger(0); private final AtomicInteger state = new AtomicInteger(0);
@ -83,7 +83,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
@Override @Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) {
log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); log.warn("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId);
switch (resourceId) { switch (resourceId) {
case 2: case 2:

307
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/RpcAbstractLwM2MIntegrationTest.java

@ -0,0 +1,307 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.rpc;
import 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 java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.eclipse.leshan.core.LwM2mId.DEVICE;
import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
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.objectInstanceId_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
@DaoSqlTest
public class RpcAbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
protected final String RPC_TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"" + "/" + DEVICE + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\": \"" + resourceIdName_3_9 + "\",\n" +
" \"" + "/" + DEVICE + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\": \"" + resourceIdName_3_14 + "\",\n" +
" \"" + "/" + BINARY_APP_DATA_CONTAINER + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_0_0 + "\",\n" +
" \"" + "/" + BINARY_APP_DATA_CONTAINER + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_1_0 + "\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"" + "/" + DEVICE + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" +
" \"" + "/" + BINARY_APP_DATA_CONTAINER + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\"\n" +
" ],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"" + "/" + DEVICE + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" +
" \"" + "/" + DEVICE + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\",\n" +
" \"" + "/" + BINARY_APP_DATA_CONTAINER + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\",\n" +
" \"" + "/" + BINARY_APP_DATA_CONTAINER + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrap\": {\n" +
" \"servers\": {\n" +
" \"binding\": \"U\",\n" +
" \"shortId\": 123,\n" +
" \"lifetime\": 300,\n" +
" \"notifIfDisabled\": true,\n" +
" \"defaultMinPeriod\": 1\n" +
" },\n" +
" \"lwm2mServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5686,\n" +
" \"serverId\": 123,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"bootstrapServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5687,\n" +
" \"serverId\": 111,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" },\n" +
" \"clientLwM2mSettings\": {\n" +
" \"clientOnlyObserveAfterConnect\": 1,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"swUpdateStrategy\": 1\n" +
" }\n" +
"}";
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;
public Set expectedInstances;
public Set expectedObjectIdVerInstances;
protected String objectInstanceIdVer_1;
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 objectIdVer_19;
public RpcAbstractLwM2MIntegrationTest(){ }
@Before
public void beforeTest() throws Exception {
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-rpc-scheduled"));
loginTenantAdmin();
wsClient = buildAndConnectWebSocketClient();
createDeviceProfile(RPC_TRANSPORT_CONFIGURATION);
clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(ENDPOINT_RPC);
Device device = createDevice(clientCredentials);
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();
expectedObjects = ConcurrentHashMap.newKeySet();
expectedObjectIdVers = ConcurrentHashMap.newKeySet();
expectedInstances = ConcurrentHashMap.newKeySet();
expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet();
client.getClient().getObjectTree().getObjectEnablers().forEach((key, val) -> {
if (key > 0) {
String objectVerId = "/" + key;
if (!val.getObjectModel().version.equals("1.0")) {
objectVerId += ("_" + val.getObjectModel().version);
}
expectedObjects.add("/" + key);
expectedObjectIdVers.add(objectVerId);
String finalObjectVerId = objectVerId;
val.getAvailableInstanceIds().forEach(inststanceId -> {
expectedInstances.add("/" + key + "/" + inststanceId);
expectedObjectIdVerInstances.add(finalObjectVerId + "/" + inststanceId);
});
}
});
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get();
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();
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;
}
@After
public void after() {
if (client != null) {
client.destroy();
}
executor.shutdownNow();
if (wsClient != null) {
wsClient.close();
}
}
}

77
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java

@ -0,0 +1,77 @@
/**
* 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.RpcAbstractLwM2MIntegrationTest;
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_0;
public class RpcLwm2mIntegrationCreateTest extends RpcAbstractLwM2MIntegrationTest {
/**
* Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}}
*
* create_2_instances_in_object
* new ObjectInstance if Object is Multiple & Resource Single
* Create {"id":"/19_1.1/0","value":{"0":{"0":"00AC", "1":1}}}
* {"result":"BAD_REQUEST","error":"instance 0 already exists"}
*
*/
@Test
public void testCreateObjectInstanceWithInstanceIdAlreadyExistsByIdKey_Result_BAD_REQUEST() throws Exception {
String expectedPath = objectIdVer_19 + "/" + objectInstanceId_0;
String expectedValue = "{\"" + resourceId_0 + "\":{\"0\":\"00AC\"}, \"1\":1}";
String actualResult = sendRPCreateById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "instance " + objectInstanceId_0 + " already exists";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}
/**
* failed: cannot_create_mandatory_single_object
* Create {"id":"/3/2","value":{"0":"00AC"}}
*/
/**
* failed: cannot_create_instance_of_security_object
* Create {"id":"/0/2","value":{"0":"00AC"}}
*/
/**
* failed: cannot_create_instance_of_absent_object
* Create {"id":"/50/2","value":{"0":"00AC"}}
*/
private String sendRPCreateById(String path, String value) throws Exception {
String setRpcRequest = "{\"method\": \"Create\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

17
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java

@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m; package org.thingsboard.server.transport.lwm2m.security;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
@ -26,6 +27,7 @@ import org.junit.Before;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.SocketUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -86,6 +88,8 @@ import static org.eclipse.leshan.client.object.Security.noSec;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; 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 @DaoSqlTest
public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
@ -169,14 +173,9 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
// certificates trustedby the server (should contain rootCA) // certificates trustedby the server (should contain rootCA)
protected final Certificate[] trustedCertificates = new Certificate[1]; protected final Certificate[] trustedCertificates = new Certificate[1];
protected static final int SECURE_PORT = 5686;
protected static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT));
protected static final String ENDPOINT = "deviceAEndpoint"; protected static final String ENDPOINT = "deviceAEndpoint";
protected static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT;
protected static final int PORT = 5685; protected CoapClient client;
protected static final Security SECURITY = noSec("coap://localhost:" + PORT, 123);
protected static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
public AbstractLwM2MIntegrationTest() { public AbstractLwM2MIntegrationTest() {
// create client credentials // create client credentials
@ -394,8 +393,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
client = new LwM2MTestClient(executor, endpoint); client = new LwM2MTestClient(executor, endpoint);
int clientPort = SocketUtils.findAvailableTcpPort();
client.init(security, coapConfig); client.init(security, coapConfig, clientPort);
String msg = wsClient.waitForUpdate(); String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);

36
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.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 static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
@Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
@Test
public void testConnectAndObserveTelemetry() throws Exception {
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(ENDPOINT);
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT);
}
}

120
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/NoSecLwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/OtaLwM2MIntegrationTest.java

@ -13,20 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.util.SocketUtils;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.security.AbstractLwM2MIntegrationTest;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -48,9 +48,11 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEU
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; 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.OtaPackageUpdateStatus.VERIFIED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
@Slf4j @Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { public class OtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
public static final int TIMEOUT = 30; public static final int TIMEOUT = 30;
private final String OTA_TRANSPORT_CONFIGURATION = "{\n" + private final String OTA_TRANSPORT_CONFIGURATION = "{\n" +
@ -130,33 +132,19 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
" \"type\": \"LWM2M\"\n" + " \"type\": \"LWM2M\"\n" +
"}"; "}";
LwM2MTestClient client = null;
@After
public void tearDown() {
if (client != null) {
client.destroy();
}
}
@Test
public void testConnectAndObserveTelemetry() throws Exception {
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(ENDPOINT);
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT);
}
@Test @Test
public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception {
LwM2MTestClient client = null; LwM2MTestClient client = null;
String endpoint = "WithoutFirmwareInfoDevice";
try { try {
createDeviceProfile(TRANSPORT_CONFIGURATION); createDeviceProfile(TRANSPORT_CONFIGURATION);
NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(ENDPOINT); clientCredentials.setEndpoint(endpoint);
Device device = createDevice(clientCredentials); Device device = createDevice(clientCredentials);
client = new LwM2MTestClient(executor, ENDPOINT); client = new LwM2MTestClient(executor, endpoint);
client.init(SECURITY, COAP_CONFIG); int clientPort = SocketUtils.findAvailableTcpPort();
client.init(SECURITY, COAP_CONFIG, clientPort);
Thread.sleep(1000); Thread.sleep(1000);
@ -183,27 +171,31 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
@Test @Test
public void testFirmwareUpdateByObject5() throws Exception { public void testFirmwareUpdateByObject5() throws Exception {
LwM2MTestClient client = null; LwM2MTestClient client = null;
String endpoint = "Ota5_Device";
List<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
try { try {
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint("OTA_" + ENDPOINT); clientCredentials.setEndpoint(endpoint);
Device device = createDevice(clientCredentials); Device device = createDevice(clientCredentials);
device.setFirmwareId(createFirmware().getId()); device.setFirmwareId(createFirmware().getId());
device = doPost("/api/device", device, Device.class); Device saveDevice = doPost("/api/device", device, Device.class);
Thread.sleep(1000); Thread.sleep(1000);
client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); client = new LwM2MTestClient(executor, endpoint);
client.init(SECURITY, COAP_CONFIG); int clientPort = SocketUtils.findAvailableTcpPort();
client.init(SECURITY, COAP_CONFIG, clientPort);
Thread.sleep(3000); 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<TsKvEntry> ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() {
}));
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
List<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
Assert.assertEquals(expectedStatuses, statuses); Assert.assertEquals(expectedStatuses, statuses);
} finally { } finally {
@ -223,41 +215,49 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
//given //given
final List<OtaPackageUpdateStatus> expectedStatuses = List.of( final List<OtaPackageUpdateStatus> expectedStatuses = List.of(
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); 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());
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);
//when
log.warn("Init the client...");
client = new LwM2MTestClient(executor, endpoint);
int clientPort = SocketUtils.findAvailableTcpPort();
client.init(SECURITY, COAP_CONFIG, clientPort);
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: {} ", x));
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList());
NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); log.warn("Converted ts to statuses: {}", statuses);
clientCredentials.setEndpoint("OTA_" + ENDPOINT);
final Device device = createDevice(clientCredentials);
device.setSoftwareId(createSoftware().getId());
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);
//when
log.warn("Init the client...");
client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT);
client.init(SECURITY, COAP_CONFIG);
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: {} ", 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); assertThat(statuses).isEqualTo(expectedStatuses);
} finally {
if (client != null) {
client.destroy();
}
}
} }
private Device getDeviceFromAPI(UUID deviceId) throws Exception { private Device getDeviceFromAPI(UUID deviceId) throws Exception {
final Device device = doGet("/api/device/" + deviceId, Device.class); final Device device = doGet("/api/device/" + deviceId, Device.class);
log.warn("Fetched device by API for deviceId {}, device is {}", deviceId, device); log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device);
return device; return device;
} }

9
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/PskLwm2mIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java

@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractLwM2MIntegrationTest;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static org.eclipse.leshan.client.object.Security.psk; import static org.eclipse.leshan.client.object.Security.psk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest {
@ -36,7 +39,7 @@ public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest {
clientCredentials.setKey(pskKey); clientCredentials.setKey(pskKey);
clientCredentials.setIdentity(pskIdentity); clientCredentials.setIdentity(pskIdentity);
Security security = psk(SECURE_URI, Security security = psk(SECURE_URI,
123, SHORT_SERVER_ID,
pskIdentity.getBytes(StandardCharsets.UTF_8), pskIdentity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(pskKey.toCharArray())); Hex.decodeHex(pskKey.toCharArray()));
super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, ENDPOINT); super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, ENDPOINT);

9
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/RpkLwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java

@ -13,15 +13,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractLwM2MIntegrationTest;
import static org.eclipse.leshan.client.object.Security.rpk; 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 AbstractLwM2MIntegrationTest {
@ -31,7 +34,7 @@ public class RpkLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
rpkClientCredentials.setEndpoint(ENDPOINT); rpkClientCredentials.setEndpoint(ENDPOINT);
rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded())); rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded()));
Security security = rpk(SECURE_URI, Security security = rpk(SECURE_URI,
123, SHORT_SERVER_ID,
clientPublicKey.getEncoded(), clientPublicKey.getEncoded(),
clientPrivateKey.getEncoded(), clientPrivateKey.getEncoded(),
serverX509Cert.getPublicKey().getEncoded()); serverX509Cert.getPublicKey().getEncoded());

37
application/src/test/java/org/thingsboard/server/transport/lwm2m/sql/X509LwM2MIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509LwM2MIntegrationTest.java

@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; 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.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import static org.eclipse.leshan.client.object.Security.x509; 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 X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
@ -31,25 +32,25 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
X509ClientCredentials credentials = new X509ClientCredentials(); X509ClientCredentials credentials = new X509ClientCredentials();
credentials.setEndpoint(ENDPOINT); credentials.setEndpoint(ENDPOINT);
Security security = x509(SECURE_URI, Security security = x509(SECURE_URI,
123, SHORT_SERVER_ID,
clientX509Cert.getEncoded(), clientX509Cert.getEncoded(),
clientPrivateKeyFromCert.getEncoded(), clientPrivateKeyFromCert.getEncoded(),
serverX509Cert.getEncoded()); serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT);
} }
@Ignore //See LwM2mClientContextImpl.unregister // @Ignore //See LwM2mClientContextImpl.unregister
@Test // @Test
public void testConnectWithCertAndObserveTelemetry() throws Exception { // public void testConnectWithCertAndObserveTelemetry() throws Exception {
X509ClientCredentials credentials = new X509ClientCredentials(); // X509ClientCredentials credentials = new X509ClientCredentials();
credentials.setEndpoint(ENDPOINT); // credentials.setEndpoint(ENDPOINT);
credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); // credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted));
Security security = x509(SECURE_URI, // Security security = x509(SECURE_URI,
123, // 123,
clientX509CertNotTrusted.getEncoded(), // clientX509CertNotTrusted.getEncoded(),
clientPrivateKeyFromCert.getEncoded(), // clientPrivateKeyFromCert.getEncoded(),
serverX509Cert.getEncoded()); // serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); // super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT);
} // }
} }

144
application/src/test/resources/lwm2m/19.xml

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-LwM2M_BinaryAppDataContainer-V1_0_1-20190221-A
Type: xml
Public Reachable Information
Path: http://www.openmobilealliance.org/tech/profiles
Name: LwM2M_BinaryAppDataContainer-v1_0_1.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LWM2M_BinaryAppDataContainer-V1_0_1
This is available at http://www.openmobilealliance.org/
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
CHANGE HISTORY
15062018 Status changed to Approved by DM, Doc Ref # OMA-DM&SE-2018-0061-INP_LWM2M_APPDATA_V1_0_ERP_for_final_Approval
21022019 Status changed to Approved by IPSO, Doc Ref # OMA-IPSO-2019-0025-INP_LwM2M_Object_App_Data_Container_1_0_1_for_Final_Approval
LEGAL DISCLAIMER
Copyright 2019 Open Mobile Alliance.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The above license is used as a license under copyright only. Please
reference the OMA IPR Policy for patent licensing terms:
https://www.omaspecworks.org/about/intellectual-property-rights/
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>BinaryAppDataContainer</Name>
<Description1><![CDATA[This LwM2M Objects provides the application service data related to a LwM2M Server, eg. Water meter data.
There are several methods to create instance to indicate the message direction based on the negotiation between Application and LwM2M. The Client and Server should negotiate the instance(s) used to exchange the data. For example:
- Using a single instance for both directions communication, from Client to Server and from Server to Client.
- Using an instance for communication from Client to Server and another one for communication from Server to Client
- Using several instances
]]></Description1>
<ObjectID>19</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:19:1.1</ObjectURN>
<LWM2MVersion>1.1</LWM2MVersion>
<ObjectVersion>1.1</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0"><Name>Data</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Indicates the application data content.]]></Description>
</Item>
<Item ID="1"><Name>Data Priority</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>1 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the Application data priority:
0:Immediate
1:BestEffort
2:Latest
3-100: Reserved for future use.
101-254: Proprietary mode.]]></Description>
</Item>
<Item ID="2"><Name>Data Creation Time</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Indicates the Data instance creation timestamp.]]></Description>
</Item>
<Item ID="3"><Name>Data Description</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>32 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the data description.
e.g. "meter reading".]]></Description>
</Item>
<Item ID="4"><Name>Data Format</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>32 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the format of the Application Data.
e.g. YG-Meter-Water-Reading
UTF8-string
]]></Description>
</Item>
<Item ID="5"><Name>App ID</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>2 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the destination Application ID.]]></Description>
</Item></Resources>
<Description2><![CDATA[]]></Description2>
</Object>
</LWM2M>

103
application/src/test/resources/lwm2m/3303.xml

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright © 2016-2018 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.
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Temperature</Name>
<Description1>This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius.</Description1>
<ObjectID>3303</ObjectID>
<ObjectURN>urn:oma:lwm2m:ext:3303</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="5700">
<Name>Sensor Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Last or Current Measured Value from the Sensor</Description>
</Item>
<Item ID="5601">
<Name>Min Measured Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The minimum value measured by the sensor since power ON or reset</Description>
</Item>
<Item ID="5602">
<Name>Max Measured Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The maximum value measured by the sensor since power ON or reset</Description>
</Item>
<Item ID="5603">
<Name>Min Range Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The minimum value that can be measured by the sensor</Description>
</Item>
<Item ID="5604">
<Name>Max Range Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The maximum value that can be measured by the sensor</Description>
</Item>
<Item ID="5701">
<Name>Sensor Units</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Measurement Units Definition.</Description>
</Item>
<Item ID="5605">
<Name>Reset Min and Max Measured Values</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Reset the Min and Max Measured Values to Current Value</Description>
</Item>
</Resources>
<Description2></Description2>
</Object>
</LWM2M>

143
application/src/test/resources/lwm2m/6.xml

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_6-V1_0-20201110-A.xml
http://www.openmobilealliance.org/release/ObjLwM2M_Location/
OMNA LwM2M Registry
Path: https://github.com/OpenMobileAlliance/lwm2m-registry
Name: 6.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M-V1_0_2
OMA-TS-LightweightM2M_Core-V1_1_1
This is available at http://www.openmobilealliance.org/release/LightweightM2M/
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
LEGAL DISCLAIMER
Copyright 2020 Open Mobile Alliance.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The above license is used as a license under copyright only. Please
reference the OMA IPR Policy for patent licensing terms:
https://www.omaspecworks.org/about/intellectual-property-rights/
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Location</Name>
<Description1><![CDATA[This LwM2M Object provides a range of location telemetry related information which can be queried by the LwM2M Server.]]></Description1>
<ObjectID>6</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:6</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Latitude</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>lat</Units>
<Description><![CDATA[The decimal notation of latitude, e.g. -43.5723 [World Geodetic System 1984].]]></Description>
</Item>
<Item ID="1">
<Name>Longitude</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>lon</Units>
<Description><![CDATA[The decimal notation of longitude, e.g. 153.21760 [World Geodetic System 1984].]]></Description>
</Item>
<Item ID="2">
<Name>Altitude</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>m</Units>
<Description><![CDATA[The decimal notation of altitude in meters above sea level.]]></Description>
</Item>
<Item ID="3">
<Name>Radius</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>m</Units>
<Description><![CDATA[The value in this resource indicates the radius of a circular area in meters. The circular area is used to describe uncertainty about a point for coordinates in a two-dimensional coordinate reference systems (CRS). The center point of a circular area is specified by using the Latitude and the Longitude Resources.]]></Description>
</Item>
<Item ID="4">
<Name>Velocity</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The velocity of the LwM2M Client, as defined in [3GPP-TS_23.032].]]></Description>
</Item>
<Item ID="5">
<Name>Timestamp</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Time</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The timestamp of when the location measurement was performed.]]></Description>
</Item>
<Item ID="6"><Name>Speed</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units>m/s</Units>
<Description><![CDATA[Speed is the time rate of change in position of a LwM2M Client without regard for direction: the scalar component of velocity.]]></Description>
</Item></Resources>
<Description2></Description2>
</Object>
</LWM2M>

16
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -19,17 +19,14 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -42,7 +39,6 @@ import java.security.KeyStoreException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
@ -57,7 +53,7 @@ public class LwM2MTransportBootstrapService {
private final LwM2MTransportBootstrapConfig bootstrapConfig; private final LwM2MTransportBootstrapConfig bootstrapConfig;
private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
private final TransportService transportService;
private LeshanBootstrapServer server; private LeshanBootstrapServer server;
@PostConstruct @PostConstruct
@ -83,15 +79,11 @@ public class LwM2MTransportBootstrapService {
/* Create CoAP Config */ /* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
/* Define model provider (Create Models )*/
List<ObjectModel> models = ObjectLoader.loadDefault();
builder.setModel(new StaticModel(models));
/* Create credentials */ /* Create credentials */
this.setServerWithCredentials(builder); this.setServerWithCredentials(builder);
/* Set securityStore with new ConfigStore */ /* Set securityStore with new ConfigStore */
builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore)); builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
/* SecurityStore */ /* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore); builder.setSecurityStore(lwM2MBootstrapSecurityStore);
@ -106,7 +98,7 @@ public class LwM2MTransportBootstrapService {
/* Set DTLS Config */ /* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService);
builder.setSessionManager(sessionManager); builder.setSessionManager(sessionManager);
/* Create BootstrapServer */ /* Create BootstrapServer */

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.bootstrap.secure; package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.BindingMode;
@ -59,6 +60,7 @@ public class LwM2MBootstrapConfig implements Serializable {
LwM2MServerBootstrap lwm2mServer; LwM2MServerBootstrap lwm2mServer;
@JsonIgnore
public BootstrapConfig getLwM2MBootstrapConfig() { public BootstrapConfig getLwM2MBootstrapConfig() {
BootstrapConfig configBs = new BootstrapConfig(); BootstrapConfig configBs = new BootstrapConfig();
/* Delete old security objects */ /* Delete old security objects */

19
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java

@ -39,7 +39,9 @@ import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.UUID; 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_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@ -58,6 +60,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private final LwM2mTransportContext context; private final LwM2mTransportContext context;
private final LwM2mTransportServerHelper helper; private final LwM2mTransportServerHelper helper;
private final Map<String /* endpoint */, TransportProtos.SessionInfoProto> bsSessions = new ConcurrentHashMap<>();
public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) { public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) {
this.bootstrapConfigStore = bootstrapConfigStore; this.bootstrapConfigStore = bootstrapConfigStore;
@ -82,7 +85,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
} }
bootstrapConfigStore.add(endPoint, bsConfigNew); bootstrapConfigStore.add(endPoint, bsConfigNew);
} catch (InvalidConfigurationException e) { } catch (InvalidConfigurationException e) {
log.error("", e); if (e.getMessage().contains("Psk identity") && e.getMessage().contains("already used for this bootstrap server")) {
log.trace("", e);
}
else {
log.error("", e);
}
} }
return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator(); return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
} }
@ -155,6 +163,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class);
UUID sessionUUiD = UUID.randomUUID(); UUID sessionUUiD = UUID.randomUUID();
TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits());
bsSessions.put(store.getEndpoint(), sessionInfo);
context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService())); context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, null, null, sessionInfo, context.getTransportService()));
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
@ -189,4 +198,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) &&
lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode())); lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode()));
} }
public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) {
return bsSessions.get(endpoint);
}
public TransportProtos.SessionInfoProto removeSessionByEndpoint(String endpoint) {
return bsSessions.remove(endpoint);
}
} }

11
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java

@ -16,7 +16,9 @@
package org.thingsboard.server.transport.lwm2m.bootstrap.secure; package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
@ -35,6 +37,11 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt
private final Lock readLock = readWriteLock.readLock(); private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock(); private final Lock writeLock = readWriteLock.writeLock();
@Override
public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
return bootstrapByEndpoint.get(endpoint);
}
@Override @Override
public Map<String, BootstrapConfig> getAll() { public Map<String, BootstrapConfig> getAll() {
readLock.lock(); readLock.lock();
@ -56,10 +63,10 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt
} }
@Override @Override
public BootstrapConfig remove(String enpoint) { public BootstrapConfig remove(String endpoint) {
writeLock.lock(); writeLock.lock();
try { try {
return super.remove(enpoint); return super.remove(endpoint);
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java

@ -42,8 +42,6 @@ public class LwM2MServerBootstrap {
public LwM2MServerBootstrap() { public LwM2MServerBootstrap() {
} }
;
public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) { public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) {
this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId();
this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); this.clientSecretKey = bootstrapFromCredential.getClientSecretKey();

178
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java

@ -16,23 +16,39 @@
package org.thingsboard.server.transport.lwm2m.bootstrap.secure; package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
import org.eclipse.leshan.core.request.BootstrapFinishRequest;
import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStoreTaskProvider;
import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause;
import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider;
import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.BootstrapSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityChecker;
import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.transport.TransportService;
import java.util.Collections; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@Slf4j @Slf4j
public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
private BootstrapSecurityStore bsSecurityStore; private BootstrapSecurityStore bsSecurityStore;
private SecurityChecker securityChecker; private SecurityChecker securityChecker;
private BootstrapTaskProvider tasksProvider;
private LwM2mBootstrapModelProvider modelProvider;
private TransportService transportService;
/** /**
* Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new * Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new
@ -40,29 +56,169 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
* *
* @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}. * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}.
*/ */
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) { public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) {
this(bsSecurityStore, new SecurityChecker()); this(bsSecurityStore, new SecurityChecker(), new BootstrapConfigStoreTaskProvider(configStore),
new StandardBootstrapModelProvider());
this.transportService = transportService;
} }
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker) { /**
super(bsSecurityStore); * Create a {@link DefaultBootstrapSessionManager}.
*
* @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}.
* @param securityChecker used to accept or refuse new {@link BootstrapSession}.
*/
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker,
BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) {
super(bsSecurityStore, securityChecker, tasksProvider, modelProvider);
this.bsSecurityStore = bsSecurityStore; this.bsSecurityStore = bsSecurityStore;
this.securityChecker = securityChecker; this.securityChecker = securityChecker;
this.tasksProvider = tasksProvider;
this.modelProvider = modelProvider;
} }
@SuppressWarnings("deprecation") @Override
public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
boolean authorized; boolean authorized;
if (bsSecurityStore != null) { Iterator<SecurityInfo> securityInfos;
Iterator<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? if (bsSecurityStore != null && securityChecker != null) {
Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName()); if (clientIdentity.isSecure() && clientIdentity.isPSK()) {
log.info("Bootstrap session started securityInfos: [{}]", securityInfos); securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity());
} else {
securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
}
authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
} else { } else {
authorized = true; authorized = true;
} }
DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
log.info("Bootstrap session started : {}", session); if (authorized) {
this.sendLogs (request.getEndpointName(),
String.format("%s: Bootstrap session started endpoint: %s, session: %s ", LOG_LWM2M_INFO, request.getEndpointName(),
session.toString()));
}
return session; return session;
} }
@Override
public boolean hasConfigFor(BootstrapSession session) {
BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null);
if (firstTasks == null) return false;
initTasks(session, firstTasks);
return true;
}
protected void initTasks(BootstrapSession bssession, BootstrapTaskProvider.Tasks tasks) {
DefaultBootstrapSession session = (DefaultBootstrapSession) bssession;
// set models
if (tasks.supportedObjects != null)
session.setModel(modelProvider.getObjectModel(session, tasks.supportedObjects));
// set Requests to Send
session.setRequests(tasks.requestsToSend);
// prepare list where we will store Responses
session.setResponses(new ArrayList<LwM2mResponse>(tasks.requestsToSend.size()));
// is last Tasks ?
session.setMoreTasks(!tasks.last);
}
@Override
public BootstrapDownlinkRequest<? extends LwM2mResponse> getFirstRequest(BootstrapSession bsSession) {
return nextRequest(bsSession);
}
protected BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest(BootstrapSession bsSession) {
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsToSend = session.getRequests();
if (!requestsToSend.isEmpty()) {
// get next requests
return requestsToSend.remove(0);
} else {
if (session.hasMoreTasks()) {
BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses());
if (nextTasks == null) {
session.setMoreTasks(false);
return new BootstrapFinishRequest();
}
initTasks(session, nextTasks);
return nextRequest(bsSession);
} else {
return new BootstrapFinishRequest();
}
}
}
@Override
public BootstrapPolicy onResponseSuccess(BootstrapSession bsSession,
BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response) {
if (!(request instanceof BootstrapFinishRequest)) {
// store response
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
session.getResponses().add(response);
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: %s %s receives success response %s for %s : %s", LOG_LWM2M_INFO,
request.getClass().getSimpleName(), request.getPath().toString(), response.toString(), bsSession.toString(), request.toString()));
// on success for NOT bootstrap finish request we send next request
return BootstrapPolicy.continueWith(nextRequest(bsSession));
} else {
// on success for bootstrap finish request we stop the session
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: %s receives success response for bootstrap finish request and stop the session: %s", LOG_LWM2M_INFO,
request.getClass().getSimpleName(), bsSession.toString()));
return BootstrapPolicy.finished();
}
}
@Override
public BootstrapPolicy onResponseError(BootstrapSession bsSession,
BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response) {
if (!(request instanceof BootstrapFinishRequest)) {
// store response
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
session.getResponses().add(response);
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: %s %s receives error response %s for %s : %s", LOG_LWM2M_INFO,
request.getClass().getSimpleName(),
request.getPath().toString(), response.toString(), bsSession.toString(), request.toString()));
// on response error for NOT bootstrap finish request we continue any sending next request
return BootstrapPolicy.continueWith(nextRequest(bsSession));
} else {
// on response error for bootstrap finish request we stop the session
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: %s %s error response %s for request %s bootstrap finish. Stop the session: %s", LOG_LWM2M_INFO,
request.getClass().getSimpleName(),
request.getPath().toString(), response.toString(), request.toString(), bsSession.toString()));
return BootstrapPolicy.failed();
}
}
@Override
public BootstrapPolicy onRequestFailure(BootstrapSession bsSession,
BootstrapDownlinkRequest<? extends LwM2mResponse> request, Throwable cause) {
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: %s %s failed because of %s for %s : %s", LOG_LWM2M_INFO,
request.getClass().getSimpleName(), request.getPath().toString(), cause.toString(), bsSession.toString(), request.toString()));
return BootstrapPolicy.failed();
}
@Override
public void end(BootstrapSession bsSession) {
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: Bootstrap session finished : %s", LOG_LWM2M_INFO, bsSession.toString()));
}
@Override
public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
this.sendLogs (bsSession.getEndpoint(),
String.format("%s: Bootstrap session failed by %s: %s", LOG_LWM2M_INFO, cause.toString(), bsSession.toString()));
}
private void sendLogs (String endpointName, String logMsg) {
log.trace(logMsg);
transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg);
}
} }

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -15,24 +15,17 @@
*/ */
package org.thingsboard.server.transport.lwm2m.config; package org.thingsboard.server.transport.lwm2m.config;
import com.google.common.io.Resources;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.ResourceUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore; import java.security.KeyStore;
@Slf4j @Slf4j

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java

@ -16,11 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.leshan.core.californium.LwM2mCoapResource; import org.eclipse.leshan.core.californium.LwM2mCoapResource;
import org.thingsboard.server.common.transport.TransportServiceCallback;
@Slf4j @Slf4j
public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource {

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -20,8 +20,8 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
@ -110,9 +110,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
LeshanServerBuilder builder = new LeshanServerBuilder(); LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(config.getHost(), config.getPort()); builder.setLocalAddress(config.getHost(), config.getPort());
builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort());
builder.setDecoder(new DefaultLwM2mNodeDecoder()); builder.setDecoder(new DefaultLwM2mDecoder());
/* Use a magic converter to support bad type send by the UI. */ /* Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance()));
/* Create CoAP Config */ /* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config));

15
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java

@ -52,12 +52,15 @@ public enum LwM2mOperationType {
DELETE(16, "Delete", true), DELETE(16, "Delete", true),
// only for RPC // only for RPC
FW_UPDATE(17, "FirmwareUpdate", false); FW_UPDATE(17, "FirmwareUpdate", false),
// FW_READ_INFO(18, "FirmwareReadInfo", false),
SW_UPDATE(19, "SoftwareUpdate", false),
// SW_READ_INFO(20, "SoftwareReadInfo", false),
SW_UNINSTALL(21, "SoftwareUninstall", false),
CREATE(11, "Create", true);
// FW_READ_INFO(18, "FirmwareReadInfo"),
// SW_READ_INFO(19, "SoftwareReadInfo"),
// SW_UPDATE(20, "SoftwareUpdate"),
// SW_UNINSTALL(21, "SoftwareUninstall");
@Getter @Getter
private final int code; private final int code;
@ -78,7 +81,7 @@ public enum LwM2mOperationType {
this.type = type; this.type = type;
this.hasObjectId = hasObjectId; this.hasObjectId = hasObjectId;
this.composite = composite; this.composite = composite;
if(hasObjectId && composite){ if (hasObjectId && composite) {
throw new IllegalArgumentException("Can't set both Composite and hasObjectId for the same operation!"); throw new IllegalArgumentException("Can't set both Composite and hasObjectId for the same operation!");
} }
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java

@ -98,7 +98,7 @@ public class LwM2mServerListener {
@Override @Override
public void onError(Observation observation, Registration registration, Exception error) { public void onError(Observation observation, Registration registration, Exception error) {
log.error("Unable to handle notification of [{}:{}]", observation.getRegistrationId(), observation.getPath(), error); log.error("Unable to handle notification of [{}:{}] [{}]", observation.getRegistrationId(), observation.getPath(), error.getMessage());
} }
@Override @Override

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java

@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService; 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.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import com.google.gson.JsonParser;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser; import org.eclipse.leshan.core.model.DDFFileParser;
@ -50,6 +51,7 @@ import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.
public class LwM2mTransportServerHelper { public class LwM2mTransportServerHelper {
private final LwM2mTransportContext context; private final LwM2mTransportContext context;
private final static JsonParser JSON_PARSER = new JsonParser();;
public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) { public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder(); PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder();
@ -229,7 +231,7 @@ public class LwM2mTransportServerHelper {
case STRING_V: case STRING_V:
return kv.getStringV(); return kv.getStringV();
case JSON_V: case JSON_V:
return kv.getJsonV(); return JSON_PARSER.parse(kv.getJsonV());
} }
return null; return null;
} }

72
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java

@ -16,6 +16,9 @@
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.Attribute;
@ -26,6 +29,7 @@ import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.SimpleDownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest;
@ -39,18 +43,23 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans
import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.client.ResourceValue;
import org.thingsboard.server.transport.lwm2m.server.downlink.HasVersionedId;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION;
@ -302,6 +311,12 @@ public class LwM2mTransportUtil {
return attributeLists.toArray(Attribute[]::new); return attributeLists.toArray(Attribute[]::new);
} }
/**
* "UNSIGNED_INTEGER": // Number -> Integer Example:
* Alarm Timestamp [32-bit unsigned integer]
* Short Server ID, Object ID, Object Instance ID, Resource ID, Resource Instance ID
* "CORELINK": // String used in Attribute
*/
public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) {
switch (value.getClass().getSimpleName()) { switch (value.getClass().getSimpleName()) {
case "Double": case "Double":
@ -323,6 +338,63 @@ public class LwM2mTransportUtil {
} }
} }
public static void validateVersionedId(LwM2mClient client, HasVersionedId request) {
String msgExceptionStr = "";
if (request.getObjectId() == null) {
msgExceptionStr = "Specified object id is null!";
} else {
msgExceptionStr = client.isValidObjectVersion(request.getVersionedId());
}
if (!msgExceptionStr.isEmpty() ) {
throw new IllegalArgumentException(msgExceptionStr);
}
}
public static String getMsgException(String keyName, Set<String> msgException) {
if (msgException.size() == 1) {
msgException.add(" is not configured in the device profile!");
}
msgException.remove("");
return String.format("%s %s", keyName, String.join(",", msgException)).trim();
}
public static Map convertMultiResourceValuesFromRpcBody(LinkedHashMap value, ResourceModel.Type type, String versionedId) {
Gson gson = new Gson();
JsonParser JSON_PARSER = new JsonParser();
String json = gson.toJson(value, LinkedHashMap.class);
return convertMultiResourceValuesFromJson((JsonObject) JSON_PARSER.parse(json), type, versionedId);
}
public static Map convertMultiResourceValuesFromJson(JsonObject newValProto, ResourceModel.Type type, String versionedId) {
Map newValues = equalsMultiResourceValuesResourceType(type);
newValProto.getAsJsonObject().entrySet().forEach((obj) -> {
newValues.put(Integer.valueOf(obj.getKey()), LwM2mValueConverterImpl.getInstance().convertValue(obj.getValue().getAsString(),
STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))));
});
return newValues;
}
public static Map equalsMultiResourceValuesResourceType(ResourceModel.Type type) {
switch (type) {
case FLOAT:
return new HashMap<Integer, Float>();
case INTEGER:
return new HashMap<Integer, Integer>();
case STRING:
return new HashMap<Integer, String>();
case BOOLEAN:
return new HashMap<Integer, Boolean>();
case OPAQUE:
return new HashMap<Integer, byte[]>();
case TIME:
return new HashMap<Integer, Date>();
case OBJLNK:
return new HashMap<Integer, ObjectLink>();
default:
return null;
}
}
public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) {
switch (type) { switch (type) {
/** Integer [0:255]; */ /** Integer [0:255]; */

113
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java

@ -17,11 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.attributes;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
@ -43,14 +46,19 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; 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.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_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.LwM2mTransportUtil.fromVersionedIdToObjectId;
@Slf4j @Slf4j
@ -145,7 +153,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
newSoftwareTag = getStrValue(tsKvProto); newSoftwareTag = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) {
newSoftwareUrl = getStrValue(tsKvProto); newSoftwareUrl = getStrValue(tsKvProto);
}else { } else {
otherAttributes.add(tsKvProto); otherAttributes.add(tsKvProto);
} }
} }
@ -156,7 +164,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag)); otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag));
} }
if (!otherAttributes.isEmpty()) { if (!otherAttributes.isEmpty()) {
onAttributesUpdate(lwM2MClient, otherAttributes); onAttributesUpdate(lwM2MClient, otherAttributes, true);
} }
} else if (lwM2MClient == null) { } else if (lwM2MClient == null) {
log.error("OnAttributeUpdate, lwM2MClient is null"); log.error("OnAttributeUpdate, lwM2MClient is null");
@ -169,42 +177,97 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
* => send to client Request Update of value (new value from shared attribute) * => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path) * and LwM2MClient.delayedRequests.add(path)
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values * #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
*/ */
@Override @Override
public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos) { public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos, boolean logFailedUpdateOfNonChangedValue) {
log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos);
Map <String, TransportProtos.TsKvProto> attributesUpdate = new ConcurrentHashMap<>();
tsKvProtos.forEach(tsKvProto -> { tsKvProtos.forEach(tsKvProto -> {
String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); try {
if (pathIdVer != null) { String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey(), false);
// #1.1 if (pathIdVer != null) {
if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { // #1.1
if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) {
if (tsKvProto.getTs() >= lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
attributesUpdate.put(pathIdVer, tsKvProto);
}
} else {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
attributesUpdate.put(pathIdVer, tsKvProto);
} }
} else {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
} }
} catch (IllegalArgumentException e){
log.error("Failed update resource [{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), e.getMessage());
String logMsg = String.format("%s: Failed update resource onAttributesUpdate %s.",
LOG_LWM2M_ERROR, e.getMessage());
logService.log(lwM2MClient, logMsg);
} }
}); });
clientContext.update(lwM2MClient); clientContext.update(lwM2MClient);
// #2.1 // #2.1
lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { attributesUpdate.forEach((pathIdVer, tsKvProto) -> {
this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config.getModelProvider());
getValueFromKvProto(tsKvProto.getKv()), pathIdVer); Object newValProto = getValueFromKvProto(tsKvProto.getKv());
Object oldResourceValue = this.getResourceValueFormatKv(lwM2MClient, pathIdVer);
if (!resourceModel.multiple || !(newValProto instanceof JsonElement)) {
this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, logFailedUpdateOfNonChangedValue);
} else {
pushUpdateMultiToClientIfNeeded(lwM2MClient, resourceModel, (JsonElement) newValProto,
(Map<Integer, LwM2mResourceInstance>) oldResourceValue, pathIdVer);
}
}); });
} }
private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue,
if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { String versionedId, boolean logFailedUpdateOfNonChangedValue) {
if (newValue == null) {
String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. New value is bad",
LOG_LWM2M_ERROR, versionedId, "null");
logService.log(lwM2MClient, logMsg);
log.error("Failed update resource [{}] [{}]", versionedId, 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 {
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build();
downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId));
}
}
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());
}
}
});
}
}
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 { } else {
log.error("Failed update resource [{}] [{}]", versionedId, newValue); 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", String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad",
LOG_LWM2M_ERROR, versionedId, newValue); LOG_LWM2M_ERROR, versionedId, newValProto);
logService.log(lwM2MClient, logMsg); logService.log(client, logMsg);
log.info("Failed update resource [{}] [{}]", versionedId, newValue);
} }
} }
@ -217,8 +280,14 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
if (resourceValue != null) { if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, if (!resourceValue.isMultiInstances()) {
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)));
} else if (resourceValue.getInstances().size() > 0) {
return resourceValue.getInstances();
} else {
return null;
}
} else { } else {
return null; return null;
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java

@ -30,5 +30,5 @@ public interface LwM2MAttributesService {
void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo); void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo);
void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos); void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos, boolean logFailedUpdateOfNonChangedValue);
} }

121
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -27,7 +27,7 @@ import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.request.WriteRequest.Mode;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -43,8 +43,10 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -57,10 +59,11 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; 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.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.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; 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.fromVersionedIdToObjectId;
@ -101,11 +104,11 @@ public class LwM2mClient implements Serializable {
@Getter @Getter
private Long psmActivityTimer; private Long psmActivityTimer;
@Getter @Getter
private Long edrxCycle;
@Getter
private Long pagingTransmissionWindow; private Long pagingTransmissionWindow;
@Getter @Getter
@Setter @Setter
private Long edrxCycle;
@Getter
private Registration registration; private Registration registration;
@Getter @Getter
@Setter @Setter
@ -118,6 +121,10 @@ public class LwM2mClient implements Serializable {
private boolean firstEdrxDownlink = true; private boolean firstEdrxDownlink = true;
@Getter
Set<ContentFormat> clientSupportContentFormats;
@Getter
ContentFormat defaultContentFormat;
@Getter @Getter
private final AtomicInteger retryAttempts; private final AtomicInteger retryAttempts;
@ -147,6 +154,12 @@ public class LwM2mClient implements Serializable {
this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow(); this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow();
} }
public void setRegistration(Registration registration) {
this.registration = registration;
this.clientSupportContentFormats = clientSupportContentFormat(registration);
this.defaultContentFormat = calculateDefaultContentFormat(registration);
}
public void lock() { public void lock() {
lock.lock(); lock.lock();
} }
@ -210,15 +223,16 @@ public class LwM2mClient implements Serializable {
.build(); .build();
} }
public boolean saveResourceValue(String pathRezIdVer, LwM2mResource rez, LwM2mModelProvider modelProvider) { public boolean saveResourceValue(String pathRezIdVer, LwM2mResource resource, LwM2mModelProvider modelProvider, Mode mode) {
if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) { if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null &&
this.resources.get(pathRezIdVer).setLwM2mResource(rez); resourceEqualsModel(resource, this.resources.get(pathRezIdVer).getResourceModel())) {
this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode);
return true; return true;
} else { } else {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) { if (resourceModel != null && resourceEqualsModel(resource, resourceModel)) {
this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel)); this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel));
return true; return true;
} else { } else {
return false; return false;
@ -226,6 +240,11 @@ public class LwM2mClient implements Serializable {
} }
} }
private boolean resourceEqualsModel(LwM2mResource resource, ResourceModel resourceModel) {
return ((!resourceModel.multiple && resource instanceof LwM2mSingleResource) ||
(resourceModel.multiple && resource instanceof LwM2mMultipleResource));
}
public Object getResourceValue(String pathRezIdVer, String pathRezId) { public Object getResourceValue(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) { if (this.resources.get(pathRez) != null) {
@ -282,7 +301,6 @@ public class LwM2mClient implements Serializable {
} }
public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverter converter) { LwM2mValueConverter converter) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
@ -299,31 +317,61 @@ public class LwM2mClient implements Serializable {
return resources; return resources;
} }
/**
* The instance must have all the resources that have the property
* <Mandatory>Mandatory</Mandatory>
*/
public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverter converter) { LwM2mValueConverter converter) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet(); Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration) Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()).resources; .getObjectModel(pathIds.getObjectId()).resources;
resourceModels.forEach((resId, resourceModel) -> { if (params != null) {
if (((Map) params).containsKey(String.valueOf(resId))) { resourceModels.forEach((resourceId, resourceModel) -> {
Object value = ((Map) params).get((String.valueOf(resId))); if (((Map) params).containsKey(String.valueOf(resourceId))) {
resources.add(LwM2mSingleResource.newResource(resId, Object value = ((Map) params).get((String.valueOf(resourceId)));
converter.convertValue(value, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type)); LwM2mResource resource = null;
if (resourceModel.multiple) {
} if (value instanceof LinkedHashMap) {
}); Map values = convertMultiResourceValuesFromRpcBody((LinkedHashMap) value, resourceModel.type, pathRezIdVer);
resource = LwM2mMultipleResource.newResource(resourceId, (Map<Integer, ?>) values, resourceModel.type);
}
} else {
Object valueRez = value.getClass().getSimpleName().equals("Integer") ? ((Integer) value).longValue() : value;
resource = LwM2mSingleResource.newResource(resourceId,
converter.convertValue(valueRez, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type);
}
if (resource != null) {
resources.add(resource);
} else if (resourceModel.operations.isWritable() && resourceModel.mandatory) {
throw new IllegalArgumentException("Resource id=" + resourceId + " is mandatory. The value of this resource must not be null.");
}
}
else if (resourceModel.operations.isWritable() && resourceModel.mandatory) {
throw new IllegalArgumentException("Resource id=" + resourceId + " is mandatory. The value of this resource must not be null.");
}
});
}
else {
throw new IllegalArgumentException("The value of this resource must not be null.");
}
return resources; return resources;
} }
public void isValidObjectVersion(String path) { public String isValidObjectVersion(String path) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
String verRez = getVerFromPathIdVerOrId(path); if (verSupportedObject == null) {
if ((verRez != null && !verRez.equals(verSupportedObject)) || return String.format("Specified resource id %s is missing from the client!", path);
(verRez == null && !LWM2M_OBJECT_VERSION_DEFAULT.equals(verSupportedObject))) { } else {
throw new IllegalArgumentException(String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject)); String verRez = getVerFromPathIdVerOrId(path);
if ((verRez != null && !verRez.equals(verSupportedObject)) ||
(verRez == null && !LWM2M_OBJECT_VERSION_DEFAULT.equals(verSupportedObject))) {
return String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject);
}
} }
return "";
} }
/** /**
@ -365,12 +413,33 @@ public class LwM2mClient implements Serializable {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public ContentFormat getDefaultContentFormat() { private ContentFormat calculateDefaultContentFormat(Registration registration) {
if (registration == null) { if (registration == null) {
return ContentFormat.DEFAULT; return ContentFormat.DEFAULT;
} else{
return LwM2mVersion.fromVersion(registration.getLwM2mVersion()).getContentFormat();
}
}
private Set<ContentFormat> clientSupportContentFormat(Registration registration) {
Set<ContentFormat> contentFormats = new HashSet<>();
if (registration == null) {
contentFormats.add(ContentFormat.DEFAULT);
} else { } else {
return LwM2mVersion.fromVersionStr(registration.getLwM2mVersion()).getContentFormat(); String code = Arrays.stream(registration.getObjectLinks()).filter(link -> link.getUrl().equals("/")).
findFirst().get().getAttributes().get("ct");
if (code != null) {
Set<ContentFormat> codes = Stream.of(code.replaceAll("\"", "").split(" ", -1))
.map(String::trim)
.map(Integer::parseInt)
.map(ContentFormat::fromCode)
.collect(Collectors.toSet());
contentFormats.addAll(codes);
} else {
contentFormats.add(ContentFormat.DEFAULT);
}
} }
return contentFormats;
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.client; package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
@ -51,7 +52,7 @@ public interface LwM2mClientContext {
LwM2mClient getClientByDeviceId(UUID deviceId); LwM2mClient getClientByDeviceId(UUID deviceId);
String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName); String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName, boolean isCompositeOperation);
void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials);
@ -61,7 +62,7 @@ public interface LwM2mClientContext {
void sendMsgsAfterSleeping(LwM2mClient lwM2MClient); void sendMsgsAfterSleeping(LwM2mClient lwM2MClient);
boolean isComposite(LwM2mClient client); ContentFormat getContentFormatComposite(LwM2mClient client);
void onUplink(LwM2mClient client); void onUplink(LwM2mClient client);

34
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -18,8 +18,8 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -36,7 +36,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
@ -59,8 +58,7 @@ import java.util.function.Predicate;
import static org.eclipse.leshan.core.SecurityMode.NO_SEC; 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.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getMsgException;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey;
@Slf4j @Slf4j
@Service @Service
@ -291,12 +289,13 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
@Override @Override
public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName) { public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName, boolean isCompositeOperation) {
Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getProfileId()); Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getProfileId());
Set<String> msgException = ConcurrentHashMap.newKeySet();
msgException.add("");
return profile.getObserveAttr().getKeyName().entrySet().stream() return profile.getObserveAttr().getKeyName().entrySet().stream()
.filter(e -> e.getValue().equals(keyName) && validateResourceInModel(client, e.getKey(), false)).findFirst().orElseThrow( .filter(e -> e.getValue().equals(keyName) && (isCompositeOperation || !msgException.add(client.isValidObjectVersion(e.getKey())))).findFirst().orElseThrow(
() -> new IllegalArgumentException(keyName + " is not configured in the device profile!") () -> new IllegalArgumentException(getMsgException (keyName, msgException))
).getKey(); ).getKey();
} }
@ -336,7 +335,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) {
PowerMode powerMode = getPowerMode(lwM2MClient); PowerMode powerMode = getPowerMode(lwM2MClient);
if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) { if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) {
defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient); defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient, false);
TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg
.newBuilder() .newBuilder()
.setSessionInfo(lwM2MClient.getSession()) .setSessionInfo(lwM2MClient.getSession())
@ -412,9 +411,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
@Override @Override
public boolean isComposite(LwM2mClient client) { public ContentFormat getContentFormatComposite(LwM2mClient client) {
return LwM2mVersion.fromVersionStr(client.getRegistration().getLwM2mVersion()).isComposite() & return client.getClientSupportContentFormats().contains(ContentFormat.SENML_JSON) ? ContentFormat.SENML_JSON :
getProfile(client.getProfileId()).getClientLwM2mSettings().isCompositeOperationsSupport(); client.getClientSupportContentFormats().contains(ContentFormat.SENML_CBOR) ? ContentFormat.SENML_CBOR : null;
} }
@Override @Override
@ -541,15 +540,4 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
return timeout; return timeout;
} }
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config
.getModelProvider());
Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathIdVer);
return resourceModel != null && (isWritableNotOptional ?
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
}
} }

44
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java

@ -16,13 +16,20 @@
package org.thingsboard.server.transport.lwm2m.server.client; package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.request.WriteRequest.Mode;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@Data @Data
public class ResourceValue implements Serializable { public class ResourceValue implements Serializable {
@ -32,32 +39,35 @@ public class ResourceValue implements Serializable {
private TbResourceModel resourceModel; private TbResourceModel resourceModel;
public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) { public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) {
this.lwM2mResource = toTbLwM2MResource(lwM2mResource);
this.resourceModel = toTbResourceModel(resourceModel); this.resourceModel = toTbResourceModel(resourceModel);
updateLwM2mResource(lwM2mResource, Mode.UPDATE);
} }
public void setLwM2mResource(LwM2mResource lwM2mResource) { public void updateLwM2mResource(LwM2mResource lwM2mResource, Mode mode) {
this.lwM2mResource = toTbLwM2MResource(lwM2mResource); if (lwM2mResource instanceof LwM2mSingleResource) {
this.lwM2mResource = new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType());
} else if (lwM2mResource instanceof LwM2mMultipleResource) {
if (lwM2mResource.getInstances().values().size() > 0) {
Set <TbLwM2MResourceInstance> instancesSet = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet());
if (Mode.REPLACE.equals(mode) && this.lwM2mResource != null) {
Map<Integer, LwM2mResourceInstance> oldInstances = this.lwM2mResource.getInstances();
oldInstances.values().forEach(v -> {
if (instancesSet.stream().filter(vIns -> v.getId() == vIns.getId()).collect(Collectors.toList()).size() == 0){
instancesSet.add(toTbLwM2MResourceInstance(v));
}
});
}
TbLwM2MResourceInstance[] instances = instancesSet.toArray(new TbLwM2MResourceInstance[0]);
this.lwM2mResource = new TbLwM2mMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances);
}
}
} }
public void setResourceModel(ResourceModel resourceModel) { public void setResourceModel(ResourceModel resourceModel) {
this.resourceModel = toTbResourceModel(resourceModel); this.resourceModel = toTbResourceModel(resourceModel);
} }
private static TbLwM2MResource toTbLwM2MResource(LwM2mResource lwM2mResource) {
if (lwM2mResource.isMultiInstances()) {
if ( lwM2mResource.getInstances().values().size() > 0) {
TbLwM2MResourceInstance [] instances = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()).toArray(new TbLwM2MResourceInstance[0]);
return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances);
}
else {
return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), new TbLwM2MResourceInstance[0]);
}
} else {
return new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType());
}
}
private static TbLwM2MResourceInstance toTbLwM2MResourceInstance(LwM2mResourceInstance instance) { private static TbLwM2MResourceInstance toTbLwM2MResourceInstance(LwM2mResourceInstance instance) {
return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType()); return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType());
} }

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MMultipleResource.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java

@ -17,16 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
public class TbLwM2MMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable { public class TbLwM2mMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable {
private static final long serialVersionUID = 4658477128628087186L; private static final long serialVersionUID = 4658477128628087186L;
public TbLwM2MMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) { public TbLwM2mMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) {
super(id, type, instances); super(id, type, instances);
} }
} }

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java

@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.common;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
public abstract class LwM2MExecutorAwareService { public abstract class LwM2MExecutorAwareService {

423
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

@ -18,16 +18,19 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.Link;
import org.eclipse.leshan.core.LwM2m;
import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CompositeDownlinkRequest; import org.eclipse.leshan.core.request.CompositeDownlinkRequest;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.request.DownlinkRequest;
@ -40,7 +43,9 @@ import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.exception.ClientSleepingException; import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.eclipse.leshan.core.request.exception.InvalidRequestException;
import org.eclipse.leshan.core.request.exception.TimeoutException; import org.eclipse.leshan.core.request.exception.TimeoutException;
import org.eclipse.leshan.core.response.CreateResponse;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
@ -55,7 +60,6 @@ import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -73,9 +77,12 @@ import javax.annotation.PreDestroy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -85,7 +92,11 @@ import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN;
import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; 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.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.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateVersionedId;
@Slf4j @Slf4j
@Service @Service
@ -123,40 +134,49 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback) { public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback) {
validateVersionedId(client, request); try {
ReadRequest downlink = new ReadRequest(getRequestContentFormat(client, request, this.config.getModelProvider()), request.getObjectId()); validateVersionedId(client, request);
sendSimpleRequest(client, downlink, request.getTimeout(), callback); ReadRequest downlink = new ReadRequest(getReadRequestContentFormat(client, request, this.config.getModelProvider()), request.getObjectId());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage());
}
} }
@Override @Override
public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback) { public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request,
validateVersionedIds(client, request); DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback, ContentFormat contentFormatComposite) {
ContentFormat requestContentFormat = ContentFormat.SENML_JSON; try {
ContentFormat responseContentFormat = ContentFormat.SENML_JSON; ReadCompositeRequest downlink = new ReadCompositeRequest(contentFormatComposite, contentFormatComposite, request.getObjectIds());
sendCompositeRequest(client, downlink, this.config.getTimeout(), callback);
ReadCompositeRequest downlink = new ReadCompositeRequest(requestContentFormat, responseContentFormat, request.getObjectIds()); } catch (InvalidRequestException e) {
sendCompositeRequest(client, downlink, request.getTimeout(), callback); callback.onValidationError(request.toString(), e.getMessage());
}
} }
@Override @Override
public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) { public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) {
validateVersionedId(client, request); try {
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); validateVersionedId(client, request);
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
ObserveRequest downlink; if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) {
ContentFormat contentFormat = getRequestContentFormat(client, request, this.config.getModelProvider()); ObserveRequest downlink;
if (resultIds.isResource()) { ContentFormat contentFormat = getReadRequestContentFormat(client, request, this.config.getModelProvider());
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); if (resultIds.isResource()) {
} else if (resultIds.isObjectInstance()) { downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); } else if (resultIds.isObjectInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} else { } else {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); callback.onValidationError(resultIds.toString(), "Observation is already registered!");
} }
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); } catch (InvalidRequestException e) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); callback.onValidationError(request.toString(), e.getMessage());
} else {
callback.onValidationError(resultIds.toString(), "Observation is already registered!");
} }
} }
@ -174,25 +194,36 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback<ExecuteRequest, ExecuteResponse> callback) { public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback<ExecuteRequest, ExecuteResponse> callback) {
ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); try {
if (resourceModelExecute != null) { ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider());
ExecuteRequest downlink; if (resourceModelExecute != null) {
if (request.getParams() != null && !resourceModelExecute.multiple) { validateVersionedId(client, request);
downlink = new ExecuteRequest(request.getVersionedId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); ExecuteRequest downlink;
} else { if (request.getParams() != null && !resourceModelExecute.multiple) {
downlink = new ExecuteRequest(request.getVersionedId()); downlink = new ExecuteRequest(request.getObjectId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId())));
} else {
downlink = new ExecuteRequest(request.getObjectId());
}
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} }
sendSimpleRequest(client, downlink, request.getTimeout(), callback); } catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage());
} }
} }
@Override @Override
public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback<DeleteRequest, DeleteResponse> callback) { public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback<DeleteRequest, DeleteResponse> callback) {
sendSimpleRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); try {
validateVersionedId(client, request);
sendSimpleRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback);
} catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage());
}
} }
@Override @Override
public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) {
validateVersionedId(client, request);
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId());
callback.onSuccess(request, observeCancelCnt); callback.onSuccess(request, observeCancelCnt);
} }
@ -209,51 +240,86 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
sendSimpleRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); sendSimpleRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback);
} }
/**
* Example # 1:
* AttributeSet attributes = new AttributeSet(new Attribute(Attribute.MINIMUM_PERIOD, 10L),
* new Attribute(Attribute.MAXIMUM_PERIOD, 100L));
* WriteAttributesRequest requestTest = new WriteAttributesRequest(3, 0, 14, attributes);
* sendSimpleRequest(client, requestTest, request.getTimeout(), callback);
* <p>
* Example # 2
* Dimension and Object version are read only attributes.
* addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255);
* addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity());
*/
@Override @Override
public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback<WriteAttributesRequest, WriteAttributesResponse> callback) { public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback<WriteAttributesRequest, WriteAttributesResponse> callback) {
validateVersionedId(client, request); try {
if (request.getAttributes() == null) { validateVersionedId(client, request);
throw new IllegalArgumentException("Attributes to write are not specified!"); if (request.getAttributes() == null) {
throw new IllegalArgumentException("Attributes to write are not specified!");
}
ObjectAttributes params = request.getAttributes();
List<Attribute> attributes = new LinkedList<>();
addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax());
addAttribute(attributes, MINIMUM_PERIOD, params.getPmin());
addAttribute(attributes, GREATER_THAN, params.getGt());
addAttribute(attributes, LESSER_THAN, params.getLt());
addAttribute(attributes, STEP, params.getSt());
AttributeSet attributeSet = new AttributeSet(attributes);
sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback);
} catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage());
} }
ObjectAttributes params = request.getAttributes();
List<Attribute> attributes = new LinkedList<>();
// Dimension and Object version are read only attributes.
// addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255);
// addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity());
addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax());
addAttribute(attributes, MINIMUM_PERIOD, params.getPmin());
addAttribute(attributes, GREATER_THAN, params.getGt());
addAttribute(attributes, LESSER_THAN, params.getLt());
addAttribute(attributes, STEP, params.getSt());
AttributeSet attributeSet = new AttributeSet(attributes);
sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback);
} }
@Override @Override
public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) { public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) {
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
if (resourceModelWrite != null) { if (resultIds.isResource() || resultIds.isResourceInstance()) {
ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); validateVersionedId(client, request);
try { ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider());
LwM2mPath path = new LwM2mPath(request.getObjectId()); if (resourceModelWrite != null) {
WriteRequest downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider());
path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); try {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); WriteRequest downlink = null;
} catch (Exception e) { if (resourceModelWrite.multiple) {
callback.onError(toString(request), e); if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) {
downlink = new WriteRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(),
(Map<Integer, ?>) request.getValue(), resourceModelWrite.type);
} else {
callback.onValidationError(toString(request), "Resource value is: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!");
}
} else {
downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat,
resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), request.getValue());
}
if (downlink != null) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} else {
callback.onValidationError(toString(request), "WriteRequest is null.");
}
} catch (Exception e) {
callback.onError(toString(request), e);
}
} else {
callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!");
} }
} else { } else {
callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for Resource or ResourceInstance!");
} }
} }
@Override @Override
public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback) { public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest,
ContentFormat contentFormat = ContentFormat.SENML_JSON; DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback, ContentFormat contentFormatComposite) {
try { try {
WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormat, rpcWriteCompositeRequest.getNodes()); WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormatComposite, rpcWriteCompositeRequest.getNodes());
//TODO: replace config.getTimeout(); //TODO: replace config.getTimeout();
sendWriteCompositeRequest(client, downlink, config.getTimeout(), callback); sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback);
} catch (InvalidRequestException e) {
callback.onValidationError(rpcWriteCompositeRequest.toString(), e.getMessage());
} catch (Exception e) { } catch (Exception e) {
callback.onError(toString(rpcWriteCompositeRequest), e); callback.onError(toString(rpcWriteCompositeRequest), e);
} }
@ -261,38 +327,90 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) { 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);
} else {
callback.onValidationError(toString(request), "Resource value is bad. Format: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!");
}
}
}
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 !");
}
} catch (Exception e) {
callback.onValidationError(toString(request), e.getMessage());
}
}
public void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback<CreateRequest, CreateResponse> callback) {
validateVersionedId(client, request);
CreateRequest downlink = null;
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
if (resultIds.isResource()) { ObjectModel objectModel = client.getObjectModel(request.getObjectId(), this.config.getModelProvider());
/* // POST /{Object ID}/{Object Instance ID} && Resources is Mandatory
* send request: path = '/3/0' node == wM2mObjectInstance if (objectModel.multiple) {
* with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} // LwM2M CBOR, SenML CBOR, SenML JSON, or TLV (see [LwM2M-CORE])
**/ ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider());
Collection<LwM2mResource> resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); if (resultIds.isObject() || resultIds.isObjectInstance()) {
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); Collection<LwM2mResource> resources;
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); if (resultIds.isObject()) {
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), // contentFormat = ContentFormat.TLV;
resultIds.getObjectInstanceId(), resources); if (request.getValue() != null) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter);
} else if (resultIds.isObjectInstance()) { downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), resources);
/* } else if (request.getNodes() != null && request.getNodes().size() > 0) {
* params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" Set<LwM2mObjectInstance> instances = ConcurrentHashMap.newKeySet();
* int rscId = resultIds.getObjectInstanceId(); request.getNodes().forEach((key, value) -> {
* contentFormat – Format of the payload (TLV or JSON). Collection<LwM2mResource> resourcesForInstance = client.getNewResourcesForInstance(request.getVersionedId(), value, this.config.getModelProvider(), this.converter);
*/ LwM2mObjectInstance instance = new LwM2mObjectInstance(Integer.parseInt(key), resourcesForInstance);
Collection<LwM2mResource> resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); instances.add(instance);
if (resources.size() > 0) { });
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : ContentFormat.DEFAULT; LwM2mObjectInstance[] instanceArrays = instances.toArray(new LwM2mObjectInstance[instances.size()]);
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), instanceArrays);
}
} else {
resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter);
LwM2mObjectInstance instance = new LwM2mObjectInstance(resultIds.getObjectInstanceId(), resources);
downlink = new CreateRequest(contentFormat, resultIds.getObjectId(), instance);
}
}
if (downlink != null) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback); sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} else { } else {
callback.onValidationError(toString(request), "No resources to update!"); callback.onValidationError(toString(request), "Path " + request.getVersionedId() +
". This operation can only be used for created new ObjectInstance !");
} }
} else { } else {
callback.onValidationError(toString(request), "Update of the root level object is not supported yet!"); throw new IllegalArgumentException("Path " + request.getVersionedId() +
". Object must be Multiple !");
} }
} }
private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendSimpleRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback) { private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendSimpleRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback) {
sendRequest(client, request, timeoutInMs, callback, r -> request.getPath().toString()); sendRequest(client, request, timeoutInMs, callback, r -> request.getPath().toString());
} }
@ -301,6 +419,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
sendRequest(client, request, timeoutInMs, callback, r -> request.getPaths().toString()); sendRequest(client, request, timeoutInMs, callback, r -> request.getPaths().toString());
} }
private <R extends DownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback, Function<R, String> pathToStringFunction) { private <R extends DownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback, Function<R, String> pathToStringFunction) {
if (!clientContext.isDownlinkAllowed(client)) { if (!clientContext.isDownlinkAllowed(client)) {
log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint()); log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint());
@ -319,7 +438,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
clientContext.awake(client); clientContext.awake(client);
} }
}); });
},e -> handleDownlinkError(client, request, callback, e)); }, e -> handleDownlinkError(client, request, callback, e));
} catch (Exception e) { } catch (Exception e) {
handleDownlinkError(client, request, callback, e); handleDownlinkError(client, request, callback, e);
} }
@ -366,6 +485,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
}); });
} }
private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) { private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) {
switch (type) { switch (type) {
case STRING: // String case STRING: // String
@ -395,24 +515,6 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
} }
} }
private void validateVersionedId(LwM2mClient client, HasVersionedId request) {
client.isValidObjectVersion(request.getVersionedId());
if (request.getObjectId() == null) {
throw new IllegalArgumentException("Specified object id is null!");
}
}
private void validateVersionedIds(LwM2mClient client, HasVersionedIds request) {
for (String versionedId : request.getVersionedIds()) {
client.isValidObjectVersion(versionedId);
}
for (String objectId : request.getObjectIds()) {
if (objectId == null) {
throw new IllegalArgumentException("Specified object id is null!");
}
}
}
private static <T> void addAttribute(List<Attribute> attributes, String attributeName, T value) { private static <T> void addAttribute(List<Attribute> attributes, String attributeName, T value) {
addAttribute(attributes, attributeName, value, null, null); addAttribute(attributes, attributeName, value, null, null);
} }
@ -427,52 +529,91 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
} }
} }
private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { private static <T extends HasContentFormat & HasVersionedId> ContentFormat getReadRequestContentFormat(LwM2mClient client, T request, LwM2mModelProvider modelProvider) {
switch (type) { if (request.getRequestContentFormat().isPresent()) {
case BOOLEAN: return request.getRequestContentFormat().get();
case STRING: } else {
case TIME: return getRequestContentFormat(client, request.getVersionedId(), modelProvider);
case INTEGER:
case FLOAT:
return client.getDefaultContentFormat();
case OPAQUE:
return ContentFormat.OPAQUE;
case OBJLNK:
return ContentFormat.LINK;
default:
} }
throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type);
} }
private static ContentFormat getRequestContentFormat(LwM2mClient client, HasContentFormat request, LwM2mModelProvider modelProvider) { private static ContentFormat getWriteRequestContentFormat(LwM2mClient client, TbLwM2MDownlinkRequest request, LwM2mModelProvider modelProvider) {
if (request.getRequestContentFormat() != null) { if (request instanceof TbLwM2MWriteReplaceRequest && ((TbLwM2MWriteReplaceRequest) request).getContentFormat() != null) {
return request.getRequestContentFormat(); return ((TbLwM2MWriteReplaceRequest) request).getContentFormat();
} else if (request instanceof TbLwM2MWriteUpdateRequest && ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat() != null) {
return ((TbLwM2MWriteUpdateRequest) request).getObjectContentFormat();
} else { } else {
String versionedId = null; String versionedId = null;
if (request instanceof TbLwM2MReadRequest) { if (request instanceof TbLwM2MWriteReplaceRequest) {
versionedId = ((TbLwM2MReadRequest) request).getVersionedId(); versionedId = ((TbLwM2MWriteReplaceRequest) request).getVersionedId();
} else if (request instanceof TbLwM2MObserveRequest) { } else if (request instanceof TbLwM2MWriteUpdateRequest) {
versionedId = ((TbLwM2MObserveRequest) request).getVersionedId(); versionedId = ((TbLwM2MWriteUpdateRequest) request).getVersionedId();
} else if (request instanceof TbLwM2MCreateRequest) {
versionedId = ((TbLwM2MCreateRequest) request).getVersionedId();
}
return getRequestContentFormat(client, versionedId, modelProvider);
}
}
private static ContentFormat getRequestContentFormat(LwM2mClient client, String versionedId, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(versionedId));
if (pathIds.isResourceInstance()) {
ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider);
if (OBJLNK.equals(resourceModel.type)) {
return ContentFormat.LINK;
} else if (OPAQUE.equals(resourceModel.type)) {
return ContentFormat.OPAQUE;
} else {
return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON);
}
} else if (pathIds.isResource()) {
ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider);
if (!resourceModel.multiple) {
if (OBJLNK.equals(resourceModel.type)) {
return ContentFormat.LINK;
} else if (OPAQUE.equals(resourceModel.type)) {
return ContentFormat.OPAQUE;
} else {
return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON);
}
} else {
return getContentFormatForComplex(client);
} }
String id = fromVersionedIdToObjectId(versionedId); } else {
if (id != null && new LwM2mPath(id).isResource() && !client.isResourceMultiInstances(versionedId, modelProvider)) { return getContentFormatForComplex(client);
return client.getDefaultContentFormat(); }
}
private static ContentFormat getContentFormatForComplex(LwM2mClient client) {
if (LwM2m.Version.V1_0.equals(client.getRegistration().getLwM2mVersion())) {
return ContentFormat.TLV;
} else if (LwM2m.Version.V1_1.equals(client.getRegistration().getLwM2mVersion())) {
ContentFormat result = findFirst(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON);
if (result != null) {
return result;
} else { } else {
return ContentFormat.DEFAULT; throw new RuntimeException("The client does not support any of SenML CBOR, SenML JSON, TLV or JSON formats. Can't send complex requests. Try using singe-instance requests.");
}
} else {
throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!");
}
}
private static ContentFormat findFirst(Set<ContentFormat> supported, ContentFormat defaultValue, ContentFormat... desiredFormats) {
for (ContentFormat contentFormat : desiredFormats) {
if (supported.contains(contentFormat)) {
return contentFormat;
} }
} }
return defaultValue;
} }
private <R> String toString(R request) { private <R> String toString(R request) {
try { try {
try { return request != null ? request.toString() : "";
return JacksonUtil.toString(request);
} catch (Exception e) {
return request.toString();
}
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to convert request to string", e); log.trace("Failed to convert request to string", e);
return request != null ? request.getClass().getSimpleName() : ""; return request.getClass().getSimpleName();
} }
} }
} }

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java

@ -17,9 +17,11 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import java.util.Optional;
public interface HasContentFormat { public interface HasContentFormat {
ContentFormat getRequestContentFormat(); Optional<ContentFormat> getRequestContentFormat();
default ContentFormat getResponseContentFormat() { default ContentFormat getResponseContentFormat() {
return null; return null;

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java

@ -16,6 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server.downlink; package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.Link;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ExecuteRequest;
@ -25,6 +27,7 @@ import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.CreateResponse;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
@ -45,7 +48,7 @@ public interface LwM2mDownlinkMsgHandler {
void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback); void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback);
void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback); void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> callback, ContentFormat contentFormatComposite);
void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback); void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback);
@ -67,9 +70,10 @@ public interface LwM2mDownlinkMsgHandler {
void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback); void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback);
void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback); void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback, ContentFormat contentFormatComposite);
void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback); void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback);
void sendCreateRequest(LwM2mClient client, TbLwM2MCreateRequest request, DownlinkRequestCallback<CreateRequest, CreateResponse> callback);
} }

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java

@ -16,12 +16,10 @@
package org.thingsboard.server.transport.lwm2m.server.downlink; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL;
@Slf4j @Slf4j
public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelObserveRequest, Integer> { public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelObserveRequest, Integer> {

50
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateRequest.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.CreateResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import java.util.Map;
public class TbLwM2MCreateRequest extends AbstractTbLwM2MTargetedDownlinkRequest<CreateResponse> {
@Getter
private final Object value;
@Getter
private final ContentFormat objectContentFormat;
@Getter
private final Map<String, Object> nodes;;
@Builder
private TbLwM2MCreateRequest(String versionedId, long timeout, Object value, ContentFormat objectContentFormat, Map<String, Object> nodes) {
super(versionedId, timeout);
this.value = value;
this.objectContentFormat = objectContentFormat;
this.nodes = nodes;
}
@Override
public LwM2mOperationType getType() {
return LwM2mOperationType.CREATE;
}
}

36
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCreateResponseCallback.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.response.CreateResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MCreateResponseCallback extends TbLwM2MUplinkTargetedCallback<CreateRequest, CreateResponse> {
public TbLwM2MCreateResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(handler, logService, client, targetId);
}
@Override
public void onSuccess(CreateRequest request, CreateResponse response) {
super.onSuccess(request, response);
handler.onCreateResponseOk(client, versionedId, request);
}
}

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java

@ -19,7 +19,6 @@ import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback<DeleteRequest, DeleteResponse> { public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback<DeleteRequest, DeleteResponse> {

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java

@ -17,9 +17,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback<DiscoverRequest, DiscoverResponse> { public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback<DiscoverRequest, DiscoverResponse> {

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java

@ -17,9 +17,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback<ExecuteRequest, ExecuteResponse> { public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback<ExecuteRequest, ExecuteResponse> {

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java

@ -18,9 +18,9 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ObserveResponse;
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.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@Slf4j @Slf4j
public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback<ObserveRequest, ObserveResponse> { public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback<ObserveRequest, ObserveResponse> {

13
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java

@ -16,20 +16,20 @@
package org.thingsboard.server.transport.lwm2m.server.downlink; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ObserveResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import java.util.Optional;
public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ObserveResponse> implements HasContentFormat { public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ObserveResponse> implements HasContentFormat {
@Getter private final Optional<ContentFormat> requestContentFormat;
private final ContentFormat requestContentFormat;
@Builder @Builder
private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat requestContentFormat) {
super(versionedId, timeout); super(versionedId, timeout);
this.requestContentFormat = requestContentFormat; this.requestContentFormat = Optional.ofNullable(requestContentFormat);
} }
@Override @Override
@ -38,5 +38,8 @@ public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkReques
} }
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormat;
}
} }

13
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java

@ -16,20 +16,20 @@
package org.thingsboard.server.transport.lwm2m.server.downlink; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import java.util.Optional;
public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> implements HasContentFormat { public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> implements HasContentFormat {
@Getter private final Optional<ContentFormat> requestContentFormat;
private final ContentFormat requestContentFormat;
@Builder @Builder
private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat requestContentFormat) { private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat requestContentFormat) {
super(versionedId, timeout); super(versionedId, timeout);
this.requestContentFormat = requestContentFormat; this.requestContentFormat = Optional.ofNullable(requestContentFormat);
} }
@Override @Override
@ -38,5 +38,8 @@ public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<R
} }
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormat;
}
} }

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java

@ -16,6 +16,9 @@
package org.thingsboard.server.transport.lwm2m.server.downlink; package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.util.Hex;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
@ -45,8 +48,28 @@ public abstract class TbLwM2MTargetedCallback<R, T> extends AbstractTbLwM2MReque
public void onSuccess(R request, T response) { public void onSuccess(R request, T response) {
//TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])") //TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")
String requestName = request.getClass().getSimpleName(); String requestName = request.getClass().getSimpleName();
log.trace("[{}] {} [{}] successful: {}", client.getEndpoint(), requestName, versionedId != null ? versionedId : versionedIds, response); String responseStr = response.toString();
logService.log(client, String.format("[%s]: %s [%s] successful. Result: [%s]", LOG_LWM2M_INFO, requestName, versionedId != null ? versionedId : Arrays.toString(versionedIds), response)); if (response instanceof ReadResponse) {
responseStr = responseToString ((ReadResponse) response);
}
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) {
if (response.getContent() instanceof LwM2mSingleResource) {
if (((LwM2mSingleResource) response.getContent()).getType().name().equals("OPAQUE")) {
if (((byte[])((LwM2mSingleResource) response.getContent()).getValue()).length > 0) {
int len = ((byte[])((LwM2mSingleResource) response.getContent()).getValue()).length;
String valueReplace = len + "Bytes";
String valueStr = Hex.encodeHexString((byte[]) (((LwM2mSingleResource) response.getContent()).getValue()));
return response.toString().replace(valueReplace, valueStr);
}
}
return response.toString();
}
else {
return response.toString();
}
}
} }

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java

@ -19,7 +19,6 @@ import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback<WriteAttributesRequest, WriteAttributesResponse> { public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback<WriteAttributesRequest, WriteAttributesResponse> {

13
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/composite/TbLwM2MReadCompositeRequest.java

@ -22,10 +22,12 @@ import org.eclipse.leshan.core.response.ReadCompositeResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat; import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat;
import java.util.Optional;
public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest<ReadCompositeResponse> implements HasContentFormat { public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlinkCompositeRequest<ReadCompositeResponse> implements HasContentFormat {
@Getter
private final ContentFormat requestContentFormat; private final Optional<ContentFormat> requestContentFormatOpt;
@Getter @Getter
private final ContentFormat responseContentFormat; private final ContentFormat responseContentFormat;
@ -33,7 +35,7 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink
@Builder @Builder
private TbLwM2MReadCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) { private TbLwM2MReadCompositeRequest(String [] versionedIds, long timeout, ContentFormat requestContentFormat, ContentFormat responseContentFormat) {
super(versionedIds, timeout); super(versionedIds, timeout);
this.requestContentFormat = requestContentFormat; this.requestContentFormatOpt = Optional.ofNullable(requestContentFormat);
this.responseContentFormat = responseContentFormat; this.responseContentFormat = responseContentFormat;
} }
@ -41,4 +43,9 @@ public class TbLwM2MReadCompositeRequest extends AbstractTbLwM2MTargetedDownlink
public LwM2mOperationType getType() { public LwM2mOperationType getType() {
return LwM2mOperationType.READ_COMPOSITE; return LwM2mOperationType.READ_COMPOSITE;
} }
@Override
public Optional<ContentFormat> getRequestContentFormat() {
return this.requestContentFormatOpt;
}
} }

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java

@ -21,7 +21,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java

@ -180,10 +180,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
} }
var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings(); var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings();
onFirmwareStrategyUpdate(client, clientSettings); onFirmwareStrategyUpdate(client, clientSettings);
onCurrentSoftwareStrategyUpdate(client, clientSettings); onCurrentSoftwareStrategyUpdate(client, clientSettings);
if (!attributesToFetch.isEmpty()) { if (!attributesToFetch.isEmpty()) {
var future = attributesService.getSharedAttributes(client, attributesToFetch); var future = attributesService.getSharedAttributes(client, attributesToFetch);
DonAsynchron.withCallback(future, attrs -> { DonAsynchron.withCallback(future, attrs -> {

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java

@ -20,12 +20,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

103
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -18,6 +18,9 @@ package org.thingsboard.server.transport.lwm2m.server.rpc;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.ResponseCode; 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.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -35,6 +38,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllOb
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCreateRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCreateResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest;
@ -61,11 +66,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.rpc.composite.RpcWriteCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertMultiResourceValuesFromRpcBody;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
@Slf4j @Slf4j
@Service @Service
@ -95,7 +103,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
} }
try { try {
if (operationType.isHasObjectId()) { if (operationType.isHasObjectId()) {
String objectId = getIdFromParameters(client, rpcRequest); LwM2MRpcRequestHeader header = JacksonUtil.fromString(rpcRequest.getParams(), LwM2MRpcRequestHeader.class);
String objectId = getIdFromParameters(client, header);
ContentFormat contentFormat = null;
if (StringUtils.isNotEmpty(header.getContentFormat())) {
contentFormat = ContentFormat.fromName(header.getContentFormat());
}
switch (operationType) { switch (operationType) {
case READ: case READ:
sendReadRequest(client, rpcRequest, objectId); sendReadRequest(client, rpcRequest, objectId);
@ -124,17 +137,21 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
case WRITE_REPLACE: case WRITE_REPLACE:
sendWriteReplaceRequest(client, rpcRequest, objectId); sendWriteReplaceRequest(client, rpcRequest, objectId);
break; break;
case CREATE:
sendCreateRequest(client, rpcRequest, objectId);
break;
default: default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
} }
} else if (operationType.isComposite()) { } else if (operationType.isComposite()) {
if (clientContext.isComposite(client)) { ContentFormat contentFormatComposite = clientContext.getContentFormatComposite(client);
if (contentFormatComposite != null) {
switch (operationType) { switch (operationType) {
case READ_COMPOSITE: case READ_COMPOSITE:
sendReadCompositeRequest(client, rpcRequest); sendReadCompositeRequest(client, rpcRequest, contentFormatComposite);
break; break;
case WRITE_COMPOSITE: case WRITE_COMPOSITE:
sendWriteCompositeRequest(client, rpcRequest); sendWriteCompositeRequest(client, rpcRequest, contentFormatComposite);
break; break;
default: default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
@ -173,12 +190,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
downlinkHandler.sendReadRequest(client, request, rpcCallback); downlinkHandler.sendReadRequest(client, request, rpcCallback);
} }
private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) {
String[] versionedIds = getIdsFromParameters(client, requestMsg); String[] versionedIds = getIdsFromParameters(client, requestMsg);
TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build(); TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build();
var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds); var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds);
var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback); var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback); downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback, contentFormatComposite);
} }
private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
@ -231,8 +248,25 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback); downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback);
} }
private void sendCreateRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
RpcCreateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcCreateRequest.class);
TbLwM2MCreateRequest.TbLwM2MCreateRequestBuilder builder = TbLwM2MCreateRequest.builder().versionedId(versionedId);
builder.value(requestBody.getValue()).nodes(requestBody.getNodes()).timeout(clientContext.getRequestTimeout(client));
var mainCallback = new TbLwM2MCreateResponseCallback(uplinkHandler, logService, client, versionedId);
var rpcCallback = new RpcCreateResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendCreateRequest(client, builder.build(), rpcCallback);
}
private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class);
LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(versionedId));
if (path.isResource() && requestBody.getValue() instanceof LinkedHashMap) {
ResourceModel resourceModel = client.getResourceModel(versionedId, this.config.getModelProvider());
if (resourceModel != null && resourceModel.multiple) {
Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) requestBody.getValue(), resourceModel.type, versionedId);
requestBody.setValue(value);
}
}
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId)
.value(requestBody.getValue()) .value(requestBody.getValue())
.timeout(clientContext.getRequestTimeout(client)).build(); .timeout(clientContext.getRequestTimeout(client)).build();
@ -249,11 +283,39 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
* nodes.put("/1/0/2", 100); * nodes.put("/1/0/2", 100);
* nodes.put("/5/0/1", "coap://localhost:5685"); * nodes.put("/5/0/1", "coap://localhost:5685");
*/ */
private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) {
RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class);
var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); Map validNodes = validateNodes(client, rpcWriteCompositeRequest.getNodes());
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); if (validNodes.size() > 0) {
downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); rpcWriteCompositeRequest.setNodes(validNodes);
var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback, contentFormatComposite);
} else {
throw new IllegalArgumentException(String.format("nodes: %s is not validate value", rpcWriteCompositeRequest.getNodes().toString()));
}
}
private Map validateNodes(LwM2mClient client, Map nodes) {
Map newNodes = new LinkedHashMap();
nodes.forEach((key, value) -> {
String versionedId;
try {
// validate key.toString()
new LwM2mPath(fromVersionedIdToObjectId(key.toString()));
versionedId = key.toString();
} catch (Exception e) {
versionedId = clientContext.getObjectIdByKeyNameFromProfile(client, key.toString(), true);
}
// validate value. Must be only primitive, not Json
if (value instanceof LinkedHashMap) {
throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " +
"The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString()));
} else {
newNodes.put(fromVersionedIdToObjectId(versionedId), value);
}
});
return newNodes;
} }
private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
@ -277,13 +339,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback);
} }
private String getIdFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { private String getIdFromParameters(LwM2mClient client, LwM2MRpcRequestHeader header) {
IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class);
String targetId; String targetId;
if (StringUtils.isNotEmpty(requestParams.getKey())) { if (StringUtils.isNotEmpty(header.getKey())) {
targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); targetId = clientContext.getObjectIdByKeyNameFromProfile(client, header.getKey(), false);
} else if (StringUtils.isNotEmpty(requestParams.getId())) { } else if (StringUtils.isNotEmpty(header.getId())) {
targetId = requestParams.getId(); targetId = header.getId();
} else { } else {
throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!"); throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!");
} }
@ -293,14 +354,14 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
private String[] getIdsFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { private String[] getIdsFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) {
RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), RpcReadCompositeRequest.class); RpcReadCompositeRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), RpcReadCompositeRequest.class);
if (requestParams.getKeys() != null && requestParams.getKeys().length > 0) { if (requestParams.getKeys() != null && requestParams.getKeys().length > 0) {
Set targetIds = ConcurrentHashMap.newKeySet(); Set<String> targetIds = new HashSet<>();
for (String key : requestParams.getKeys()) { for (String key : requestParams.getKeys()) {
String targetId = clientContext.getObjectIdByKeyNameFromProfile(client, key); String targetId = clientContext.getObjectIdByKeyNameFromProfile(client, key, true);
if (targetId != null) { if (targetId != null) {
targetIds.add(targetId); targetIds.add(targetId);
} }
} }
return (String[]) targetIds.toArray(String[]::new); return targetIds.toArray(String[]::new);
} else if (requestParams.getIds() != null && requestParams.getIds().length > 0) { } else if (requestParams.getIds() != null && requestParams.getIds().length > 0) {
return requestParams.getIds(); return requestParams.getIds();
} else { } else {

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHeader.java

@ -20,8 +20,9 @@ import lombok.Data;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class IdOrKeyRequest { public class LwM2MRpcRequestHeader {
private String key; private String key;
private String id; private String id;
private String contentFormat;
} }

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java

@ -21,7 +21,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelAllRequest, Integer> { public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelAllRequest, Integer> {

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java

@ -16,19 +16,12 @@
package org.thingsboard.server.transport.lwm2m.server.rpc; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.response.ReadResponse;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
import java.util.Optional;
public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelObserveRequest, Integer> { public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelObserveRequest, Integer> {
public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) {

18
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateRequest.java

@ -13,15 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.bootstrap.secure; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import lombok.Data;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; import lombok.EqualsAndHashCode;
public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter { import java.util.Map;
public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) { @Data
super(store); @EqualsAndHashCode(callSuper = true)
} public class RpcCreateRequest extends LwM2MRpcRequestHeader {
private Object value;
private String contentFormat;
private Map<String, Object> nodes;
} }

38
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCreateResponseCallback.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.request.LwM2mRequest;
import org.eclipse.leshan.core.response.CreateResponse;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import java.util.Optional;
public class RpcCreateResponseCallback<R extends LwM2mRequest<T>, T extends CreateResponse> extends RpcLwM2MDownlinkCallback<R, T> {
public RpcCreateResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) {
super(transportService, client, requestMsg, callback);
}
@Override
protected Optional<String> serializeSuccessfulResponse(T response) {
String value = response.getLocation() != null ? "location=" + response.getLocation() : "";
return Optional.of(value);
}
}

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java

@ -16,14 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server.rpc; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.Link;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java

@ -15,18 +15,13 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.rpc; package org.thingsboard.server.transport.lwm2m.server.rpc;
import org.eclipse.leshan.core.Link;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.response.DiscoverResponse;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import java.util.Optional;
import java.util.Set;
public class RpcLinkSetCallback<R, T> extends RpcDownlinkRequestCallbackProxy<R, T> { public class RpcLinkSetCallback<R, T> extends RpcDownlinkRequestCallbackProxy<R, T> {
public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) { public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<R, T> callback) {

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RpcWriteAttributesRequest extends IdOrKeyRequest { public class RpcWriteAttributesRequest extends LwM2MRpcRequestHeader {
private ObjectAttributes attributes; private ObjectAttributes attributes;

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java

@ -20,7 +20,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RpcWriteReplaceRequest extends IdOrKeyRequest { public class RpcWriteReplaceRequest extends LwM2MRpcRequestHeader {
private Object value; private Object value;

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java

@ -17,11 +17,10 @@ package org.thingsboard.server.transport.lwm2m.server.rpc;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class RpcWriteUpdateRequest extends IdOrKeyRequest { public class RpcWriteUpdateRequest extends LwM2MRpcRequestHeader {
private Object value; private Object value;
private String contentFormat; private String contentFormat;

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadCompositeRequest.java

@ -22,7 +22,7 @@ import lombok.Data;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class RpcReadCompositeRequest { public class RpcReadCompositeRequest {
private String [] keys; private String[] keys;
private String [] ids; private String[] ids;
} }

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java

@ -15,21 +15,13 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.store; package org.thingsboard.server.transport.lwm2m.server.store;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.jetbrains.annotations.Nullable;
import org.nustaq.serialization.FSTConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo;
import java.util.concurrent.locks.Lock;
public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore { public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore {
private static final String OTA_EP = "OTA#EP#"; private static final String OTA_EP = "OTA#EP#";

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java

@ -726,7 +726,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
public synchronized void start() { public synchronized void start() {
if (!started) { if (!started) {
started = true; started = true;
cleanerTask = schedExecutor.scheduleAtFixedRate(new TbLwM2mRedisRegistrationStore.Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS); cleanerTask = schedExecutor.scheduleAtFixedRate(new Cleaner(), cleanPeriod, cleanPeriod, TimeUnit.SECONDS);
} }
} }

60
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java

@ -23,15 +23,20 @@ import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.WriteRequest.Mode;
import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
@ -69,8 +74,6 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest;
@ -217,7 +220,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
+ registration.getLwM2mVersion() + " and modes: " + registration.getQueueMode() + ", " + registration.getBindingMode()); + registration.getLwM2mVersion() + " and modes: " + registration.getQueueMode() + ", " + registration.getBindingMode());
sessionManager.register(lwM2MClient.getSession()); sessionManager.register(lwM2MClient.getSession());
this.initClientTelemetry(lwM2MClient); this.initClientTelemetry(lwM2MClient);
this.initAttributes(lwM2MClient); this.initAttributes(lwM2MClient, true);
otaService.init(lwM2MClient); otaService.init(lwM2MClient);
lwM2MClient.getRetryAttempts().set(0); lwM2MClient.getRetryAttempts().set(0);
} catch (LwM2MClientStateException stateException) { } catch (LwM2MClientStateException stateException) {
@ -319,7 +322,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path); this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path);
} else if (response.getContent() instanceof LwM2mResource) { } else if (response.getContent() instanceof LwM2mResource) {
LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); LwM2mResource lwM2mResource = (LwM2mResource) response.getContent();
this.updateResourcesValue(lwM2MClient, lwM2mResource, path); this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE);
} }
} }
if (clientContext.awake(lwM2MClient)) { if (clientContext.awake(lwM2MClient)) {
@ -342,7 +345,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
} else if (v instanceof LwM2mObjectInstance) { } else if (v instanceof LwM2mObjectInstance) {
this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString()); this.updateObjectInstanceResourceValue(lwM2MClient, (LwM2mObjectInstance) v, k.toString());
} else if (v instanceof LwM2mResource) { } else if (v instanceof LwM2mResource) {
this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString()); this.updateResourcesValue(lwM2MClient, (LwM2mResource) v, k.toString(), Mode.UPDATE);
} }
} }
}); });
@ -482,11 +485,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
} }
private void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId) {
TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(targetId).timeout(clientContext.getRequestTimeout(lwM2MClient)).build();
defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(logService, lwM2MClient, targetId));
}
private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) {
sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)); sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId));
} }
@ -527,7 +525,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer));
lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> {
String pathRez = pathIds.toString() + "/" + resourceId; String pathRez = pathIds.toString() + "/" + resourceId;
this.updateResourcesValue(client, resource, pathRez); this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE);
}); });
} }
@ -537,14 +535,14 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
* #2 Update new Resources (replace old Resource Value on new Resource Value) * #2 Update new Resources (replace old Resource Value on new Resource Value)
* #3 If fr_update -> UpdateFirmware * #3 If fr_update -> UpdateFirmware
* #4 updateAttrTelemetry * #4 updateAttrTelemetry
* * @param lwM2MClient - Registration LwM2M Client
* @param lwM2MClient - Registration LwM2M Client
* @param lwM2mResource - LwM2mSingleResource response.getContent() * @param lwM2mResource - LwM2mSingleResource response.getContent()
* @param path - resource * @param path - resource
* @param mode - Replace, Update
*/ */
private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path) { private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode) {
Registration registration = lwM2MClient.getRegistration(); Registration registration = lwM2MClient.getRegistration();
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider(), mode)) {
if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) {
otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { } else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) {
@ -570,7 +568,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
this.updateAttrTelemetry(registration, Collections.singleton(path)); this.updateAttrTelemetry(registration, Collections.singleton(path));
} else { } else {
log.error("Fail update Resource [{}]", lwM2mResource); log.error("Fail update path [{}] Resource [{}]", path, lwM2mResource);
} }
} }
@ -686,7 +684,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
Gson gson = new GsonBuilder().create(); Gson gson = new GsonBuilder().create();
ResourceModel.Type finalCurrentType = currentType; ResourceModel.Type finalCurrentType = currentType;
resourceValue.getInstances().forEach((k, v) -> { resourceValue.getInstances().forEach((k, v) -> {
Object val = this.converter.convertValue(v, finalCurrentType, expectedType, Object val = this.converter.convertValue(v.getValue(), finalCurrentType, expectedType,
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)));
JsonElement element = gson.toJsonTree(val, val.getClass()); JsonElement element = gson.toJsonTree(val, val.getClass());
((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element);
@ -714,21 +712,37 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
@Override @Override
public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) {
if (request.getNode() instanceof LwM2mResource) { if (request.getNode() instanceof LwM2mResource) {
this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path); this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE);
clientContext.update(client); clientContext.update(client);
} else if (request.getNode() instanceof LwM2mObjectInstance) { } else if (request.getNode() instanceof LwM2mObjectInstance) {
((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> {
this.updateResourcesValue(client, resource, path + "/" + resId); this.updateResourcesValue(client, resource, path + "/" + resId, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE);
}); });
clientContext.update(client); 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 ->
instance.getResources()
);
// this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path, request.isReplaceRequest() ? Mode.REPLACE : Mode.UPDATE);
clientContext.update(client);
}
}
@Override @Override
public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) { public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) {
log.trace("ReadCompositeResponse: [{}]", request.getNodes()); log.trace("ReadCompositeResponse: [{}]", request.getNodes());
request.getNodes().forEach((k, v) -> { request.getNodes().forEach((k, v) -> {
this.updateResourcesValue(client, (LwM2mResource) v, k.toString()); if (v instanceof LwM2mSingleResource) {
this.updateResourcesValue(client, (LwM2mResource) v, k.toString(), Mode.REPLACE);
} else {
LwM2mResourceInstance resourceInstance = (LwM2mResourceInstance) v;
LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(v.getId(), resourceInstance.getType(), resourceInstance);
this.updateResourcesValue(client, multipleResource, k.toString(), Mode.REPLACE);
}
}); });
} }
@ -928,14 +942,14 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
* *
* @param lwM2MClient - LwM2M Client * @param lwM2MClient - LwM2M Client
*/ */
public void initAttributes(LwM2mClient lwM2MClient) { public void initAttributes(LwM2mClient lwM2MClient, boolean logFailedUpdateOfNonChangedValue) {
Map<String, String> keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); Map<String, String> keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient);
if (!keyNamesMap.isEmpty()) { if (!keyNamesMap.isEmpty()) {
Set<String> keysToFetch = new HashSet<>(keyNamesMap.values()); Set<String> keysToFetch = new HashSet<>(keyNamesMap.values());
keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS);
keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS);
DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch), DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch),
v -> attributesService.onAttributesUpdate(lwM2MClient, v), v -> attributesService.onAttributesUpdate(lwM2MClient, v, logFailedUpdateOfNonChangedValue),
t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t), t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t),
executor); executor);
} }
@ -950,7 +964,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
return this.config; return this.config;
} }
private void reportActivitySubscription(TransportProtos.SessionInfoProto sessionInfo) { private void reportActivitySubscription(SessionInfoProto sessionInfo) {
transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder()
.setAttributeSubscription(true) .setAttributeSubscription(true)
.setRpcSubscription(true) .setRpcSubscription(true)

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.server.uplink; package org.thingsboard.server.transport.lwm2m.server.uplink;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CreateRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse;
@ -56,6 +57,8 @@ public interface LwM2mUplinkMsgHandler {
void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request); void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request);
void onCreateResponseOk(LwM2mClient client, String path, CreateRequest request);
void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request); void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request);
void onToTransportUpdateCredentials(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); void onToTransportUpdateCredentials(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToTransportUpdateCredentialsProto updateCredentials);

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.utils; package org.thingsboard.server.transport.lwm2m.utils;
import com.google.api.client.util.Base64;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
@ -166,13 +167,16 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
case OPAQUE: case OPAQUE:
if (currentType == Type.STRING) { if (currentType == Type.STRING) {
/** let's assume we received an hexadecimal string */ /** let's assume we received an hexadecimal string */
log.debug("Trying to convert hexadecimal string [{}] to byte array", value); log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value);
// TODO check if we shouldn't instead assume that the string contains Base64 encoded data
try { try {
return Hex.decodeHex(((String)value).toCharArray()); return Hex.decodeHex(((String)value).toCharArray());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new CodecException("Unable to convert hexastring [%s] to byte array for resource %s", value, try {
resourcePath); return Base64.decodeBase64(((String) value).getBytes());
} catch (IllegalArgumentException ea) {
throw new CodecException("Unable to convert hexastring or base64 [%s] to byte array for resource %s",
value, resourcePath);
}
} }
} }
break; break;

2
pom.xml

@ -71,7 +71,7 @@
<jackson-core.version>2.12.1</jackson-core.version> <jackson-core.version>2.12.1</jackson-core.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version> <json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>2.6.1</californium.version> <californium.version>2.6.1</californium.version>
<leshan.version>2.0.0-M3</leshan.version> <leshan.version>2.0.0-M4</leshan.version>
<gson.version>2.6.2</gson.version> <gson.version>2.6.2</gson.version>
<freemarker.version>2.3.30</freemarker.version> <freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>

Loading…
Cancel
Save