Browse Source

Merge branch 'master' into lwm2m_rpc_fw_sw

# Conflicts:
#	common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
pull/4607/head
nickAS21 5 years ago
parent
commit
5b26bddeab
  1. 4
      application/pom.xml
  2. 2
      application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
  3. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 19
      application/src/main/java/org/thingsboard/server/controller/FirmwareController.java
  5. 5
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  6. 3
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  7. 46
      application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java
  10. 1
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  13. 11
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java
  14. 13
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  15. 8
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java
  16. 10
      application/src/main/java/org/thingsboard/server/service/queue/processing/IdMsgPair.java
  17. 6
      application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java
  18. 13
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  19. 1
      application/src/main/resources/thingsboard.yml
  20. 3
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  21. 3
      application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
  22. 222
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  23. 163
      application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java
  24. 207
      application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java
  25. 259
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  26. 199
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  27. 3
      application/src/test/resources/application-test.properties
  28. 2
      application/src/test/resources/logback.xml
  29. 405
      application/src/test/resources/lwm2m/0.xml
  30. 360
      application/src/test/resources/lwm2m/1.xml
  31. 123
      application/src/test/resources/lwm2m/2.xml
  32. 331
      application/src/test/resources/lwm2m/3.xml
  33. BIN
      application/src/test/resources/lwm2m/credentials/clientKeyStore.jks
  34. BIN
      application/src/test/resources/lwm2m/credentials/serverKeyStore.jks
  35. 1
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java
  36. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java
  37. 3
      common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java
  38. 27
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java
  39. 34
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java
  40. 36
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java
  41. 20
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java
  42. 24
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java
  43. 30
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java
  44. 24
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java
  45. 30
      common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java
  46. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java
  47. 26
      common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java
  48. 6
      common/queue/pom.xml
  49. 4
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java
  50. 4
      common/transport/lwm2m/pom.xml
  51. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  52. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java
  53. 42
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  54. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java
  55. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  56. 15
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java
  57. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java
  58. 58
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java
  59. 119
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  60. 67
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java
  61. 195
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java
  62. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java
  63. 26
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java
  64. 47
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java
  65. 89
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  66. 62
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  67. 19
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  68. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java
  69. 35
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  70. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  71. 16
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  72. 80
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java
  73. 40
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java
  74. 66
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java
  75. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java
  76. 373
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  77. 64
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java
  78. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java
  79. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java
  80. 3
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  81. 4
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  82. 4
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  83. 9
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
  84. 5
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  85. 63
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  86. 35
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  87. 50
      common/transport/transport-api/src/test/java/JsonConverterTest.java
  88. 51
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  89. 61
      dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java
  90. 4
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java
  91. 4
      dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java
  92. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java
  93. 13
      dao/src/main/resources/sql/schema-ts-psql.sql
  94. 3
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
  95. 5
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java
  96. 3
      dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java
  97. 14
      pom.xml
  98. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
  99. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  100. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java

4
application/pom.xml

@ -145,6 +145,10 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>

2
application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql

@ -43,7 +43,7 @@ BEGIN
into max_customer_ttl;
max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl);
if max_ttl IS NOT NULL AND max_ttl > 0 THEN
date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - (max_ttl / 1000));
date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl);
partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date);
RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date;
IF partition_by_max_ttl_date IS NOT NULL THEN

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -160,8 +160,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
public abstract class BaseController {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
protected static final String HOME_DASHBOARD = "homeDashboardId";

19
application/src/main/java/org/thingsboard/server/controller/FirmwareController.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.controller;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ByteArrayResource;
@ -35,6 +34,7 @@ import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
@ -53,6 +53,7 @@ import java.nio.ByteBuffer;
public class FirmwareController extends BaseController {
public static final String FIRMWARE_ID = "firmwareId";
public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm";
@PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')")
@RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET)
@ -125,9 +126,10 @@ public class FirmwareController extends BaseController {
@ResponseBody
public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId,
@RequestParam(required = false) String checksum,
@RequestParam(required = false) String checksumAlgorithm,
@RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr,
@RequestBody MultipartFile file) throws ThingsboardException {
checkParameter(FIRMWARE_ID, strFirmwareId);
checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr);
try {
FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ);
@ -141,18 +143,19 @@ public class FirmwareController extends BaseController {
firmware.setVersion(info.getVersion());
firmware.setAdditionalInfo(info.getAdditionalInfo());
byte[] data = file.getBytes();
if (StringUtils.isEmpty(checksumAlgorithm)) {
checksumAlgorithm = "sha256";
checksum = Hashing.sha256().hashBytes(data).toString();
ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase());
byte[] bytes = file.getBytes();
if (StringUtils.isEmpty(checksum)) {
checksum = firmwareService.generateChecksum(checksumAlgorithm, ByteBuffer.wrap(bytes));
}
firmware.setChecksumAlgorithm(checksumAlgorithm);
firmware.setChecksum(checksum);
firmware.setFileName(file.getOriginalFilename());
firmware.setContentType(file.getContentType());
firmware.setData(ByteBuffer.wrap(data));
firmware.setDataSize((long) data.length);
firmware.setData(ByteBuffer.wrap(bytes));
firmware.setDataSize((long) bytes.length);
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
logEntityAction(savedFirmware.getId(), savedFirmware, null, ActionType.UPDATED, null);
return savedFirmware;

5
application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)
@ResponseBody
public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode,
public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode,
@PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException {
checkNotNull(strSecurityMode);
try {
SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode);
return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer);
} catch (Exception e) {
throw handleException(e);

3
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -193,6 +193,9 @@ public class ThingsboardInstallService {
databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
case "3.2.2":
log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("3.2.2");
}
databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
dataUpdateService.updateData("3.2.2");

46
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java

@ -65,7 +65,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReentrantLock;
@Service
@ -78,8 +77,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
private static final String DEVICE_PROVISION_STATE = "provisionState";
private static final String PROVISIONED_STATE = "provisioned";
private final ReentrantLock deviceCreationLock = new ReentrantLock();
@Autowired
DeviceDao deviceDao;
@ -177,12 +174,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
}
private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
deviceCreationLock.lock();
try {
return processCreateDevice(provisionRequest, profile);
} finally {
deviceCreationLock.unlock();
}
return processCreateDevice(provisionRequest, profile);
}
private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
@ -191,28 +183,26 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
}
private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
try {
if (device == null) {
if (StringUtils.isEmpty(provisionRequest.getDeviceName())) {
String newDeviceName = RandomStringUtils.randomAlphanumeric(20);
log.info("Device name not found in provision request. Generated name is: {}", newDeviceName);
provisionRequest.setDeviceName(newDeviceName);
}
Device savedDevice = deviceService.saveDevice(provisionRequest, profile);
deviceStateService.onDeviceAdded(savedDevice);
saveProvisionStateAttribute(savedDevice).get();
pushDeviceCreatedEventToRuleEngine(savedDevice);
notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS);
} else {
log.warn("[{}] The device is already provisioned!", device.getName());
if (StringUtils.isEmpty(provisionRequest.getDeviceName())) {
String newDeviceName = RandomStringUtils.randomAlphanumeric(20);
log.info("Device name not found in provision request. Generated name is: {}", newDeviceName);
provisionRequest.setDeviceName(newDeviceName);
}
Device savedDevice = deviceService.saveDevice(provisionRequest, profile);
deviceStateService.onDeviceAdded(savedDevice);
saveProvisionStateAttribute(savedDevice).get();
pushDeviceCreatedEventToRuleEngine(savedDevice);
notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS);
} catch (Exception e) {
log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e);
Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
if (device != null) {
notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
}
} catch (InterruptedException | ExecutionException e) {
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
}
}

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java

@ -172,7 +172,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
@Override
public void onEdgeEvent(EdgeId edgeId) {
log.trace("[{}] onEdgeEvent", edgeId.getId());
if (!sessionNewEvents.get(edgeId)) {
if (Boolean.FALSE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}] set session new events flag to true", edgeId.getId());
sessionNewEvents.put(edgeId, true);
}
@ -204,7 +204,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
if (sessions.containsKey(edgeId)) {
ScheduledFuture<?> schedule = scheduler.schedule(() -> {
try {
if (sessionNewEvents.get(edgeId)) {
if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}] Set session new events flag to false", edgeId.getId());
sessionNewEvents.put(edgeId, false);
session.processEdgeEvents();

2
application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java

@ -297,7 +297,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum())));
telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {

1
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -51,6 +51,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
case "2.5.0":
case "3.1.1":
case "3.2.1":
case "3.2.2":
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

6
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -209,6 +209,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);");
}
break;
case "3.2.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Load Drop Partitions functions ...");
loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3");
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

2
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -184,6 +184,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1");
}
break;
case "3.2.2":
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

11
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.lwm2m;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.util.Hex;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository {
* @param bootstrapServer
* @return ServerSecurityConfig more value is default: Important - port, host, publicKey
*/
public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) {
LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase());
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode);
public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) {
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode);
result.setBootstrapServerIs(bootstrapServer);
return result;
}
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) {
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) {
ServerSecurityConfig bsServ = new ServerSecurityConfig();
bsServ.setServerId(serverConfig.getId());
switch (mode) {
switch (securityMode) {
case NO_SEC:
bsServ.setHost(serverConfig.getHost());
bsServ.setPort(serverConfig.getPort());

13
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -63,6 +63,7 @@ import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.firmware.FirmwareStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
@ -74,6 +75,7 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -198,14 +200,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
if (msgs.isEmpty()) {
continue;
}
ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect(
Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
List<IdMsgPair<ToCoreMsg>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList());
ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = orderedMsgList.stream().collect(
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>(
processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
PendingMsgHolder pendingMsgHolder = new PendingMsgHolder();
Future<?> packSubmitFuture = consumersExecutor.submit(() -> {
pendingMap.forEach((id, msg) -> {
orderedMsgList.forEach((element) -> {
UUID id = element.getUuid();
TbProtoQueueMsg<ToCoreMsg> msg = element.getMsg();
log.trace("[{}] Creating main callback for message: {}", id, msg.getValue());
TbCallback callback = new TbPackCallback<>(id, ctx);
try {
@ -223,7 +228,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreMsg.hasEdgeNotificationMsg()) {
log.trace("[{}] Forwarding message to edge service {}", id, toCoreMsg.getEdgeNotificationMsg());
forwardToEdgeNotificationService(toCoreMsg.getEdgeNotificationMsg(), callback);
} else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
} else if (!toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray());
if (actorMsg.isPresent()) {
TbActorMsg tbActorMsg = actorMsg.get();

8
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java

@ -27,7 +27,7 @@ import java.util.stream.Collectors;
public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy {
protected final String queueName;
protected List<IdMsgPair> orderedMsgList;
protected List<IdMsgPair<TransportProtos.ToRuleEngineMsg>> orderedMsgList;
private volatile boolean stopped;
public AbstractTbRuleEngineSubmitStrategy(String queueName) {
@ -38,7 +38,7 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine
@Override
public void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs) {
orderedMsgList = msgs.stream().map(msg -> new IdMsgPair(UUID.randomUUID(), msg)).collect(Collectors.toList());
orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList());
}
@Override
@ -48,8 +48,8 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine
@Override
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) {
List<IdMsgPair> newOrderedMsgList = new ArrayList<>(reprocessMap.size());
for (IdMsgPair pair : orderedMsgList) {
List<IdMsgPair<TransportProtos.ToRuleEngineMsg>> newOrderedMsgList = new ArrayList<>(reprocessMap.size());
for (IdMsgPair<TransportProtos.ToRuleEngineMsg> pair : orderedMsgList) {
if (reprocessMap.containsKey(pair.uuid)) {
newOrderedMsgList.add(pair);
}

10
application/src/main/java/org/thingsboard/server/service/queue/processing/IdMsgPair.java

@ -15,16 +15,18 @@
*/
package org.thingsboard.server.service.queue.processing;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import lombok.Getter;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import java.util.UUID;
public class IdMsgPair {
public class IdMsgPair<T extends com.google.protobuf.GeneratedMessageV3> {
@Getter
final UUID uuid;
final TbProtoQueueMsg<ToRuleEngineMsg> msg;
@Getter
final TbProtoQueueMsg<T> msg;
public IdMsgPair(UUID uuid, TbProtoQueueMsg<ToRuleEngineMsg> msg) {
public IdMsgPair(UUID uuid, TbProtoQueueMsg<T> msg) {
this.uuid = uuid;
this.msg = msg;
}

6
application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java

@ -33,7 +33,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer;
private volatile ConcurrentMap<UUID, EntityId> msgToEntityIdMap = new ConcurrentHashMap<>();
private volatile ConcurrentMap<EntityId, Queue<IdMsgPair>> entityIdToListMap = new ConcurrentHashMap<>();
private volatile ConcurrentMap<EntityId, Queue<IdMsgPair<TransportProtos.ToRuleEngineMsg>>> entityIdToListMap = new ConcurrentHashMap<>();
public SequentialByEntityIdTbRuleEngineSubmitStrategy(String queueName) {
super(queueName);
@ -66,7 +66,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
protected void doOnSuccess(UUID id) {
EntityId entityId = msgToEntityIdMap.get(id);
if (entityId != null) {
Queue<IdMsgPair> queue = entityIdToListMap.get(entityId);
Queue<IdMsgPair<TransportProtos.ToRuleEngineMsg>> queue = entityIdToListMap.get(entityId);
if (queue != null) {
IdMsgPair next = null;
synchronized (queue) {
@ -86,7 +86,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
private void initMaps() {
msgToEntityIdMap.clear();
entityIdToListMap.clear();
for (IdMsgPair pair : orderedMsgList) {
for (IdMsgPair<TransportProtos.ToRuleEngineMsg> pair : orderedMsgList) {
EntityId entityId = getEntityId(pair.msg.getValue());
if (entityId != null) {
msgToEntityIdMap.put(pair.uuid, entityId);

13
application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java

@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
@ -39,6 +40,8 @@ import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@ -69,6 +72,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
private final ExecutorService callbackExecutor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("js-executor-remote-callback"));
public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) {
super(apiUsageStateService, apiUsageClient);
@ -139,7 +144,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
}
queueFailedMsgs.incrementAndGet();
}
}, MoreExecutors.directExecutor());
}, callbackExecutor);
return Futures.transform(future, response -> {
JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse();
UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
@ -151,7 +156,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails());
throw new RuntimeException(compilationResult.getErrorDetails());
}
}, MoreExecutors.directExecutor());
}, callbackExecutor);
}
@Override
@ -194,7 +199,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
}
queueFailedMsgs.incrementAndGet();
}
}, MoreExecutors.directExecutor());
}, callbackExecutor);
return Futures.transform(future, response -> {
JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse();
if (invokeResult.getSuccess()) {
@ -204,7 +209,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
throw new RuntimeException(invokeResult.getErrorDetails());
}
}, MoreExecutors.directExecutor());
}, callbackExecutor);
}
@Override

1
application/src/main/resources/thingsboard.yml

@ -643,6 +643,7 @@ transport:
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
# Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap:
enable: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}"

3
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
@ -120,7 +121,7 @@ public abstract class AbstractWebTest {
protected String refreshToken;
protected String username;
private TenantId tenantId;
protected TenantId tenantId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;

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

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

222
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -0,0 +1,222 @@
/**
* 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 com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.io.IOUtils;
import org.eclipse.leshan.core.util.Hex;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.thingsboard.common.util.JacksonUtil;
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.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.controller.AbstractWebsocketTest;
import org.thingsboard.server.controller.TbTestWebSocketClient;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@DaoSqlTest
public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
protected DeviceProfile deviceProfile;
protected ScheduledExecutorService executor;
protected TbTestWebSocketClient wsClient;
protected final PublicKey clientPublicKey; // client public key used for RPK
protected final PrivateKey clientPrivateKey; // client private key used for RPK
protected final PublicKey serverPublicKey; // server public key used for RPK
protected final PrivateKey serverPrivateKey; // server private key used for RPK
// client private key used for X509
protected final PrivateKey clientPrivateKeyFromCert;
// server private key used for X509
protected final PrivateKey serverPrivateKeyFromCert;
// client certificate signed by rootCA with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509Cert;
// client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test)
protected final X509Certificate clientX509CertWithBadCN;
// client certificate self-signed with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509CertSelfSigned;
// client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test)
protected final X509Certificate clientX509CertNotTrusted;
// server certificate signed by rootCA
protected final X509Certificate serverX509Cert;
// self-signed server certificate
protected final X509Certificate serverX509CertSelfSigned;
// rootCA used by the server
protected final X509Certificate rootCAX509Cert;
// certificates trustedby the server (should contain rootCA)
protected final Certificate[] trustedCertificates = new Certificate[1];
public AbstractLwM2MIntegrationTest() {
// create client credentials
try {
// Get point values
byte[] publicX = Hex
.decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray());
byte[] publicY = Hex
.decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray());
byte[] privateS = Hex
.decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray());
// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
// Get keys
clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
// Get certificates from key store
char[] clientKeyStorePwd = "client".toCharArray();
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) {
clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
}
clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd);
clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client");
clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn");
clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed");
clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted");
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
// create server credentials
try {
// Get point values
byte[] publicX = Hex
.decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray());
byte[] publicY = Hex
.decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray());
byte[] privateS = Hex
.decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray());
// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
// Get keys
serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
// Get certificates from key store
char[] serverKeyStorePwd = "server".toCharArray();
KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) {
serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd);
}
serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd);
rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA");
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server");
serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed");
trustedCertificates[0] = rootCAX509Cert;
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
@Before
public void beforeTest() throws Exception {
executor = Executors.newScheduledThreadPool(10);
loginTenantAdmin();
String[] resources = new String[]{"1.xml", "2.xml", "3.xml"};
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();
}
protected void createDeviceProfile(String transportConfiguration) throws Exception {
deviceProfile = new DeviceProfile();
deviceProfile.setName("LwM2M");
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);
}
@After
public void after() {
executor.shutdownNow();
wsClient.close();
}
}

163
application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java

@ -0,0 +1,163 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
import java.util.Collections;
import java.util.List;
import static org.eclipse.leshan.client.object.Security.noSec;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected final String TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"/3_1.0/0/9\": \"batteryLevel\"\n" +
" },\n" +
" \"observe\": [],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"/3_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrap\": {\n" +
" \"servers\": {\n" +
" \"binding\": \"UQ\",\n" +
" \"shortId\": 123,\n" +
" \"lifetime\": 300,\n" +
" \"notifIfDisabled\": true,\n" +
" \"defaultMinPeriod\": 1\n" +
" },\n" +
" \"lwm2mServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5685,\n" +
" \"serverId\": 123,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"bootstrapServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5687,\n" +
" \"serverId\": 111,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" },\n" +
" \"clientLwM2mSettings\": {\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
private final int port = 5685;
private final Security security = noSec("coap://localhost:" + port, 123);
private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port));
@NotNull
private Device createDevice(String deviceAEndpoint) throws Exception {
Device device = new Device();
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 noSecCredentials = new LwM2MCredentials();
NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
clientCredentials.setEndpoint(deviceAEndpoint);
noSecCredentials.setClient(clientCredentials);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials));
doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
return device;
}
@Test
public void testConnectAndObserveTelemetry() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
String deviceAEndpoint = "deviceAEndpoint";
Device device = createDevice(deviceAEndpoint);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint);
client.init(security, coapConfig);
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
client.destroy();
}
}

207
application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java

@ -0,0 +1,207 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import java.util.Collections;
import java.util.List;
import static org.eclipse.leshan.client.object.Security.x509;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected final String TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"/3_1.0/0/9\": \"batteryLevel\"\n" +
" },\n" +
" \"observe\": [],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"/3_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrap\": {\n" +
" \"servers\": {\n" +
" \"binding\": \"UQ\",\n" +
" \"shortId\": 123,\n" +
" \"lifetime\": 300,\n" +
" \"notifIfDisabled\": true,\n" +
" \"defaultMinPeriod\": 1\n" +
" },\n" +
" \"lwm2mServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5686,\n" +
" \"serverId\": 123,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"bootstrapServer\": {\n" +
" \"host\": \"localhost\",\n" +
" \"port\": 5687,\n" +
" \"serverId\": 111,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"serverPublicKey\": \"\",\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" },\n" +
" \"clientLwM2mSettings\": {\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
private final int port = 5686;
private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port));
private final String endpoint = "deviceAEndpoint";
private final String serverUri = "coaps://localhost:" + port;
@NotNull
private Device createDevice(X509ClientCredentials 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);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
return device;
}
@Test
public void testConnectAndObserveTelemetry() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
X509ClientCredentials credentials = new X509ClientCredentials();
credentials.setEndpoint(endpoint);
Device device = createDevice(credentials);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
client.init(security, coapConfig);
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
client.destroy();
}
@Test
public void testConnectWithCertAndObserveTelemetry() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
X509ClientCredentials credentials = new X509ClientCredentials();
credentials.setEndpoint(endpoint);
credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted));
Device device = createDevice(credentials);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
client.init(security, coapConfig);
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
client.destroy();
}
}

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

@ -0,0 +1,259 @@
/**
* 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;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.elements.Connector;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.ClientHandshaker;
import org.eclipse.californium.scandium.dtls.DTLSSession;
import org.eclipse.californium.scandium.dtls.HandshakeException;
import org.eclipse.californium.scandium.dtls.Handshaker;
import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker;
import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker;
import org.eclipse.californium.scandium.dtls.ServerHandshaker;
import org.eclipse.californium.scandium.dtls.SessionAdapter;
import org.eclipse.leshan.client.californium.LeshanClient;
import org.eclipse.leshan.client.californium.LeshanClientBuilder;
import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.client.object.Server;
import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.californium.DefaultEndpointFactory;
import org.eclipse.leshan.core.model.LwM2mModel;
import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
import org.eclipse.leshan.core.request.BindingMode;
import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import static org.eclipse.leshan.core.LwM2mId.DEVICE;
import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER;
@Slf4j
@Data
public class LwM2MTestClient {
private final ScheduledExecutorService executor;
private final String endpoint;
private LeshanClient client;
public void init(Security security, NetworkConfig coapConfig) {
String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"};
List<ObjectModel> models = new ArrayList<>();
for (String resourceName : resources) {
models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
}
LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = new ObjectsInitializer(model);
initializer.setInstancesForObject(SECURITY, security);
initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false));
initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice());
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedCipherSuitesOnly(true);
DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
engineFactory.setReconnectOnUpdate(false);
engineFactory.setResumeOnConnect(true);
DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) {
@Override
protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
return new DTLSConnector(dtlsConfig) {
@Override
protected void onInitializeHandshaker(Handshaker handshaker) {
handshaker.addSessionListener(new SessionAdapter() {
@Override
public void handshakeStarted(Handshaker handshaker) throws HandshakeException {
if (handshaker instanceof ServerHandshaker) {
log.info("DTLS Full Handshake initiated by server : STARTED ...");
} else if (handshaker instanceof ResumingServerHandshaker) {
log.info("DTLS abbreviated Handshake initiated by server : STARTED ...");
} else if (handshaker instanceof ClientHandshaker) {
log.info("DTLS Full Handshake initiated by client : STARTED ...");
} else if (handshaker instanceof ResumingClientHandshaker) {
log.info("DTLS abbreviated Handshake initiated by client : STARTED ...");
}
}
@Override
public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession)
throws HandshakeException {
if (handshaker instanceof ServerHandshaker) {
log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker);
} else if (handshaker instanceof ResumingServerHandshaker) {
log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker);
} else if (handshaker instanceof ClientHandshaker) {
log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker);
} else if (handshaker instanceof ResumingClientHandshaker) {
log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker);
}
}
@Override
public void handshakeFailed(Handshaker handshaker, Throwable error) {
/** get cause */
String cause;
if (error != null) {
if (error.getMessage() != null) {
cause = error.getMessage();
} else {
cause = error.getClass().getName();
}
} else {
cause = "unknown cause";
}
if (handshaker instanceof ServerHandshaker) {
log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause);
} else if (handshaker instanceof ResumingServerHandshaker) {
log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause);
} else if (handshaker instanceof ClientHandshaker) {
log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause);
} else if (handshaker instanceof ResumingClientHandshaker) {
log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause);
}
}
});
}
};
}
};
LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
builder.setLocalAddress("0.0.0.0", 11000);
builder.setObjects(initializer.createAll());
builder.setCoapConfig(coapConfig);
builder.setDtlsConfig(dtlsConfig);
builder.setRegistrationEngineFactory(engineFactory);
builder.setEndpointFactory(endpointFactory);
builder.setSharedExecutor(executor);
builder.setDecoder(new DefaultLwM2mNodeDecoder(true));
builder.setEncoder(new DefaultLwM2mNodeEncoder(true));
client = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() {
@Override
public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapStarted...");
}
@Override
public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapSuccess...");
}
@Override
public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver -> onBootstrapFailure...");
}
@Override
public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapTimeout...");
}
@Override
public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName());
}
@Override
public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID);
}
@Override
public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server);
}
@Override
public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request);
}
@Override
public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request);
}
@Override
public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request);
}
@Override
public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
}
@Override
public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
}
@Override
public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId());
}
@Override
public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId());
}
@Override
public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
}
@Override
public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
}
};
this.client.addObserver(observer);
client.start();
}
public void destroy() {
client.destroy(true);
}
}

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

@ -0,0 +1,199 @@
/**
* 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.LwM2mResource;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import javax.security.auth.Destroyable;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;
@Slf4j
public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
private static final Random RANDOM = new Random();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3
// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21
);
@Override
public ReadResponse read(ServerIdentity identity, int resourceid) {
if (!identity.isSystem())
log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid);
switch (resourceid) {
case 0:
return ReadResponse.success(resourceid, getManufacturer());
case 1:
return ReadResponse.success(resourceid, getModelNumber());
case 2:
return ReadResponse.success(resourceid, getSerialNumber());
case 3:
return ReadResponse.success(resourceid, getFirmwareVersion());
case 9:
return ReadResponse.success(resourceid, getBatteryLevel());
case 10:
return ReadResponse.success(resourceid, getMemoryFree());
case 11:
Map<Integer, Long> errorCodes = new HashMap<>();
errorCodes.put(0, getErrorCode());
return ReadResponse.success(resourceid, errorCodes, ResourceModel.Type.INTEGER);
case 14:
return ReadResponse.success(resourceid, getUtcOffset());
case 15:
return ReadResponse.success(resourceid, getTimezone());
case 16:
return ReadResponse.success(resourceid, getSupportedBinding());
case 17:
return ReadResponse.success(resourceid, getDeviceType());
case 18:
return ReadResponse.success(resourceid, getHardwareVersion());
case 19:
return ReadResponse.success(resourceid, getSoftwareVersion());
case 20:
return ReadResponse.success(resourceid, getBatteryStatus());
case 21:
return ReadResponse.success(resourceid, getMemoryTotal());
default:
return super.read(identity, resourceid);
}
}
@Override
public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) {
String withParams = null;
if (params != null && params.length() != 0) {
withParams = " with params " + params;
}
log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceid, withParams != null ? withParams : "");
return ExecuteResponse.success();
}
@Override
public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) {
log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid);
switch (resourceid) {
case 13:
return WriteResponse.notFound();
case 14:
setUtcOffset((String) value.getValue());
fireResourcesChange(resourceid);
return WriteResponse.success();
case 15:
setTimezone((String) value.getValue());
fireResourcesChange(resourceid);
return WriteResponse.success();
default:
return super.write(identity, resourceid, value);
}
}
private String getManufacturer() {
return "Leshan Demo Device";
}
private String getModelNumber() {
return "Model 500";
}
private String getSerialNumber() {
return "LT-500-000-0001";
}
private String getFirmwareVersion() {
return "1.0.0";
}
private long getErrorCode() {
return 0;
}
private int getBatteryLevel() {
return 42;
}
private long getMemoryFree() {
return Runtime.getRuntime().freeMemory() / 1024;
}
private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime());
private String getUtcOffset() {
return utcOffset;
}
private void setUtcOffset(String t) {
utcOffset = t;
}
private String timeZone = TimeZone.getDefault().getID();
private String getTimezone() {
return timeZone;
}
private void setTimezone(String t) {
timeZone = t;
}
private String getSupportedBinding() {
return "U";
}
private String getDeviceType() {
return "Demo";
}
private String getHardwareVersion() {
return "1.0.1";
}
private String getSoftwareVersion() {
return "1.0.2";
}
private int getBatteryStatus() {
return RANDOM.nextInt(7);
}
private long getMemoryTotal() {
return Runtime.getRuntime().totalMemory() / 1024;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
@Override
public void destroy() {
}
}

3
application/src/test/resources/application-test.properties

@ -0,0 +1,3 @@
transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks
transport.lwm2m.security.key_store_password=server
edges.enabled=true

2
application/src/test/resources/logback.xml

@ -14,6 +14,8 @@
<logger name="org.springframework.boot.test" level="WARN"/>
<logger name="org.apache.cassandra" level="WARN"/>
<logger name="org.cassandraunit" level="INFO"/>
<logger name="org.eclipse.leshan" level="TRACE"/>
<root level="WARN">
<appender-ref ref="console"/>

405
application/src/test/resources/lwm2m/0.xml

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_0-V1_2-20201110-A.xml
Path: http://www.openmobilealliance.org/release/ObjLwM2M_Security/
OMNA LwM2M Registry
Path: https://github.com/OpenMobileAlliance/lwm2m-registry
Name: 0.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M_Core-V1_2
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-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>LWM2M Security</Name>
<Description1><![CDATA[This LwM2M Object provides the keying material of a LwM2M Client appropriate to access a specified LwM2M Server. One Object Instance SHOULD address a LwM2M Bootstrap-Server.
These LwM2M Object Resources MUST only be changed by a LwM2M Bootstrap-Server or Bootstrap from Smartcard and MUST NOT be accessible by any other LwM2M Server.]]></Description1>
<ObjectID>0</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:0:1.2</ObjectURN>
<LWM2MVersion>1.1</LWM2MVersion>
<ObjectVersion>1.2</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Resources>
<Item ID="0">
<Name>LWM2M Server URI</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>String</Type>
<RangeEnumeration>0..255</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Uniquely identifies the LwM2M Server or LwM2M Bootstrap-Server. The format of the CoAP URI is defined in Section 6 of RFC 7252.]]></Description>
</Item>
<Item ID="1">
<Name>Bootstrap-Server</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Determines if the current instance concerns a LwM2M Bootstrap-Server (true) or a standard LwM2M Server (false)]]></Description>
</Item>
<Item ID="2">
<Name>Security Mode</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..4</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Determines which security mode is used
0: Pre-Shared Key mode
1: Raw Public Key mode
2: Certificate mode
3: NoSec mode
4: Certificate mode with EST]]></Description>
</Item>
<Item ID="3">
<Name>Public Key or Identity</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Stores the LwM2M Client's certificate, public key (RPK mode) or PSK Identity (PSK mode).]]></Description>
</Item>
<Item ID="4">
<Name>Server Public Key</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Stores the LwM2M Server's, respectively LwM2M Bootstrap-Server's, certificate, public key (RPK mode) or trust anchor. The Certificate Mode Resource determines the content of this resource.]]></Description>
</Item>
<Item ID="5">
<Name>Secret Key</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Stores the secret key (PSK mode) or private key (RPK or certificate mode).]]></Description>
</Item>
<Item ID="6">
<Name>SMS Security Mode</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..255</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Determines which SMS security mode is used:
0: Reserved for future use
1: DTLS mode (Device terminated) PSK mode assumed
2: Secure Packet Structure mode (Smartcard terminated)
3: NoSec mode
4: Reserved mode (DTLS mode with multiplexing Security Association support)
5-203 : Reserved for future use
204-255: Proprietary modes]]></Description>
</Item>
<Item ID="7">
<Name>SMS Binding Key Parameters</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration>6</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Stores the KIc, KID, SPI and TAR.]]></Description>
</Item>
<Item ID="8">
<Name>SMS Binding Secret Key(s)</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration>16,32,48</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Stores the values of the key(s) for the SMS binding.]]></Description>
</Item>
<Item ID="9">
<Name>LwM2M Server SMS Number</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[MSISDN used by the LwM2M Client to send messages to the LwM2M Server via the SMS binding.]]></Description>
</Item>
<Item ID="10">
<Name>Short Server ID</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>1..65534</RangeEnumeration>
<Units></Units>
<Description><![CDATA[This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client.
This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'.
The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.]]></Description>
</Item>
<Item ID="11">
<Name>Client Hold Off Time</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode.
In case client initiated bootstrap is supported by the LwM2M Client, this resource MUST be supported. This information is relevant for use with a Bootstrap-Server only.]]></Description>
</Item>
<Item ID="12">
<Name>Bootstrap-Server Account Timeout</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The LwM2M Client MUST purge the LwM2M Bootstrap-Server Account after the timeout value given by this resource. The lowest timeout value is 1.
If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.]]></Description>
</Item>
<Item ID="13">
<Name>Matching Type</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..3</RangeEnumeration>
<Units></Units>
<Description><![CDATA[The Matching Type Resource specifies how the certificate or raw public key in in the Server Public Key is presented. Four values are currently defined:
0: Exact match. This is the default value and also corresponds to the functionality of LwM2M v1.0. Hence, if this resource is not present then the content of the Server Public Key Resource corresponds to this value.
1: SHA-256 hash [RFC6234]
2: SHA-384 hash [RFC6234]
3: SHA-512 hash [RFC6234]]]></Description>
</Item>
<Item ID="14">
<Name>SNI</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[This resource holds the value of the Server Name Indication (SNI) value to be used during the TLS handshake. When this resource is present then the LwM2M Server URI acts as the address of the service while the SNI value is used for matching a presented certificate, or PSK identity.]]></Description>
</Item>
<Item ID="15">
<Name>Certificate Usage</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..3</RangeEnumeration>
<Units></Units>
<Description><![CDATA[The Certificate Usage Resource specifies the semantic of the certificate or
raw public key stored in the Server Public Key Resource, which is used to match
the certificate presented in the TLS/DTLS handshake. The currently defined values are
0 for "CA constraint", 1 for "service certificate constraint", 2 for "trust anchor
assertion", and 3 for "domain-issued certificate". When this resource is absent,
value (3) for domain issued certificate mode is assumed. More details about the
semantic of each value can be found in the security consideration section of the
LwM2M specification.]]></Description>
</Item>
<Item ID="16">
<Name>DTLS/TLS Ciphersuite</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[When this resource is present it instructs the TLS/DTLS client to propose the indicated ciphersuite(s) in the ClientHello of the handshake. A ciphersuite is indicated as a 32-bit integer value. The IANA TLS ciphersuite registry is maintained at https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml. As an example, the TLS_PSK_WITH_AES_128_CCM_8 ciphersuite is represented with the following string "0xC0,0xA8". To form an integer value the two values are concatenated. In this example, the value is 0xc0a8 or 49320.]]></Description>
</Item>
<Item ID="17"><Name>OSCORE Security Mode</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it provides a link to the OSCORE Object Instance and OSCORE MUST be used by the LwM2M Client with the linked OSCORE Object Instance.]]></Description>
</Item>
<Item ID="18">
<Name>Groups To Use by Client</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates what groups the LwM2M Client should use with a LwM2M Server/LwM2M Bootstrap-Server (ordered from most preferred to least preferred). Resource instance 0 indicates the most preferred group. The values are taken from Section 4.2.7 of RFC 8446. An example is secp256r1 (0x0017).]]></Description>
</Item>
<Item ID="19">
<Name>Signature Algorithms Supported by Server</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates what signature algorithms the LwM2M Server/LwM2M Bootstrap-Server supports. The values are taken from Section 4.2.3 of RFC 8446. An example is ecdsa_secp256r1_sha256(0x0403).]]></Description>
</Item>
<Item ID="20"><Name>Signature Algorithms To Use by Client</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates what signature algorithms the LwM2M Client should use with a LwM2M Server/LwM2M Bootstrap-Server (ordered from most preferred to least preferred). Resource instance 0 indicates the most preferred group. The values are taken from Section 4.2.3 of RFC 8446. An example is ecdsa_secp256r1_sha256(0x0403).]]></Description>
</Item>
<Item ID="21">
<Name>Signature Algorithm Certs Supported by Server</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates what certificate-specific signature algorithms the the LwM2M Server/LwM2M Bootstrap-Server supports. The values are taken from Section 4.2.3 of RFC 8446. An example is ecdsa_secp256r1_sha256(0x0403).]]></Description>
</Item>
<Item ID="22">
<Name>TLS 1.3 Features To Use by Client</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates which features the LwM2M Client should use with the respective LwM2M Server/LwM2M Bootstrap-Server. The bitmask values listed below are defined. A bit value of '0' means the feature should not be used. bit(0) - PSK Plain, bit(1) - 0-RTT, bit(2) - PSK with PFS, bit(3) - Certificate-based Authentication. Bit(4) to bit(31) are reserved.]]></Description>
</Item>
<Item ID="23">
<Name>TLS Extensions Supported by Server</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates what extensions the LwM2M Server/LwM2M Bootstrap-Server supports in form of a bitmap. The following values are defined: bit(0) - Server Name Indication (RFC 6066), bit (1) - Max Fragment Length (RFC 6066), bit (2) - Status Request (RFC 6066), bit (3) - Heartbeat (RFC 6520), bit (4) - Application Layer Protocol Negotiation (RFC 7301), bit (5) - Signed Certificate Timestamp (RFC 6962), bit (6) - Certificate Compression (draft-ietf-tls-certificate-compression), bit (7) - Record Size Limit (RFC 8449), bit (8) - Ticket Pinning (draft-ietf-tls-pinning-ticket), bit (9) - Certificate Authorities (RFC 8446), bit (10) - OID Filters (RFC 8446), bit (11) - Post Handshake Auth (RFC 8446), bit (12) - Connection ID (draft-ietf-tls-dtls-connection-id/draft-ietf-tls-dtls13). Bit(13) to bit(31) are reserved. ]]></Description>
</Item>
<Item ID="24">
<Name>TLS Extensions To Use by Client</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it indicates what extensions the LwM2M Client should use with the LwM2M Server/LwM2M Bootstrap-Server in form of a bitmap. The following values are defined: bit(0) - Server Name Indication (RFC 6066), bit (1) - Max Fragment Length (RFC 6066), bit (2) - Status Request (RFC 6066), bit (3) - Heartbeat (RFC 6520), bit (4) - Application Layer Protocol Negotiation (RFC 7301), bit (5) - Signed Certificate Timestamp (RFC 6962), bit (6) - Certificate Compression (draft-ietf-tls-certificate-compression), bit (7) - Record Size Limit (RFC 8449), bit (8) - Ticket Pinning (draft-ietf-tls-pinning-ticket), bit (9) - Certificate Authorities (RFC 8446), bit (10) - OID Filters (RFC 8446), bit (11) - Post Handshake Auth (RFC 8446), bit (12) - Connection ID (draft-ietf-tls-dtls-connection-id/draft-ietf-tls-dtls13). Bit(13) to bit(31) are reserved. ]]></Description>
</Item>
<Item ID="25">
<Name>Secondary LwM2M Server URI</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>0..255</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is present then the LwM2M Server URI in the Security Object, Resource ID 0, is augmented with information about further LwM2M Server URIs that can be used with the same security information found in the LwM2M Security Object. This is useful when a LwM2M Server is reachable via two different transport bindings (i.e. URIs). For example when the same server is reachable with two different URIs, such as a "coaps" and a "coaps+tcp" URI scheme.]]></Description>
</Item>
<Item ID="26"><Name>MQTT Server</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it provides a link to a MQTT Server Object Instance, which offers additional configuration information for use with this MQTT server. This Resource is used only when the URI scheme in the LwM2M Server URI Resource indicates the use of MQTT.]]></Description>
</Item>
<Item ID="27"><Name>LwM2M COSE Security</Name>
<Operations></Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it provides a links to LwM2M COSE Object Instances, which contain security-relevant configuration information for use with COSE.]]></Description>
</Item>
<Item ID="28"><Name>RDS Destination Port</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..15</RangeEnumeration>
<Units></Units>
<Description><![CDATA[This resource provides the default RDS Destination Port Number (as defined in 3GPP TS 24.250) to use for contacting the LwM2M or Bootstrap Server when communicating through the SCEF across the Non-IP binding.]]></Description>
</Item>
<Item ID="29"><Name>RDS Source Port</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..15</RangeEnumeration>
<Units></Units>
<Description><![CDATA[This resource provides the default RDS Source Port Number (as defined in 3GPP TS 24.250) to use for contacting the LwM2M or Bootstrap Server when communicating through the SCEF across the Non-IP binding.]]></Description>
</Item>
<Item ID="30"><Name>RDS Application ID</Name>
<Operations></Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[This resource provides the Application ID (as defined in 3GPP TS 24.250) to use for querying the SCEF for the source and destination port numbers for contacting the LwM2M or Bootstrap Server when communicating through the SCEF across the Non-IP binding.]]></Description>
</Item>
</Resources>
<Description2><![CDATA[]]></Description2>
</Object>
</LWM2M>

360
application/src/test/resources/lwm2m/1.xml

@ -0,0 +1,360 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_1-V1_2-20201110-A.xml
Path: http://www.openmobilealliance.org/release/ObjLwM2M_Server/
OMNA LwM2M Registry
Path: https://github.com/OpenMobileAlliance/lwm2m-registry
Name: 1.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M_Core-V1_2
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-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>LwM2M Server</Name>
<Description1><![CDATA[This LwM2M Objects provides the data related to a LwM2M Server. A Bootstrap-Server has no such an Object Instance associated to it.]]></Description1>
<ObjectID>1</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:1:1.2</ObjectURN>
<LWM2MVersion>1.2</LWM2MVersion>
<ObjectVersion>1.2</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Resources>
<Item ID="0">
<Name>Short Server ID</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>1..65534</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Used as link to associate server Object Instance.]]></Description>
</Item>
<Item ID="1">
<Name>Lifetime</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[Specify the lifetime of the registration in seconds (see Client Registration Interface). If the value is set to 0, the lifetime is infinite.]]></Description>
</Item>
<Item ID="2">
<Name>Default Minimum Period</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation.
If this Resource doesn’t exist, the default value is 0.]]></Description>
</Item>
<Item ID="3">
<Name>Default Maximum Period</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The default value the LwM2M Client should use for the Maximum Period of an Observation in the absence of this parameter being included in an Observation.]]></Description>
</Item>
<Item ID="4">
<Name>Disable</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this Resource is executed, this LwM2M Server Object is disabled for a certain period defined in the Disabled Timeout Resource. After receiving "Execute" operation, LwM2M Client MUST send response of the operation and perform de-registration process, and underlying network connection between the Client and Server MUST be disconnected to disable the LwM2M Server account.
After the above process, the LwM2M Client MUST NOT send any message to the Server and ignore all the messages from the LwM2M Server for the period.]]></Description>
</Item>
<Item ID="5">
<Name>Disable Timeout</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[A period to disable the Server. After this period, the LwM2M Client MUST perform registration process to the Server. If this Resource is not set, a default timeout value is 86400 (1 day).]]></Description>
</Item>
<Item ID="6">
<Name>Notification Storing When Disabled or Offline</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If true, the LwM2M Client stores "Notify" operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored "Notify" operations to the Server.
If false, the LwM2M Client discards all the "Notify" operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline.
The default value is true.
The maximum number of storing Notifications per Server is up to the implementation.]]></Description>
</Item>
<Item ID="7">
<Name>Binding</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The possible values are those listed in the LwM2M Core Specification. This Resource defines the transport binding configured for the LwM2M Client.
If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.]]></Description>
</Item>
<Item ID="8">
<Name>Registration Update Trigger</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this Resource is executed the LwM2M Client MUST perform an "Update" operation with this LwM2M Server. The LwM2M Client can use a transport binding supported in the Current Binding Mode, Preferred Transport resource or the transport specified as an argument in the Registration Update Trigger.]]></Description>
</Item>
<Item ID="9">
<Name>Bootstrap-Request Trigger</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[When this Resource is executed the LwM2M Client MUST initiate a "Client Initiated Bootstrap" procedure in using the LwM2M Bootstrap-Server Account.]]></Description>
</Item>
<Item ID="10">
<Name>APN Link</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it provides a link to the APN connection profile Object Instance (OMNA registered Object ID:11) to be used to communicate with this server.]]></Description>
</Item>
<Item ID="11">
<Name>TLS-DTLS Alert Code</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..255</RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it contains the most recent TLS / DTLS alert message received from the LwM2M Server respective represented by the AlertDescription defined in Section 7.2 of RFC 5246. This resource set by the LwM2M Client may help the LwM2M Bootstrap-Server to determine the cause of TLS/DTLS connection failure with the respective LwM2M Server.]]></Description>
</Item>
<Item ID="12">
<Name>Last Bootstrapped</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it represents the last time that the bootstrap server updated this LwM2M Server Account. The LwM2M Client is responsible for updating this value. When the Bootstrap Server detects that this LwM2M Server Account is "out-of-date", the Bootstrap Server can update the LwM2M Server Account as represented by the LwM2M Server object instance.]]></Description>
</Item>
<Item ID="13">
<Name>Registration Priority Order</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The LwM2M Client sequences the LwM2M Server registrations in increasing order of this value. If this value is not defined, registration attempts to this server are not impacted by other server registrations.]]></Description>
</Item>
<Item ID="14">
<Name>Initial Registration Delay Timer</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The delay, in seconds, before registration is attempted for this LwM2M Server based upon the completion of registration of the previous LwM2M Server in the registration order. This is only applied until the first successful registration after a successful bootstrapping sequence.]]></Description>
</Item>
<Item ID="15">
<Name>Registration Failure Block</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[When set to true and registration to this LwM2M server fails, the LwM2M Client blocks registration to other servers in the order. When set to false, the LwM2M Client proceeds with registration to the next server in the order.]]></Description>
</Item>
<Item ID="16">
<Name>Bootstrap on Registration Failure</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If set to true, this indicates that the LwM2M Client should re-bootstrap when either registration is explicitly rejected by the LwM2M Server or registration is considered as failing as dictated by the other resource settings. If set to false, the LwM2M Client will continue with the registration attempts as dictated by the other resource settings.]]></Description>
</Item>
<Item ID="17">
<Name>Communication Retry Count</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The number of successive communication attempts before which a communication sequence is considered as failed.]]></Description>
</Item>
<Item ID="18">
<Name>Communication Retry Timer</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The delay, in seconds, between successive communication attempts in a communication sequence. This value is multiplied by two to the power of the communication retry attempt minus one (2**(retry attempt-1)) to create an exponential back-off.]]></Description>
</Item>
<Item ID="19">
<Name>Communication Sequence Delay Timer</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units>s</Units>
<Description><![CDATA[The delay, in seconds, between successive communication sequences. A communication sequence is defined as the exhaustion of the Communication Retry Count and Communication Retry Timer values. A communication sequence can be applied to server registrations or bootstrapping attempts. MAX_VALUE means do not perform another communication sequence.]]></Description>
</Item>
<Item ID="20">
<Name>Communication Sequence Retry Count</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[The number of successive communication sequences before which a registration attempt is considered as failed.]]></Description>
</Item>
<Item ID="21">
<Name>Trigger</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Using the Trigger Resource a LwM2M Client can indicate whether it is reachable over SMS (value set to 'true') or not (value set to 'false'). The default value (resource not present) is 'false'. When set to 'true' the LwM2M Server MAY, for example, request the LwM2M Client to perform operations, such as the "Update" operation by sending an "Execute" operation on "Registration Update Trigger" Resource via SMS. No SMS response is expected for such a message.]]></Description>
</Item>
<Item ID="22">
<Name>Preferred Transport</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>The possible values are those listed in the LwM2M Core Specification</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Only a single transport binding SHALL be present. When the LwM2M client supports multiple transports, it MAY use this transport to initiate a connection. This resource can also be used to switch between multiple transports e.g. a non-IP device can switch to UDP transport to perform firmware updates.]]></Description>
</Item>
<Item ID="23"><Name>Mute Send</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If true or the Resource is not present, the LwM2M Client Send command capability is de-activated.
If false, the LwM2M Client Send Command capability is activated.]]></Description>
</Item>
<Item ID="24">
<Name>Alternate APN Links</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[If this resource is defined, it provides links to alternate APN connection profile Object Instance (OMNA registered Object ID:11) to be used to communicate with this server if Resource 10 has configuration conflicts.]]></Description>
</Item>
<Item ID="25">
<Name>Supported Server Versions</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[This resource provides the supported enabler versions of the server to the client as a set of strings. Format for each string is 1*DIGIT"."1*DIGIT"."1*DIGIT where the third DIGIT is optional.]]></Description>
</Item>
<Item ID="26">
<Name>Default Notification Mode</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..1</RangeEnumeration>
<Units></Units>
<Description><![CDATA[This resource indicates the default mode for observations to be sent: 0 = Non-Confirmable, 1 = Confirmable.]]></Description>
</Item>
<Item ID="27">
<Name>Profile ID Hash Algorithm</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..255</RangeEnumeration>
<Units/>
<Description><![CDATA[If this resource is defined, it contains the hash algorithm the LwM2M Server would prefer the LwM2M Client to use with the dynamically generated mode of creating Profile IDs. The numerical ID value of the 'Suite Identifiers' registered by RFC 6920 is used in this Resource.]]></Description>
</Item>
</Resources>
<Description2></Description2>
</Object>
</LWM2M>

123
application/src/test/resources/lwm2m/2.xml

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_2-V1_1-20201110-A.xml
Path: http://www.openmobilealliance.org/release/ObjLwM2M_ACL/
OMNA LwM2M Registry
Path: https://github.com/OpenMobileAlliance/lwm2m-registry
Name: 2.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M_Core-V1_2
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>LwM2M Access Control</Name>
<Description1><![CDATA[Access Control Object is used to check whether the LwM2M Server has access right for performing an operation.]]></Description1>
<ObjectID>2</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:2:1.1</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.1</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Object ID</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>1..65534</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Resources 0 and 1 point to the Object Instance for which the Instances of the ACL Resource of that Access Control Object Instance are applicable.]]></Description>
</Item>
<Item ID="1">
<Name>Object Instance ID</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[See above]]></Description>
</Item>
<Item ID="2">
<Name>ACL</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..31</RangeEnumeration>
<Units></Units>
<Description><![CDATA[The Resource Instance ID MUST be the Short Server ID of a certain LwM2M Server for which associated access rights are contained in the Resource Instance value.
The Resource Instance ID 0 is a specific ID, determining the ACL Instance which contains the default access rights.
Each bit set in the Resource Instance value, grants an access right to the LwM2M Server to the corresponding operation.
The bit order is specified as below.
1st LSB: R(Read, Observe, Write-Attributes)
2nd LSB: W(Write)
3rd LSB: E(Execute)
4th LSB: D(Delete)
5th LSB: C(Create)
Other bits are reserved for future use.]]></Description>
</Item>
<Item ID="3">
<Name>Access Control Owner</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..65535</RangeEnumeration>
<Units></Units>
<Description><![CDATA[Short Server ID of a certain LwM2M Server; only such an LwM2M Server can manage the Resources of this Object Instance.
The specific value MAX_ID=65535 means this Access Control Object Instance is created and modified during a Bootstrap phase only.]]></Description>
</Item>
</Resources>
<Description2><![CDATA[]]></Description2>
</Object>
</LWM2M>

331
application/src/test/resources/lwm2m/3.xml

@ -0,0 +1,331 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_3-V1_2-20201110-A.xml
Path: http://www.openmobilealliance.org/release/ObjLwM2M_Device/
OMNA LwM2M Registry
Path: https://github.com/OpenMobileAlliance/lwm2m-registry
Name: 3.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M_Core-V1_2
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-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>Device</Name>
<Description1><![CDATA[This LwM2M Object provides a range of device related information which can be queried by the LwM2M Server, and a device reboot and factory reset function.]]></Description1>
<ObjectID>3</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:3:1.0</ObjectURN>
<LWM2MVersion>1.1</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Resources>
<Item ID="0">
<Name>Manufacturer</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Human readable manufacturer name]]></Description>
</Item>
<Item ID="1">
<Name>Model Number</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[A model identifier (manufacturer specified string)]]></Description>
</Item>
<Item ID="2">
<Name>Serial Number</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Serial Number]]></Description>
</Item>
<Item ID="3">
<Name>Firmware Version</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Current firmware version of the Device.The Firmware Management function could rely on this resource.]]></Description>
</Item>
<Item ID="4">
<Name>Reboot</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Reboot the LwM2M Device to restore the Device from unexpected firmware failure.]]></Description>
</Item>
<Item ID="5">
<Name>Factory Reset</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Perform factory reset of the LwM2M Device to make the LwM2M Device to go through initial deployment sequence where provisioning and bootstrap sequence is performed. This requires client ensuring post factory reset to have minimal information to allow it to carry out one of the bootstrap methods specified in section 5.2.3.
When this Resource is executed, "De-register" operation MAY be sent to the LwM2M Server(s) before factory reset of the LwM2M Device.]]></Description>
</Item>
<Item ID="6">
<Name>Available Power Sources</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..7</RangeEnumeration>
<Units></Units>
<Description><![CDATA[0: DC power
1: Internal Battery
2: External Battery
3: Fuel Cell
4: Power over Ethernet
5: USB
6: AC (Mains) power
7: Solar
The same Resource Instance ID MUST be used to associate a given Power Source (Resource ID:6) with its Present Voltage (Resource ID:7) and its Present Current (Resource ID:8)]]></Description>
</Item>
<Item ID="7">
<Name>Power Source Voltage</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Present voltage for each Available Power Sources Resource Instance. The unit used for this resource is in mV.]]></Description>
</Item>
<Item ID="8">
<Name>Power Source Current</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Present current for each Available Power Source. The unit used for this resource is in mA.]]></Description>
</Item>
<Item ID="9">
<Name>Battery Level</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..100</RangeEnumeration>
<Units>/100</Units>
<Description><![CDATA[Contains the current battery level as a percentage (with a range from 0 to 100). This value is only valid for the Device internal Battery if present (one Available Power Sources Resource Instance is 1).]]></Description>
</Item>
<Item ID="10">
<Name>Memory Free</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Estimated current available amount of storage space which can store data and software in the LwM2M Device (expressed in kilobytes). Note: 1 kilobyte corresponds to 1000 bytes.]]></Description>
</Item>
<Item ID="11">
<Name>Error Code</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..32</RangeEnumeration>
<Units></Units>
<Description><![CDATA[0=No error
1=Low battery power
2=External power supply off
3=GPS module failure
4=Low received signal strength
5=Out of memory
6=SMS failure
7=IP connectivity failure
8=Peripheral malfunction
9..15=Reserved for future use
16..32=Device specific error codes
When the single Device Object Instance is initiated, there is only one error code Resource Instance whose value is equal to 0 that means no error. When the first error happens, the LwM2M Client changes error code Resource Instance to any non-zero value to indicate the error type. When any other error happens, a new error code Resource Instance is created. When an error associated with a Resource Instance is no longer present, that Resource Instance is deleted. When the single existing error is no longer present, the LwM2M Client returns to the original no error state where Instance 0 has value 0.
This error code Resource MAY be observed by the LwM2M Server. How to deal with LwM2M Client’s error report depends on the policy of the LwM2M Server. Error codes in between 16 and 32 are specific to the Device and may have different meanings among implementations.]]></Description>
</Item>
<Item ID="12">
<Name>Reset Error Code</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Delete all error code Resource Instances and create only one zero-value error code that implies no error, then re-evaluate all error conditions and update and create Resources Instances to capture all current error conditions.]]></Description>
</Item>
<Item ID="13">
<Name>Current Time</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Current UNIX time of the LwM2M Client.
The LwM2M Client should be responsible to increase this time value as every second elapses.
The LwM2M Server is able to write this Resource to make the LwM2M Client synchronized with the LwM2M Server.]]></Description>
</Item>
<Item ID="14">
<Name>UTC Offset</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Indicates the UTC offset currently in effect for this LwM2M Device. UTC+X [ISO 8601].]]></Description>
</Item>
<Item ID="15">
<Name>Timezone</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Indicates in which time zone the LwM2M Device is located, in IANA Timezone (TZ) database format.]]></Description>
</Item>
<Item ID="16">
<Name>Supported Binding and Modes</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Indicates which bindings and modes are supported in the LwM2M Client. The possible values are those listed in the LwM2M Core Specification.]]></Description>
</Item>
<Item ID="17"><Name>Device Type</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Type of the device (manufacturer specified string: e.g. smart meters / dev Class / ...)]]></Description>
</Item>
<Item ID="18"><Name>Hardware Version</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Current hardware version of the device]]></Description>
</Item>
<Item ID="19"><Name>Software Version</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Current software version of the device (manufacturer specified string). On elaborated LwM2M device, SW could be split in 2 parts: a firmware one and a higher level software on top.
Both pieces of Software are together managed by LwM2M Firmware Update Object (Object ID 5)]]></Description>
</Item>
<Item ID="20"><Name>Battery Status</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0..6</RangeEnumeration>
<Units></Units>
<Description><![CDATA[This value is only valid for the Device Internal Battery if present (one Available Power Sources Resource Instance value is 1).
Battery
Status Meaning Description
0 Normal The battery is operating normally and not on power.
1 Charging The battery is currently charging.
2 Charge Complete The battery is fully charged and still on power.
3 Damaged The battery has some problem.
4 Low Battery The battery is low on charge.
5 Not Installed The battery is not installed.
6 Unknown The battery information is not available.]]></Description>
</Item>
<Item ID="21"><Name>Memory Total</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Total amount of storage space which can store data and software in the LwM2M Device (expressed in kilobytes). Note: 1 kilobyte corresponds to 1000 bytes.]]></Description>
</Item>
<Item ID="22"><Name>ExtDevInfo</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Objlnk</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Reference to external "Device" object instance containing information. For example, such an external device can be a Host Device, which is a device into which the Device containing the LwM2M client is embedded. This Resource may be used to retrieve information about the Host Device.]]></Description>
</Item></Resources>
<Description2></Description2>
</Object>
</LWM2M>

BIN
application/src/test/resources/lwm2m/credentials/clientKeyStore.jks

Binary file not shown.

BIN
application/src/test/resources/lwm2m/credentials/serverKeyStore.jks

Binary file not shown.

1
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java

@ -145,7 +145,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
// empty implementation
}
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {

5
common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.firmware;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
@ -25,12 +26,16 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.nio.ByteBuffer;
public interface FirmwareService {
FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo);
Firmware saveFirmware(Firmware firmware);
String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data);
Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId);
FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId);

3
common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
@ -39,7 +40,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId>
private boolean hasData;
private String fileName;
private String contentType;
private String checksumAlgorithm;
private ChecksumAlgorithm checksumAlgorithm;
private String checksum;
private Long dataSize;

27
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public abstract class AbstractLwM2MClientCredentials implements LwM2MClientCredentials {
private String endpoint;
}

34
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Hex;
public abstract class HasKey extends AbstractLwM2MClientCredentials {
private byte[] key;
@SneakyThrows
public void setKey(String key) {
if (key != null) {
this.key = Hex.decodeHex(key.toLowerCase().toCharArray());
}
}
public byte[] getKey() {
return key;
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.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.common.data.device.credentials.lwm2m;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "securityConfigClientMode")
@JsonSubTypes({
@JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"),
@JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"),
@JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"),
@JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509")})
public interface LwM2MClientCredentials {
@JsonIgnore
LwM2MSecurityMode getSecurityConfigClientMode();
String getEndpoint();
}

20
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public enum LwM2MSecurityMode {
PSK, RPK, X509, NO_SEC;
}

24
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class NoSecClientCredentials extends AbstractLwM2MClientCredentials {
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.NO_SEC;
}
}

30
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PSKClientCredentials extends HasKey {
private String identity;
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.PSK;
}
}

24
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
public class RPKClientCredentials extends HasKey {
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.RPK;
}
}

30
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.credentials.lwm2m;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class X509ClientCredentials extends AbstractLwM2MClientCredentials {
private String cert;
@Override
public LwM2MSecurityMode getSecurityConfigClientMode() {
return LwM2MSecurityMode.X509;
}
}

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java

@ -82,7 +82,8 @@ public class MqttTopics {
public static final String DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN;
public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC;
public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR;
public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d";
public static final String DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "/%s" + RESPONSE + "/%s" + CHUNK + "%d";
public static final String DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN;
public static final String DEVICE_SOFTWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC;

26
common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.firmware;
public enum ChecksumAlgorithm {
MD5,
SHA256,
SHA384,
SHA512,
CRC32,
MURMUR3_32,
MURMUR3_128
}

6
common/queue/pom.xml

@ -120,12 +120,16 @@
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>

4
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java

@ -61,6 +61,10 @@ public class TbKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId);
}
this.settings = settings;
// Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class
// details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
this.producer = new KafkaProducer<>(props);
this.defaultTopic = defaultTopic;
this.admin = admin;

4
common/transport/lwm2m/pom.xml

@ -52,6 +52,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -65,7 +65,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')")
@RequiredArgsConstructor
public class LwM2MTransportBootstrapServerConfiguration {
//TODO: @ybondarenko please refactor this to be similar to DefaultLwM2mTransportService
public class LwM2MTransportBootstrapService {
private PublicKey publicKey;
private PrivateKey privateKey;
private boolean pskMode = false;

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

@ -73,17 +73,17 @@ public class LwM2MBootstrapConfig {
configBs.servers.put(0, server0);
/* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */
this.bootstrapServer.setBootstrapServerIs(true);
configBs.security.put(0, setServerSecuruty(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId()));
configBs.security.put(0, setServerSecurity(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId()));
/* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */
configBs.security.put(1, setServerSecuruty(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId()));
configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId()));
return configBs;
}
private BootstrapConfig.ServerSecurity setServerSecuruty(String host, Integer port, boolean bootstrapServer, String securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) {
private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) {
BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity();
serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port);
serverSecurity.bootstrapServer = bootstrapServer;
serverSecurity.securityMode = SecurityMode.valueOf(securityMode);
serverSecurity.securityMode = securityMode;
serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode);
serverSecurity.serverPublicKey = (serverPublicKey != null && !serverPublicKey.isEmpty()) ? Hex.decodeHex(serverPublicKey.toCharArray()) : new byte[]{};
serverSecurity.secretKey = (secretKey != null && !secretKey.isEmpty()) ? Hex.decodeHex(secretKey.toCharArray()) : new byte[]{};
@ -91,9 +91,9 @@ public class LwM2MBootstrapConfig {
return serverSecurity;
}
private byte[] setPublicKeyOrId(String publicKeyOrIdStr, String securityMode) {
private byte[] setPublicKeyOrId(String publicKeyOrIdStr, SecurityMode securityMode) {
return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} :
SecurityMode.valueOf(securityMode).equals(SecurityMode.PSK) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) :
SecurityMode.PSK.equals(securityMode) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) :
Hex.decodeHex(publicKeyOrIdStr.toCharArray());
}
}

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

@ -31,7 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
@ -74,7 +73,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
public List<SecurityInfo> getAllByEndpoint(String endPoint) {
EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
BootstrapConfig bsConfigNew = store.getBootstrapConfig();
@ -98,13 +97,13 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
public SecurityInfo getByIdentity(String identity) {
EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
BootstrapConfig bsConfig = store.getBootstrapConfig();
if (bsConfig.security != null) {
try {
bootstrapConfigStore.add(store.getEndPoint(), bsConfig);
bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
} catch (InvalidConfigurationException e) {
log.error("", e);
}
@ -119,29 +118,29 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
if (lwM2MBootstrapConfig != null) {
/* Security info */
switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) {
switch (lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode()) {
/* Use RPK only */
case PSK:
store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(),
store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(),
lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(),
Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray())));
store.setSecurityMode(SecurityMode.PSK.code);
store.setSecurityMode(SecurityMode.PSK);
break;
case RPK:
try {
store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndPoint(),
store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(),
SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray()))));
store.setSecurityMode(SecurityMode.RPK.code);
store.setSecurityMode(SecurityMode.RPK);
break;
} catch (IOException | GeneralSecurityException e) {
log.error("Unable to decode Client public key for [{}] [{}]", store.getEndPoint(), e.getMessage());
log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage());
}
case X509:
store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndPoint()));
store.setSecurityMode(SecurityMode.X509.code);
store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint()));
store.setSecurityMode(SecurityMode.X509);
break;
case NO_SEC:
store.setSecurityMode(SecurityMode.NO_SEC.code);
store.setSecurityMode(SecurityMode.NO_SEC);
store.setSecurityInfo(null);
break;
default:
@ -153,10 +152,9 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) {
try {
JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential();
if (bootstrapJsonCredential != null) {
LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig();
if (lwM2MBootstrapConfig != null) {
ObjectMapper mapper = new ObjectMapper();
LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class);
JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile());
lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class);
LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class);
@ -167,22 +165,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo);
return lwM2MBootstrapConfig;
} else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo);
return null;
}
}
} catch (JsonProcessingException e) {
log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage());
log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndpoint(), e.getMessage());
return null;
}
log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint());
log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint());
return null;
}

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

@ -32,7 +32,7 @@ public class LwM2MServerBootstrap {
String host = "0.0.0.0";
Integer port = 0;
String securityMode = SecurityMode.NO_SEC.name();
SecurityMode securityMode = SecurityMode.NO_SEC;
Integer serverId = 123;
boolean bootstrapServerIs = false;

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

@ -156,7 +156,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
keyStoreValue = KeyStore.getInstance(keyStoreType);
keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray());
} catch (Exception e) {
log.trace("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
log.info("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
}
}
}

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

@ -15,24 +15,23 @@
*/
package org.thingsboard.server.transport.lwm2m.secure;
import com.google.gson.JsonObject;
import lombok.Data;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig;
@Data
public class EndpointSecurityInfo {
private ValidateDeviceCredentialsResponseMsg msg;
private ValidateDeviceCredentialsResponse msg;
private SecurityInfo securityInfo;
private int securityMode = DEFAULT_MODE.code;
private SecurityMode securityMode;
/** bootstrap */
private DeviceProfile deviceProfile;
private JsonObject bootstrapJsonCredential;
private String endPoint;
private LwM2MBootstrapConfig bootstrapCredentialConfig;
private String endpoint;
private BootstrapConfig bootstrapConfig;
}

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java

@ -33,16 +33,6 @@ import java.util.Arrays;
@Slf4j
public class LWM2MGenerationPSkRPkECC {
public LWM2MGenerationPSkRPkECC(Integer dtlsMode) {
switch (LwM2MSecurityMode.fromSecurityMode(dtlsMode)) {
case PSK:
generationPSkKey();
break;
case RPK:
generationRPKECCKey();
}
}
public LWM2MGenerationPSkRPkECC() {
generationPSkKey();
generationRPKECCKey();
@ -102,12 +92,12 @@ public class LWM2MGenerationPSkRPkECC {
/* Get Curves params */
String privHex = Hex.encodeHexString(privKey.getEncoded());
log.info("\nCreating new RPK for the next start... \n" +
" Public Key (Hex): [{}]\n" +
" Private Key (Hex): [{}]" +
" public_x : [{}] \n" +
" public_y : [{}] \n" +
" private_encode : [{}] \n" +
" Elliptic Curve parameters : [{}] \n",
" Public Key (Hex): [{}]\n" +
" Private Key (Hex): [{}]" +
" public_x : [{}] \n" +
" public_y : [{}] \n" +
" private_encode : [{}] \n" +
" Elliptic Curve parameters : [{}] \n",
Hex.encodeHexString(pubKey.getEncoded()),
privHex,
Hex.encodeHexString(x),

58
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java

@ -1,58 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.secure;
public enum LwM2MSecurityMode {
PSK(0, "psk"),
RPK(1, "rpk"),
X509(2, "x509"),
NO_SEC(3, "no_sec"),
X509_EST(4, "x509_est"),
REDIS(7, "redis"),
DEFAULT_MODE(255, "default_mode");
public int code;
public String subEndpoint;
LwM2MSecurityMode(int code, String subEndpoint) {
this.code = code;
this.subEndpoint = subEndpoint;
}
public static LwM2MSecurityMode fromSecurityMode(long code) {
return fromSecurityMode((int) code);
}
public static LwM2MSecurityMode fromSecurityMode(int code) {
for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) {
if (sm.code == code) {
return sm;
}
}
throw new IllegalArgumentException(String.format("Unsupported security code : %d", code));
}
public static LwM2MSecurityMode fromSecurityMode(String subEndpoint) {
for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) {
if (sm.subEndpoint.equals(subEndpoint)) {
return sm;
}
}
throw new IllegalArgumentException(String.format("Unsupported security subEndpoint : %d", subEndpoint));
}
}

119
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -15,33 +15,36 @@
*/
package org.thingsboard.server.transport.lwm2m.secure;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.SecurityUtil;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
import static org.eclipse.leshan.core.SecurityMode.PSK;
import static org.eclipse.leshan.core.SecurityMode.RPK;
import static org.eclipse.leshan.core.SecurityMode.X509;
@Slf4j
@Component
@ -52,19 +55,17 @@ public class LwM2mCredentialsSecurityInfoValidator {
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
public EndpointSecurityInfo getEndpointSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) {
CountDownLatch latch = new CountDownLatch(1);
final EndpointSecurityInfo[] resultSecurityStore = new EndpointSecurityInfo[1];
context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
String credentialsBody = msg.getCredentialsBody();
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
String credentialsBody = msg.getCredentials();
resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue);
resultSecurityStore[0].setMsg(msg);
Optional<DeviceProfile> deviceProfileOpt = LwM2mTransportUtil.decode(msg.getProfileBody().toByteArray());
deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile));
resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile());
latch.countDown();
}
@ -92,39 +93,32 @@ public class LwM2mCredentialsSecurityInfoValidator {
*/
private EndpointSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) {
EndpointSecurityInfo result = new EndpointSecurityInfo();
JsonObject objectMsg = LwM2mTransportUtil.validateJson(jsonStr);
if (objectMsg != null && !objectMsg.isJsonNull()) {
JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null;
/**
* Only PSK
*/
String endpointPsk = (objectMsg.has("client")
&& objectMsg.get("client").getAsJsonObject().has("endpoint")
&& objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null;
endpoint = (endpointPsk == null || endpointPsk.isEmpty()) ? endpoint : endpointPsk;
if (object != null && !object.isJsonNull()) {
if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) {
result.setBootstrapJsonCredential(object);
result.setEndPoint(endpoint);
result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code);
} else {
LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase());
switch (lwM2MSecurityMode) {
case NO_SEC:
createClientSecurityInfoNoSec(result);
break;
case PSK:
createClientSecurityInfoPSK(result, endpoint, object);
break;
case RPK:
createClientSecurityInfoRPK(result, endpoint, object);
break;
case X509:
createClientSecurityInfoX509(result, endpoint);
break;
default:
break;
}
LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class);
if (credentials != null) {
if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) {
result.setBootstrapCredentialConfig(credentials.getBootstrap());
if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) {
PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient();
endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint;
}
result.setEndpoint(endpoint);
result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode());
} else {
switch (credentials.getClient().getSecurityConfigClientMode()) {
case NO_SEC:
createClientSecurityInfoNoSec(result);
break;
case PSK:
createClientSecurityInfoPSK(result, endpoint, credentials.getClient());
break;
case RPK:
createClientSecurityInfoRPK(result, endpoint, credentials.getClient());
break;
case X509:
createClientSecurityInfoX509(result, endpoint, credentials.getClient());
break;
default:
break;
}
}
}
@ -133,19 +127,18 @@ public class LwM2mCredentialsSecurityInfoValidator {
private void createClientSecurityInfoNoSec(EndpointSecurityInfo result) {
result.setSecurityInfo(null);
result.setSecurityMode(NO_SEC.code);
result.setSecurityMode(NO_SEC);
}
private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, JsonObject object) {
/** PSK Deserialization */
String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null;
if (identity != null && !identity.isEmpty()) {
private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) {
PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig;
if (StringUtils.isNotEmpty(pskConfig.getIdentity())) {
try {
byte[] key = (object.has("key") && object.get("key").isJsonPrimitive()) ? Hex.decodeHex(object.get("key").getAsString().toCharArray()) : null;
if (key != null && key.length > 0) {
if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) {
endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint;
if (endpoint != null && !endpoint.isEmpty()) {
result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key));
result.setSecurityMode(PSK.code);
result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getKey()));
result.setSecurityMode(PSK);
}
}
} catch (IllegalArgumentException e) {
@ -156,13 +149,13 @@ public class LwM2mCredentialsSecurityInfoValidator {
}
}
private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, JsonObject object) {
private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) {
RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig;
try {
if (object.has("key") && object.get("key").isJsonPrimitive()) {
byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray());
PublicKey key = SecurityUtil.publicKey.decode(rpkkey);
if (rpkConfig.getKey() != null) {
PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey());
result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key));
result.setSecurityMode(RPK.code);
result.setSecurityMode(RPK);
} else {
log.error("Missing RPK key");
}
@ -171,8 +164,8 @@ public class LwM2mCredentialsSecurityInfoValidator {
}
}
private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint) {
private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) {
result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint));
result.setSecurityMode(X509.code);
result.setSecurityMode(X509);
}
}

67
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java

@ -0,0 +1,67 @@
/**
* 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.secure;
import lombok.RequiredArgsConstructor;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.core.request.UplinkRequest;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.Authorizer;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore;
@Component
@RequiredArgsConstructor
@TbLwM2mTransportComponent
public class TbLwM2MAuthorizer implements Authorizer {
private final TbLwM2MDtlsSessionStore sessionStorage;
private final TbLwM2mSecurityStore securityStore;
private final SecurityChecker securityChecker = new SecurityChecker();
private final LwM2mClientContext clientContext;
@Override
public Registration isAuthorized(UplinkRequest<?> request, Registration registration, Identity senderIdentity) {
if (senderIdentity.isX509()) {
TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint());
if (sessionInfo != null) {
if (sessionInfo.getX509CommonName().endsWith(senderIdentity.getX509CommonName())) {
clientContext.registerClient(registration, sessionInfo.getCredentials());
// X509 certificate is valid and matches endpoint.
return registration;
} else {
// X509 certificate is not valid.
return null;
}
}
// If session info is not found, this may be the trusted certificate, so we still need to check all other options below.
}
SecurityInfo expectedSecurityInfo = null;
if (securityStore != null) {
expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint());
}
if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) {
return registration;
} else {
return null;
}
}
}

195
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java

@ -0,0 +1,195 @@
/**
* 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.secure;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.CertPathUtil;
import org.eclipse.californium.scandium.dtls.AlertMessage;
import org.eclipse.californium.scandium.dtls.CertificateMessage;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
import org.eclipse.californium.scandium.dtls.ConnectionId;
import org.eclipse.californium.scandium.dtls.DTLSSession;
import org.eclipse.californium.scandium.dtls.HandshakeException;
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier;
import org.eclipse.californium.scandium.util.ServerNames;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
import javax.annotation.PostConstruct;
import javax.security.auth.x500.X500Principal;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@TbLwM2mTransportComponent
@RequiredArgsConstructor
public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
private final TransportService transportService;
private final TbLwM2MDtlsSessionStore sessionStorage;
private final LwM2MTransportServerConfig config;
@SuppressWarnings("deprecation")
private StaticCertificateVerifier staticCertificateVerifier;
@Value("${transport.lwm2m.server.security.skip_validity_check_for_client_cert:false}")
private boolean skipValidityCheckForClientCert;
@Override
public List<CertificateType> getSupportedCertificateType() {
return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY);
}
@PostConstruct
public void init() {
try {
/* by default trust all */
X509Certificate[] trustedCertificates = new X509Certificate[0];
if (config.getKeyStoreValue() != null) {
X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
if (rootCAX509Cert != null) {
trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
}
}
staticCertificateVerifier = new StaticCertificateVerifier(trustedCertificates);
} catch (Exception e) {
log.info("Failed to initialize the ");
}
}
@Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
CertPath certChain = message.getCertificateChain();
if (certChain == null) {
//We trust all RPK on this layer, and use TbLwM2MAuthorizer
PublicKey publicKey = message.getPublicKey();
return new CertificateVerificationResult(cid, publicKey, null);
} else {
try {
boolean x509CredentialsFound = false;
CertPath certpath = message.getCertificateChain();
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) {
try {
if (!skipValidityCheckForClientCert) {
cert.checkValidity();
}
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
CountDownLatch latch = new CountDownLatch(1);
transportService.process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
deviceCredentialsResponse[0] = msg;
}
latch.countDown();
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
});
if (latch.await(10, TimeUnit.SECONDS)) {
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) {
LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class);
if(!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)){
continue;
}
X509ClientCredentials config = (X509ClientCredentials) credentials.getClient();
String certBody = config.getCert();
String endpoint = config.getEndpoint();
if (strCert.equals(certBody)) {
x509CredentialsFound = true;
DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) {
sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg));
break;
}
} else {
log.trace("[{}][{}] Certificate mismatch. Expected: {}, Actual: {}", endpoint, sha3Hash, strCert, certBody);
}
}
}
} catch (InterruptedException |
CertificateEncodingException |
CertificateExpiredException |
CertificateNotYetValidException e) {
log.error(e.getMessage(), e);
}
}
if (!x509CredentialsFound) {
if (staticCertificateVerifier != null) {
staticCertificateVerifier.verifyCertificate(message, session);
} else {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR,
session.getPeer());
throw new HandshakeException("x509 verification not enabled!", alert);
}
}
return new CertificateVerificationResult(cid, certpath, null);
} catch (HandshakeException e) {
log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null);
}
}
}
@Override
public List<X500Principal> getAcceptedIssuers() {
return CertPathUtil.toSubjects(null);
}
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
}
}

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.secure;
import lombok.Data;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
@Data
public class TbX509DtlsSessionInfo {
private final String x509CommonName;
private final ValidateDeviceCredentialsResponse credentials;
}

26
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.secure.credentials;
import lombok.Data;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig;
@Data
public class LwM2MCredentials {
private LwM2MClientCredentials client;
private LwM2MBootstrapConfig bootstrap;
}

47
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java

@ -63,6 +63,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
@ -133,13 +134,15 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
public final LwM2mTransportServerHelper helper;
private final LwM2MJsonAdaptor adaptor;
private final LwM2mClientContext clientContext;
public final LwM2mTransportRequest lwM2mTransportRequest;
private final LwM2mTransportRequest lwM2mTransportRequest;
private final TbLwM2MDtlsSessionStore sessionStore;
public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
LwM2mClientContext clientContext,
@Lazy LwM2mTransportRequest lwM2mTransportRequest,
FirmwareDataCache firmwareDataCache,
LwM2mTransportContext context, LwM2MJsonAdaptor adaptor) {
LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) {
this.transportService = transportService;
this.config = config;
this.helper = helper;
@ -148,6 +151,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
this.firmwareDataCache = firmwareDataCache;
this.context = context;
this.adaptor = adaptor;
this.sessionStore = sessionStore;
}
@PostConstruct
@ -182,9 +186,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient);
if (sessionInfo != null) {
transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo));
transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder()
.setSessionInfo(sessionInfo)
.setSessionEvent(DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN))
.setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build())
.setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().build())
.build();
transportService.process(msg, null);
this.getInfoFirmwareUpdate(lwM2MClient);
this.getInfoSoftwareUpdate(lwM2MClient);
this.initLwM2mFromClientValue(registration, lwM2MClient);
@ -241,7 +249,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
try {
this.setCancelObservationsAll(registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId());
this.closeClientSession(registration); ;
this.closeClientSession(registration);
;
} catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t);
this.sendLogsToThingsboard(LOG_LW2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage()), registration.getId());
@ -253,6 +262,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration);
if (sessionInfo != null) {
transportService.deregisterSession(sessionInfo);
sessionStore.remove(registration.getEndpoint());
this.doCloseSession(sessionInfo);
clientContext.removeClientByRegistrationId(registration.getId());
log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
@ -322,7 +332,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
} else if (response.getContent() instanceof LwM2mObjectInstance) {
value = lwM2MClient.instanceToString((LwM2mObjectInstance) response.getContent(), this.converter, pathIdVer);
} else if (response.getContent() instanceof LwM2mResource) {
value = lwM2MClient.resourceToString ((LwM2mResource) response.getContent(), this.converter, pathIdVer);
value = lwM2MClient.resourceToString((LwM2mResource) response.getContent(), this.converter, pathIdVer);
}
String msg = String.format("%s: type operation %s path - %s value - %s", LOG_LW2M_INFO,
READ, pathIdVer, value);
@ -707,16 +717,15 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
*/
private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) {
LwM2mClient lwM2MClient = clientContext.getOrRegister(registration);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config
.getModelProvider())) {
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) {
/** version != null
* set setClient_fw_info... = value
**/
if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(this, path);
lwM2MClient.getFwUpdate().initReadValue(this, lwM2mTransportRequest, path);
}
if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getSwUpdate().initReadValue(this, path);
lwM2MClient.getSwUpdate().initReadValue(this, lwM2mTransportRequest, path);
}
/**
@ -733,13 +742,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
&& (convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) {
if (DOWNLOADED.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& lwM2MClient.getFwUpdate().conditionalFwExecuteStart()) {
lwM2MClient.getFwUpdate().executeFwSwWare();
lwM2MClient.getFwUpdate().executeFwSwWare(this, lwM2mTransportRequest);
} else if (UPDATING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& lwM2MClient.getFwUpdate().conditionalFwExecuteAfterSuccess()) {
lwM2MClient.getFwUpdate().finishFwSwUpdate(true);
lwM2MClient.getFwUpdate().finishFwSwUpdate(this, true);
} else if (UPDATING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& lwM2MClient.getFwUpdate().conditionalFwExecuteAfterError()) {
lwM2MClient.getFwUpdate().finishFwSwUpdate(false);
lwM2MClient.getFwUpdate().finishFwSwUpdate(this, false);
}
}
@ -758,13 +767,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
&& (convertPathFromObjectIdToIdVer(SW_RESULT_ID, registration).equals(path))) {
if (DOWNLOADED.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())
&& lwM2MClient.getSwUpdate().conditionalSwUpdateExecute()) {
lwM2MClient.getSwUpdate().executeFwSwWare();
lwM2MClient.getSwUpdate().executeFwSwWare(this, lwM2mTransportRequest);
} else if (UPDATING.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())
&& lwM2MClient.getSwUpdate().conditionalSwExecuteAfterSuccess()) {
lwM2MClient.getSwUpdate().finishFwSwUpdate(true);
lwM2MClient.getSwUpdate().finishFwSwUpdate(this, true);
} else if (UPDATING.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())
&& lwM2MClient.getSwUpdate().conditionalSwExecuteAfterError()) {
lwM2MClient.getSwUpdate().finishFwSwUpdate(false);
lwM2MClient.getSwUpdate().finishFwSwUpdate(this, false);
}
}
Set<String> paths = new HashSet<>();
@ -1418,7 +1427,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
lwM2MClient.getFwUpdate().setCurrentVersion(response.getVersion());
lwM2MClient.getFwUpdate().setCurrentTitle(response.getTitle());
lwM2MClient.getFwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.getFwUpdate().sendReadObserveInfo(serviceImpl);
lwM2MClient.getFwUpdate().sendReadObserveInfo(lwM2mTransportRequest);
} else {
log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}
@ -1447,7 +1456,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion());
lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle());
lwM2MClient.getSwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.getSwUpdate().sendReadObserveInfo(serviceImpl);
lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest);
} else {
log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}

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

@ -18,23 +18,23 @@ package org.thingsboard.server.transport.lwm2m.server;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.security.DefaultAuthorizer;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
@ -43,7 +43,6 @@ import javax.annotation.PreDestroy;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
@ -74,9 +73,10 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
@RequiredArgsConstructor
public class DefaultLwM2mTransportService implements LwM2MTransportService {
public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256};
public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256};
private PublicKey publicKey;
private PrivateKey privateKey;
private boolean pskMode = false;
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
@ -85,7 +85,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private final CaliforniumRegistrationStore registrationStore;
private final EditableSecurityStore securityStore;
private final LwM2mClientContext lwM2mClientContext;
private ScheduledExecutorService registrationStoreExecutor;
private final TbLwM2MDtlsCertificateVerifier certificateVerifier;
private final TbLwM2MAuthorizer authorizer;
private LeshanServer server;
@ -117,8 +118,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
}
private LeshanServer getLhServer() {
this.registrationStoreExecutor = Executors.newScheduledThreadPool(this.config.getRegistrationStorePoolSize(), ThingsBoardThreadFactory.forName("LwM2M registrationStore"));
LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(config.getHost(), config.getPort());
builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort());
@ -126,10 +125,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
/* Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/* InMemoryRegistrationStore(ScheduledExecutorService schedExecutor, long cleanPeriodInSec) */
InMemoryRegistrationStore registrationStore = new InMemoryRegistrationStore(this.registrationStoreExecutor, this.config.getCleanPeriodInSec());
builder.setRegistrationStore(registrationStore);
/* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort()));
@ -138,9 +133,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
config.setModelProvider(modelProvider);
builder.setObjectModelProvider(modelProvider);
/* Create credentials */
this.setServerWithCredentials(builder);
/* Set securityStore with new registrationStore */
builder.setSecurityStore(securityStore);
builder.setRegistrationStore(registrationStore);
@ -152,18 +144,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
dtlsConfig.setServerOnly(true);
dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers());
if (this.pskMode) {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256);
} else {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
}
/* Create credentials */
this.setServerWithCredentials(builder, dtlsConfig);
/* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig);
@ -172,40 +154,21 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
return builder.build();
}
private void setServerWithCredentials(LeshanServerBuilder builder) {
try {
if (config.getKeyStoreValue() != null) {
if (this.setBuilderX509(builder)) {
X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
/* Set securityStore with registrationStore*/
builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
@Override
protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
String expectedX509CommonName) {
return endpoint.startsWith(expectedX509CommonName);
}
}));
}
} else if (this.setServerRPK(builder)) {
this.infoPramsUri("RPK");
this.infoParamsServerKey(this.publicKey, this.privateKey);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for LWM2MServer");
this.pskMode = true;
this.infoPramsUri("PSK");
}
} catch (KeyStoreException ex) {
log.error("[{}] Unable to load X509 files server", ex.getMessage());
private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) {
if (config.getKeyStoreValue() != null && this.setBuilderX509(builder)) {
dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier);
builder.setAuthorizer(authorizer);
dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES);
} else if (this.setServerRPK(builder)) {
this.infoPramsUri("RPK");
this.infoParamsServerKey(this.publicKey, this.privateKey);
dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for LWM2MServer");
dtlsConfig.setSupportedCipherSuites(PSK_CIPHER_SUITES);
this.infoPramsUri("PSK");
}
}
@ -251,7 +214,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private boolean setServerRPK(LeshanServerBuilder builder) {
try {
this.generateKeyForRPK();
this.loadOrGenerateRPKKeys();
if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPublicKey(this.publicKey);
@ -264,7 +227,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
return false;
}
private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
private void loadOrGenerateRPKKeys() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
/* Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));

62
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java

@ -104,7 +104,7 @@ public class LwM2mTransportRequest {
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final LwM2mClientContext lwM2mClientContext;
private final DefaultLwM2MTransportMsgHandler serviceImpl;
private final DefaultLwM2MTransportMsgHandler handler;
@PostConstruct
public void init() {
@ -149,13 +149,13 @@ public class LwM2mTransportRequest {
else if (WRITE_UPDATE.name().equals(typeOper.name())) {
if (rpcRequest != null) {
String errorMsg = String.format("Path %s params is not valid", targetIdVer);
serviceImpl.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
}
}
else if (WRITE_REPLACE.name().equals(typeOper.name()) || EXECUTE.name().equals(typeOper.name()) ) {
if (rpcRequest != null) {
String errorMsg = String.format("Path %s object model is absent", targetIdVer);
serviceImpl.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
}
}
else if (!OBSERVE_CANCEL.name().equals(typeOper.name())) {
@ -163,12 +163,12 @@ public class LwM2mTransportRequest {
if (rpcRequest != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider());
String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null";
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
}
}
} else if (rpcRequest != null) {
String errorMsg = String.format("Path %s not found in object version", targetIdVer);
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
}
} else if (OBSERVE_READ_ALL.name().equals(typeOper.name()) || DISCOVER_All.name().equals(typeOper.name())) {
Set<String> paths;
@ -181,11 +181,11 @@ public class LwM2mTransportRequest {
paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet());
String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO,
typeOper.name(), paths);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
handler.sendLogsToThingsboard(msg, registration.getId());
}
if (rpcRequest != null) {
String valueMsg = String.format("Paths - %s", paths);
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE);
handler.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE);
}
} else if (OBSERVE_CANCEL.name().equals(typeOper.name())) {
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration);
@ -196,10 +196,10 @@ public class LwM2mTransportRequest {
} catch (Exception e) {
String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR,
typeOper.name(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
handler.sendLogsToThingsboard(msg, registration.getId());
if (rpcRequest != null) {
String errorMsg = String.format("Path %s type operation %s %s", targetIdVer, typeOper.name(), e.getMessage());
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
}
}
}
@ -326,31 +326,31 @@ public class LwM2mTransportRequest {
context.getServer().send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest);
} else {
String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(),
((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
handler.sendLogsToThingsboard(msg, registration.getId());
log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
/** Not Found */
if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR);
}
/** Not Found
set setClient_fw_info... = empty
**/
if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(serviceImpl, request.getPath().toString());
lwM2MClient.getFwUpdate().initReadValue(handler, request.getPath().toString());
}
if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getSwUpdate().initReadValue(serviceImpl, request.getPath().toString());
lwM2MClient.getSwUpdate().initReadValue(handler, request.getPath().toString());
}
if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
this.afterWriteFwSWUpdateError(registration, request, response.getErrorMessage());
@ -364,10 +364,10 @@ public class LwM2mTransportRequest {
set setClient_fw_info... = empty
**/
if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(serviceImpl, request.getPath().toString());
lwM2MClient.getFwUpdate().initReadValue(handler, request.getPath().toString());
}
if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getSwUpdate().initReadValue(serviceImpl, request.getPath().toString());
lwM2MClient.getSwUpdate().initReadValue(handler, request.getPath().toString());
}
if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
this.afterWriteFwSWUpdateError(registration, request, e.getMessage());
@ -376,14 +376,14 @@ public class LwM2mTransportRequest {
this.afterExecuteFwSwUpdateError(registration, request, e.getMessage());
}
if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s",
LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
handler.sendLogsToThingsboard(msg, registration.getId());
log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString());
if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR);
}
});
}
@ -425,11 +425,11 @@ public class LwM2mTransportRequest {
String patn = "/" + objectId + "/" + instanceId + "/" + resourceId;
String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client",
patn, type, value, e.toString());
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
handler.sendLogsToThingsboard(msg, registration.getId());
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString());
if (rpcRequest != null) {
String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value);
serviceImpl.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
handler.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
}
return null;
}
@ -458,17 +458,17 @@ public class LwM2mTransportRequest {
String pathIdVer = convertPathFromObjectIdToIdVer(path, registration);
String msgLog = "";
if (response instanceof ReadResponse) {
serviceImpl.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest);
handler.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest);
} else if (response instanceof DeleteResponse) {
log.warn("[{}] Path [{}] DeleteResponse 5_Send", pathIdVer, response);
} else if (response instanceof DiscoverResponse) {
String discoverValue = Link.serialize(((DiscoverResponse)response).getObjectLinks());
msgLog = String.format("%s: type operation: %s path: %s value: %s",
LOG_LW2M_INFO, DISCOVER.name(), request.getPath().toString(), discoverValue);
serviceImpl.sendLogsToThingsboard(msgLog, registration.getId());
handler.sendLogsToThingsboard(msgLog, registration.getId());
log.warn("DiscoverResponse: [{}]", (DiscoverResponse) response);
if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE);
handler.sentRpcRequest(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE);
}
} else if (response instanceof ExecuteResponse) {
log.warn("[{}] Path [{}] ExecuteResponse 7_Send", pathIdVer, response);
@ -477,16 +477,16 @@ public class LwM2mTransportRequest {
} else if (response instanceof WriteResponse) {
log.warn("[{}] Path [{}] WriteResponse 9_Send", pathIdVer, response);
this.infoWriteResponse(registration, response, request);
serviceImpl.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request);
handler.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request);
}
if (rpcRequest != null) {
if (response instanceof ExecuteResponse
|| response instanceof WriteAttributesResponse
|| response instanceof DeleteResponse) {
rpcRequest.setInfoMsg(null);
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null);
handler.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null);
} else if (response instanceof WriteResponse) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO);
handler.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO);
}
}
}
@ -519,7 +519,7 @@ public class LwM2mTransportRequest {
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value);
}
if (msg != null) {
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
handler.sendLogsToThingsboard(msg, registration.getId());
if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
this.afterWriteSuccessFwSwUpdate(registration, request);
}
@ -572,11 +572,11 @@ public class LwM2mTransportRequest {
}
private void afterObserveCancel(Registration registration, int observeCancelCnt, String observeCancelMsg, Lwm2mClientRpcRequest rpcRequest) {
serviceImpl.sendLogsToThingsboard(observeCancelMsg, registration.getId());
handler.sendLogsToThingsboard(observeCancelMsg, registration.getId());
log.warn("[{}]", observeCancelMsg);
if (rpcRequest != null) {
rpcRequest.setInfoMsg(String.format("Count: %d", observeCancelCnt));
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO);
handler.sentRpcRequest(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO);
}
}
}

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

@ -41,6 +41,7 @@ import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.ContentFormat;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
@ -106,21 +107,21 @@ public class LwM2mTransportServerHelper {
/**
* @return - sessionInfo after access connect client
*/
public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponse msg, long mostSignificantBits, long leastSignificantBits) {
return SessionInfoProto.newBuilder()
.setNodeId(context.getNodeId())
.setSessionIdMSB(mostSignificantBits)
.setSessionIdLSB(leastSignificantBits)
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits())
.setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits())
.setDeviceName(msg.getDeviceInfo().getDeviceName())
.setDeviceType(msg.getDeviceInfo().getDeviceType())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits())
.build();
}

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

@ -105,7 +105,7 @@ public class LwM2mTransportUtil {
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
public static final String
LOG_LW2M_TELEMETRY = "logLwm2m";
LOG_LW2M_TELEMETRY = "LwM2MLog";
public static final String LOG_LW2M_INFO = "info";
public static final String LOG_LW2M_ERROR = "error";
public static final String LOG_LW2M_WARN = "warn";

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

@ -31,10 +31,10 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
@ -89,7 +89,9 @@ public class LwM2mClient implements Cloneable {
@Getter
@Setter
private Registration registration;
private ValidateDeviceCredentialsResponseMsg credentialsResponse;
private ValidateDeviceCredentialsResponse credentials;
@Getter
private final Map<String, ResourceValue> resources;
@Getter
@ -106,11 +108,11 @@ public class LwM2mClient implements Cloneable {
return super.clone();
}
public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) {
public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) {
this.endpoint = endpoint;
this.identity = identity;
this.securityInfo = securityInfo;
this.credentialsResponse = credentialsResponse;
this.credentials = credentials;
this.delayedRequests = new ConcurrentHashMap<>();
this.pendingReadRequests = new CopyOnWriteArrayList<>();
this.resources = new ConcurrentHashMap<>();
@ -118,10 +120,11 @@ public class LwM2mClient implements Cloneable {
this.sessionId = sessionId;
this.init = false;
this.queuedRequests = new ConcurrentLinkedQueue<>();
this.fwUpdate = new LwM2mFwSwUpdate(this, FirmwareType.FIRMWARE);
this.swUpdate = new LwM2mFwSwUpdate(this, FirmwareType.SOFTWARE);
if (this.credentialsResponse != null && this.credentialsResponse.hasDeviceInfo()) {
this.session = createSession(nodeId, sessionId, credentialsResponse);
if (this.credentials != null && this.credentials.hasDeviceInfo()) {
this.session = createSession(nodeId, sessionId, credentials);
this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB());
this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB());
this.deviceName = session.getDeviceName();
@ -154,21 +157,21 @@ public class LwM2mClient implements Cloneable {
builder.setDeviceType(this.deviceProfileName);
}
private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponseMsg msg) {
private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponse msg) {
return SessionInfoProto.newBuilder()
.setNodeId(nodeId)
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits())
.setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits())
.setDeviceName(msg.getDeviceInfo().getDeviceName())
.setDeviceType(msg.getDeviceInfo().getDeviceType())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits())
.build();
}
@ -188,7 +191,7 @@ public class LwM2mClient implements Cloneable {
}
}
public Object getResourceValue (String pathRezIdVer, String pathRezId) {
public Object getResourceValue(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) {
return this.resources.get(pathRez).getLwM2mResource().isMultiInstances() ?

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

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Collection;
@ -59,4 +60,6 @@ public interface LwM2mClientContext {
Set<String> getSupportedIdVerInClient(Registration registration);
LwM2mClient getClientByDeviceId(UUID deviceId);
void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials);
}

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

@ -22,10 +22,10 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
@ -38,7 +38,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
@Slf4j
@ -112,7 +112,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override
public LwM2mClient fetchClientByEndpoint(String endpoint) {
EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
if (securityInfo.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
if (securityInfo.getSecurityMode() != null) {
if (securityInfo.getDeviceProfile() != null) {
UUID profileUuid = profileUpdate(securityInfo.getDeviceProfile())!= null ?
securityInfo.getDeviceProfile().getUuidId() : null;
@ -125,7 +125,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(),
securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(),
securityInfo.getMsg(), profileUuid, UUID.randomUUID());
} else if (securityInfo.getSecurityMode() == NO_SEC.code) {
} else if (NO_SEC.equals(securityInfo.getSecurityMode())) {
client = new LwM2mClient(context.getNodeId(), endpoint,
null, null,
securityInfo.getMsg(), profileUuid, UUID.randomUUID());
@ -142,6 +142,14 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
}
}
@Override
public void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials) {
LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID());
lwM2mClientsByEndpoint.put(registration.getEndpoint(), client);
lwM2mClientsByRegistrationId.put(registration.getId(), client);
profileUpdate(credentials.getDeviceProfile());
}
@Override
public Collection<LwM2mClient> getLwM2mClients() {
return lwM2mClientsByEndpoint.values();

80
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportRequest;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import java.util.ArrayList;
@ -97,7 +98,6 @@ public class LwM2mFwSwUpdate {
@Setter
private volatile boolean infoFwSwUpdate = false;
private final FirmwareType type;
private DefaultLwM2MTransportMsgHandler serviceImpl;
@Getter
LwM2mClient lwM2MClient;
@Getter
@ -113,7 +113,7 @@ public class LwM2mFwSwUpdate {
}
private void initPathId() {
if (FIRMWARE.equals(this.type) ) {
if (FIRMWARE.equals(this.type)) {
this.pathPackageId = FW_PACKAGE_ID;
this.pathStateId = FW_STATE_ID;
this.pathResultId = FW_RESULT_ID;
@ -121,7 +121,7 @@ public class LwM2mFwSwUpdate {
this.pathVerId = FW_VER_ID;
this.pathInstallId = FW_UPDATE_ID;
this.wUpdate = FW_UPDATE;
} else if (SOFTWARE.equals(this.type) ) {
} else if (SOFTWARE.equals(this.type)) {
this.pathPackageId = SW_PACKAGE_ID;
this.pathStateId = SW_UPDATE_STATE_ID;
this.pathResultId = SW_RESULT_ID;
@ -133,8 +133,7 @@ public class LwM2mFwSwUpdate {
}
}
public void initReadValue(DefaultLwM2MTransportMsgHandler serviceImpl, String pathIdVer) {
if (this.serviceImpl == null) this.serviceImpl = serviceImpl;
public void initReadValue(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, String pathIdVer) {
if (pathIdVer != null) {
this.pendingInfoRequestsStart.remove(pathIdVer);
}
@ -144,7 +143,7 @@ public class LwM2mFwSwUpdate {
boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart() :
this.conditionalSwUpdateStart();
if (conditionalStart) {
this.writeFwSwWare();
this.writeFwSwWare(handler, request);
}
}
}
@ -154,26 +153,26 @@ public class LwM2mFwSwUpdate {
* Send FsSw to Lwm2mClient:
* before operation Write: fw_state = DOWNLOADING
*/
private void writeFwSwWare() {
private void writeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) {
this.stateUpdate = FirmwareUpdateStatus.DOWNLOADING.name();
// this.observeStateUpdate();
this.sendLogs(WRITE_REPLACE.name(), LOG_LW2M_INFO, null);
this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null);
int chunkSize = 0;
int chunk = 0;
byte[] firmwareChunk = this.serviceImpl.firmwareDataCache.get(this.currentId.toString(), chunkSize, chunk);
byte[] firmwareChunk = handler.firmwareDataCache.get(this.currentId.toString(), chunkSize, chunk);
String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration());
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, this.serviceImpl.config.getTimeout(), null);
request.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, handler.config.getTimeout(), null);
}
public void sendLogs(String typeOper, String typeInfo, String msgError) {
this.sendSateOnThingsboard();
public void sendLogs(DefaultLwM2MTransportMsgHandler handler, String typeOper, String typeInfo, String msgError) {
this.sendSateOnThingsBoard(handler);
String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.",
typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate);
if (LOG_LW2M_ERROR.equals(typeInfo)) {
msg = String.format("%s Error: %s", msg, msgError);
}
serviceImpl.sendLogsToThingsboard(msg, lwM2MClient.getRegistration().getId());
handler.sendLogsToThingsboard(msg, lwM2MClient.getRegistration().getId());
}
@ -182,11 +181,11 @@ public class LwM2mFwSwUpdate {
* fw_state/sw_state = UPDATING
* send execute
*/
public void executeFwSwWare() {
this.setStateUpdate(UPDATING.name());
this.sendLogs(EXECUTE.name(), LOG_LW2M_INFO, null);
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(this.lwM2MClient.getRegistration(), this.pathInstallId, EXECUTE, ContentFormat.TLV.getName(),
null, 0, null);
public void executeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) {
this.setStateUpdate(UPDATING.name());
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null);
request.sendAllRequest(this.lwM2MClient.getRegistration(), this.pathInstallId, EXECUTE, ContentFormat.TLV.getName(),
null, 0, null);
}
@ -219,7 +218,7 @@ public class LwM2mFwSwUpdate {
*/
public boolean conditionalFwExecuteStart() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.INITIAL.code == updateResult;
return LwM2mTransportUtil.UpdateResultFw.INITIAL.code == updateResult;
}
/**
@ -228,7 +227,7 @@ public class LwM2mFwSwUpdate {
*/
public boolean conditionalFwExecuteAfterSuccess() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult;
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult;
}
/**
@ -237,7 +236,7 @@ public class LwM2mFwSwUpdate {
*/
public boolean conditionalFwExecuteAfterError() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult;
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult;
}
/**
@ -283,21 +282,20 @@ public class LwM2mFwSwUpdate {
* --- send to telemetry ( key - this is name Update Result in model) (
* -- fw_state/sw_state = FAILED
*/
public void finishFwSwUpdate(boolean success) {
public void finishFwSwUpdate(DefaultLwM2MTransportMsgHandler handler, boolean success) {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
String value = FIRMWARE.equals(this.type) ? LwM2mTransportUtil.UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type :
LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type;
String key = splitCamelCaseString((String) this.lwM2MClient.getResourceName (null, this.pathResultId));
String key = splitCamelCaseString((String) this.lwM2MClient.getResourceName(null, this.pathResultId));
if (success) {
this.stateUpdate = FirmwareUpdateStatus.UPDATED.name();
this.sendLogs(EXECUTE.name(), LOG_LW2M_INFO, null);
}
else {
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null);
} else {
this.stateUpdate = FirmwareUpdateStatus.FAILED.name();
this.sendLogs(EXECUTE.name(), LOG_LW2M_ERROR, value);
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, value);
}
this.serviceImpl.helper.sendParametersOnThingsboardTelemetry(
this.serviceImpl.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession());
handler.helper.sendParametersOnThingsboardTelemetry(
handler.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession());
}
/**
@ -306,40 +304,40 @@ public class LwM2mFwSwUpdate {
*/
public boolean conditionalSwExecuteAfterSuccess() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code == updateResult;
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* >= 50 - error "NOT_ENOUGH_STORAGE"
*/
public boolean conditionalSwExecuteAfterError() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult;
return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult;
}
private void observeStateUpdate() {
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(),
private void observeStateUpdate(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) {
request.sendAllRequest(lwM2MClient.getRegistration(),
convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE,
null, null, 0, null);
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(),
request.sendAllRequest(lwM2MClient.getRegistration(),
convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE,
null, null, 0, null);
}
public void sendSateOnThingsboard() {
public void sendSateOnThingsBoard(DefaultLwM2MTransportMsgHandler handler) {
if (StringUtils.trimToNull(this.stateUpdate) != null) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>();
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(this.type, STATE));
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(stateUpdate);
result.add(kvProto.build());
this.serviceImpl.helper.sendParametersOnThingsboardTelemetry(result,
this.serviceImpl.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration()));
handler.helper.sendParametersOnThingsboardTelemetry(result,
handler.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration()));
}
}
public void sendReadObserveInfo(DefaultLwM2MTransportMsgHandler serviceImpl) {
public void sendReadObserveInfo(LwM2mTransportRequest request) {
this.infoFwSwUpdate = true;
this.serviceImpl = this.serviceImpl == null ? serviceImpl : this.serviceImpl;
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathVerId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
@ -349,7 +347,7 @@ public class LwM2mFwSwUpdate {
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathResultId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.forEach(pathIdVer -> {
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(this.lwM2MClient.getRegistration(), pathIdVer, OBSERVE, ContentFormat.TLV.getName(),
request.sendAllRequest(this.lwM2MClient.getRegistration(), pathIdVer, OBSERVE, ContentFormat.TLV.getName(),
null, 0, null);
});

40
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.store;
import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo;
import java.util.concurrent.ConcurrentHashMap;
public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore {
private final ConcurrentHashMap<String, TbX509DtlsSessionInfo> store = new ConcurrentHashMap<>();
@Override
public void put(String endpoint, TbX509DtlsSessionInfo msg) {
store.put(endpoint, msg);
}
@Override
public TbX509DtlsSessionInfo get(String endpoint) {
return store.get(endpoint);
}
@Override
public void remove(String endpoint) {
store.remove(endpoint);
}
}

66
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java

@ -0,0 +1,66 @@
/**
* 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.store;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo;
public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore {
private static final String SESSION_EP = "SESSION#EP#";
RedisConnectionFactory connectionFactory;
public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) {
this.connectionFactory = redisConnectionFactory;
}
@Override
public void put(String endpoint, TbX509DtlsSessionInfo msg) {
try (var c = connectionFactory.getConnection()) {
var msgJson = JacksonUtil.convertValue(msg, JsonNode.class);
if (msgJson != null) {
c.set(getKey(endpoint), msgJson.toString().getBytes());
} else {
throw new RuntimeException("Problem with serialization of message: " + msg.toString());
}
}
}
@Override
public TbX509DtlsSessionInfo get(String endpoint) {
try (var c = connectionFactory.getConnection()) {
var data = c.get(getKey(endpoint));
if (data != null) {
return JacksonUtil.fromString(new String(data), TbX509DtlsSessionInfo.class);
} else {
return null;
}
}
}
@Override
public void remove(String endpoint) {
try (var c = connectionFactory.getConnection()) {
c.del(getKey(endpoint));
}
}
private byte[] getKey(String endpoint) {
return (SESSION_EP + endpoint).getBytes();
}
}

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.store;
import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo;
public interface TbLwM2MDtlsSessionStore {
void put(String endpoint, TbX509DtlsSessionInfo msg);
TbX509DtlsSessionInfo get(String endpoint);
void remove(String endpoint);
}

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

@ -26,9 +26,7 @@ import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.core.util.Validate;
import org.eclipse.leshan.server.californium.observation.ObserveUtil;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.redis.JedisLock;
import org.eclipse.leshan.server.redis.RedisRegistrationStore;
import org.eclipse.leshan.server.redis.SingleInstanceJedisLock;
import org.eclipse.leshan.server.redis.serialization.ObservationSerDes;
import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes;
import org.eclipse.leshan.server.registration.Deregistration;
@ -38,11 +36,12 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.Transaction;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.integration.redis.util.RedisLockRegistry;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@ -50,13 +49,14 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -92,7 +92,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
private final int cleanLimit; // maximum number to clean in a clean period
private final long gracePeriod; // in seconds
private final JedisLock lock;
private final RedisLockRegistry redisLock;
public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory) {
this(connectionFactory, DEFAULT_CLEAN_PERIOD, DEFAULT_GRACE_PERIOD, DEFAULT_CLEAN_LIMIT); // default clean period 60s
@ -106,20 +106,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec,
long lifetimeGracePeriodInSec, int cleanLimit) {
this(connectionFactory, schedExecutor, cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, new SingleInstanceJedisLock());
}
/**
* @since 1.1
*/
public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec,
long lifetimeGracePeriodInSec, int cleanLimit, JedisLock redisLock) {
this.connectionFactory = connectionFactory;
this.schedExecutor = schedExecutor;
this.cleanPeriod = cleanPeriodInSec;
this.cleanLimit = cleanLimit;
this.gracePeriod = lifetimeGracePeriodInSec;
this.lock = redisLock;
this.redisLock = new RedisLockRegistry(connectionFactory, "Registration");
}
/* *************** Redis Key utility function **************** */
@ -135,76 +127,79 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
return (prefix + registrationID).getBytes();
}
private byte[] toLockKey(String endpoint) {
return toKey(LOCK_EP, endpoint);
private String toLockKey(String endpoint) {
return new String(toKey(LOCK_EP, endpoint));
}
private byte[] toLockKey(byte[] endpoint) {
return toKey(LOCK_EP.getBytes(UTF_8), endpoint);
private String toLockKey(byte[] endpoint) {
return new String(toKey(LOCK_EP.getBytes(UTF_8), endpoint));
}
/* *************** Leshan Registration API **************** */
@Override
public Deregistration addRegistration(Registration registration) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] lockValue = null;
byte[] lockKey = toLockKey(registration.getEndpoint());
Lock lock = null;
try (var connection = connectionFactory.getConnection()) {
String lockKey = toLockKey(registration.getEndpoint());
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
// add registration
byte[] k = toEndpointKey(registration.getEndpoint());
byte[] old = j.getSet(k, serializeReg(registration));
byte[] old = connection.getSet(k, serializeReg(registration));
// add registration: secondary indexes
byte[] regid_idx = toRegIdKey(registration.getId());
j.set(regid_idx, registration.getEndpoint().getBytes(UTF_8));
connection.set(regid_idx, registration.getEndpoint().getBytes(UTF_8));
byte[] addr_idx = toRegAddrKey(registration.getSocketAddress());
j.set(addr_idx, registration.getEndpoint().getBytes(UTF_8));
connection.set(addr_idx, registration.getEndpoint().getBytes(UTF_8));
// Add or update expiration
addOrUpdateExpiration(j, registration);
addOrUpdateExpiration(connection, registration);
if (old != null) {
Registration oldRegistration = deserializeReg(old);
// remove old secondary index
if (!registration.getId().equals(oldRegistration.getId()))
j.del(toRegIdKey(oldRegistration.getId()));
connection.del(toRegIdKey(oldRegistration.getId()));
if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) {
removeAddrIndex(j, oldRegistration);
removeAddrIndex(connection, oldRegistration);
}
// remove old observation
Collection<Observation> obsRemoved = unsafeRemoveAllObservations(j, oldRegistration.getId());
Collection<Observation> obsRemoved = unsafeRemoveAllObservations(connection, oldRegistration.getId());
return new Deregistration(oldRegistration, obsRemoved);
}
return null;
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
}
@Override
public UpdatedRegistration updateRegistration(RegistrationUpdate update) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
Lock lock = null;
try (var connection = connectionFactory.getConnection()) {
// Fetch the registration ep by registration ID index
byte[] ep = j.get(toRegIdKey(update.getRegistrationId()));
byte[] ep = connection.get(toRegIdKey(update.getRegistrationId()));
if (ep == null) {
return null;
}
byte[] lockValue = null;
byte[] lockKey = toLockKey(ep);
String lockKey = toLockKey(ep);
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
// Fetch the registration
byte[] data = j.get(toEndpointKey(ep));
byte[] data = connection.get(toEndpointKey(ep));
if (data == null) {
return null;
}
@ -214,40 +209,42 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
Registration updatedRegistration = update.update(r);
// Store the new registration
j.set(toEndpointKey(updatedRegistration.getEndpoint()), serializeReg(updatedRegistration));
connection.set(toEndpointKey(updatedRegistration.getEndpoint()), serializeReg(updatedRegistration));
// Add or update expiration
addOrUpdateExpiration(j, updatedRegistration);
addOrUpdateExpiration(connection, updatedRegistration);
// Update secondary index :
// If registration is already associated to this address we don't care as we only want to keep the most
// recent binding.
byte[] addr_idx = toRegAddrKey(updatedRegistration.getSocketAddress());
j.set(addr_idx, updatedRegistration.getEndpoint().getBytes(UTF_8));
connection.set(addr_idx, updatedRegistration.getEndpoint().getBytes(UTF_8));
if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) {
removeAddrIndex(j, r);
removeAddrIndex(connection, r);
}
return new UpdatedRegistration(r, updatedRegistration);
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
}
@Override
public Registration getRegistration(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
return getRegistration(j, registrationId);
try (var connection = connectionFactory.getConnection()) {
return getRegistration(connection, registrationId);
}
}
@Override
public Registration getRegistrationByEndpoint(String endpoint) {
Validate.notNull(endpoint);
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] data = j.get(toEndpointKey(endpoint));
try (var connection = connectionFactory.getConnection()) {
byte[] data = connection.get(toEndpointKey(endpoint));
if (data == null) {
return null;
}
@ -258,12 +255,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public Registration getRegistrationByAdress(InetSocketAddress address) {
Validate.notNull(address);
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] ep = j.get(toRegAddrKey(address));
try (var connection = connectionFactory.getConnection()) {
byte[] ep = connection.get(toRegAddrKey(address));
if (ep == null) {
return null;
}
byte[] data = j.get(toEndpointKey(ep));
byte[] data = connection.get(toEndpointKey(ep));
if (data == null) {
return null;
}
@ -273,140 +270,99 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public Iterator<Registration> getAllRegistrations() {
return new TbLwM2mRedisRegistrationStore.RedisIterator(connectionFactory, new ScanParams().match(REG_EP + "*").count(100));
}
protected class RedisIterator implements Iterator<Registration> {
private final RedisConnectionFactory connectionFactory;
private final ScanParams scanParams;
private String cursor;
private List<Registration> scanResult;
public RedisIterator(RedisConnectionFactory connectionFactory, ScanParams scanParams) {
this.connectionFactory = connectionFactory;
this.scanParams = scanParams;
// init scan result
scanNext("0");
}
private void scanNext(String cursor) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
do {
ScanResult<byte[]> sr = j.scan(cursor.getBytes(), scanParams);
this.scanResult = new ArrayList<>();
if (sr.getResult() != null && !sr.getResult().isEmpty()) {
for (byte[] value : j.mget(sr.getResult().toArray(new byte[][]{}))) {
this.scanResult.add(deserializeReg(value));
}
}
cursor = sr.getCursor();
} while (!"0".equals(cursor) && scanResult.isEmpty());
this.cursor = cursor;
}
}
@Override
public boolean hasNext() {
if (!scanResult.isEmpty()) {
return true;
}
if ("0".equals(cursor)) {
// no more elements to scan
return false;
}
// read more elements
scanNext(cursor);
return !scanResult.isEmpty();
}
@Override
public Registration next() {
if (!hasNext()) {
throw new NoSuchElementException();
try (var connection = connectionFactory.getConnection()) {
Collection<Registration> list = new LinkedList<>();
ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(REG_EP + "*").build();
List<Cursor<byte[]>> scans = new ArrayList<>();
if (connection instanceof RedisClusterConnection) {
((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> {
scans.add(((RedisClusterConnection) connection).scan(node, scanOptions));
});
} else {
scans.add(connection.scan(scanOptions));
}
return scanResult.remove(0);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
scans.forEach(scan -> {
scan.forEachRemaining(key -> {
byte[] element = connection.get(key);
list.add(deserializeReg(element));
});
});
return list.iterator();
}
}
@Override
public Deregistration removeRegistration(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
return removeRegistration(j, registrationId, false);
try (var connection = connectionFactory.getConnection()) {
return removeRegistration(connection, registrationId, false);
}
}
private Deregistration removeRegistration(Jedis j, String registrationId, boolean removeOnlyIfNotAlive) {
private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) {
// fetch the client ep by registration ID index
byte[] ep = j.get(toRegIdKey(registrationId));
byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) {
return null;
}
byte[] lockValue = null;
byte[] lockKey = toLockKey(ep);
Lock lock = null;
String lockKey = toLockKey(ep);
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
// fetch the client
byte[] data = j.get(toEndpointKey(ep));
byte[] data = connection.get(toEndpointKey(ep));
if (data == null) {
return null;
}
Registration r = deserializeReg(data);
if (!removeOnlyIfNotAlive || !r.isAlive(gracePeriod)) {
long nbRemoved = j.del(toRegIdKey(r.getId()));
long nbRemoved = connection.del(toRegIdKey(r.getId()));
if (nbRemoved > 0) {
j.del(toEndpointKey(r.getEndpoint()));
Collection<Observation> obsRemoved = unsafeRemoveAllObservations(j, r.getId());
removeAddrIndex(j, r);
removeExpiration(j, r);
connection.del(toEndpointKey(r.getEndpoint()));
Collection<Observation> obsRemoved = unsafeRemoveAllObservations(connection, r.getId());
removeAddrIndex(connection, r);
removeExpiration(connection, r);
return new Deregistration(r, obsRemoved);
}
}
return null;
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
private void removeAddrIndex(Jedis j, Registration registration) {
private void removeAddrIndex(RedisConnection connection, Registration registration) {
// Watch the key to remove.
byte[] regAddrKey = toRegAddrKey(registration.getSocketAddress());
j.watch(regAddrKey);
connection.watch(regAddrKey);
byte[] epFromAddr = j.get(regAddrKey);
byte[] epFromAddr = connection.get(regAddrKey);
// Delete the key if needed.
if (Arrays.equals(epFromAddr, registration.getEndpoint().getBytes(UTF_8))) {
// Try to delete the key
Transaction transaction = j.multi();
transaction.del(regAddrKey);
transaction.exec();
connection.multi();
connection.del(regAddrKey);
connection.exec();
// if transaction failed this is not an issue as the socket address is probably reused and we don't neeed to
// delete it anymore.
} else {
// the key must not be deleted.
j.unwatch();
connection.unwatch();
}
}
private void addOrUpdateExpiration(Jedis j, Registration registration) {
j.zadd(EXP_EP, registration.getExpirationTimeStamp(gracePeriod), registration.getEndpoint().getBytes(UTF_8));
private void addOrUpdateExpiration(RedisConnection connection, Registration registration) {
connection.zAdd(EXP_EP, registration.getExpirationTimeStamp(gracePeriod), registration.getEndpoint().getBytes(UTF_8));
}
private void removeExpiration(Jedis j, Registration registration) {
j.zrem(EXP_EP, registration.getEndpoint().getBytes(UTF_8));
private void removeExpiration(RedisConnection connection, Registration registration) {
connection.zRem(EXP_EP, registration.getEndpoint().getBytes(UTF_8));
}
private byte[] toRegIdKey(String registrationId) {
@ -441,33 +397,35 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
*/
@Override
public Collection<Observation> addObservation(String registrationId, Observation observation) {
List<Observation> removed = new ArrayList<>();
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
try (var connection = connectionFactory.getConnection()) {
// fetch the client ep by registration ID index
byte[] ep = j.get(toRegIdKey(registrationId));
byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) {
return null;
}
byte[] lockValue = null;
byte[] lockKey = toLockKey(ep);
Lock lock = null;
String lockKey = toLockKey(ep);
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
// cancel existing observations for the same path and registration id.
for (Observation obs : getObservations(j, registrationId)) {
for (Observation obs : getObservations(connection, registrationId)) {
if (observation.getPath().equals(obs.getPath())
&& !Arrays.equals(observation.getId(), obs.getId())) {
removed.add(obs);
unsafeRemoveObservation(j, registrationId, obs.getId());
unsafeRemoveObservation(connection, registrationId, obs.getId());
}
}
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
return removed;
@ -475,29 +433,32 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public Observation removeObservation(String registrationId, byte[] observationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
try (var connection = connectionFactory.getConnection()) {
// fetch the client ep by registration ID index
byte[] ep = j.get(toRegIdKey(registrationId));
byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) {
return null;
}
// remove observation
byte[] lockValue = null;
byte[] lockKey = toLockKey(ep);
Lock lock = null;
String lockKey = toLockKey(ep);
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
Observation observation = build(get(new Token(observationId)));
if (observation != null && registrationId.equals(observation.getRegistrationId())) {
unsafeRemoveObservation(j, registrationId, observationId);
unsafeRemoveObservation(connection, registrationId, observationId);
return observation;
}
return null;
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
}
@ -509,15 +470,15 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public Collection<Observation> getObservations(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
return getObservations(j, registrationId);
try (var connection = connectionFactory.getConnection()) {
return getObservations(connection, registrationId);
}
}
private Collection<Observation> getObservations(Jedis j, String registrationId) {
private Collection<Observation> getObservations(RedisConnection connection, String registrationId) {
Collection<Observation> result = new ArrayList<>();
for (byte[] token : j.lrange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) {
byte[] obs = j.get(toKey(OBS_TKN, token));
for (byte[] token : connection.lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) {
byte[] obs = connection.get(toKey(OBS_TKN, token));
if (obs != null) {
result.add(build(deserializeObs(obs)));
}
@ -527,22 +488,24 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public Collection<Observation> removeObservations(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
try (var connection = connectionFactory.getConnection()) {
// check registration exists
Registration registration = getRegistration(j, registrationId);
Registration registration = getRegistration(connection, registrationId);
if (registration == null)
return Collections.emptyList();
// get endpoint and create lock
String endpoint = registration.getEndpoint();
byte[] lockValue = null;
byte[] lockKey = toKey(LOCK_EP, endpoint);
Lock lock = null;
String lockKey = toLockKey(endpoint);
try {
lockValue = lock.acquire(j, lockKey);
return unsafeRemoveAllObservations(j, registrationId);
lock = redisLock.obtain(lockKey);
lock.lock();
return unsafeRemoveAllObservations(connection, registrationId);
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
}
@ -565,31 +528,32 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
String endpoint = ObserveUtil.validateCoapObservation(obs);
org.eclipse.californium.core.observe.Observation previousObservation = null;
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] lockValue = null;
byte[] lockKey = toKey(LOCK_EP, endpoint);
try (var connection = connectionFactory.getConnection()) {
Lock lock = null;
String lockKey = toLockKey(endpoint);
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
String registrationId = ObserveUtil.extractRegistrationId(obs);
if (!j.exists(toRegIdKey(registrationId)))
if (!connection.exists(toRegIdKey(registrationId)))
throw new ObservationStoreException("no registration for this Id");
byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes());
byte[] serializeObs = serializeObs(obs);
byte[] previousValue;
if (ifAbsent) {
previousValue = j.get(key);
previousValue = connection.get(key);
if (previousValue == null || previousValue.length == 0) {
j.set(key, serializeObs);
connection.set(key, serializeObs);
} else {
return deserializeObs(previousValue);
}
} else {
previousValue = j.getSet(key, serializeObs);
previousValue = connection.getSet(key, serializeObs);
}
// secondary index to get the list by registrationId
j.lpush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes());
connection.lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes());
// log any collisions
if (previousValue != null && previousValue.length != 0) {
@ -599,7 +563,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
previousObservation.getRequest(), obs.getRequest());
}
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
return previousObservation;
@ -607,17 +573,17 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public void remove(Token token) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
try (var connection = connectionFactory.getConnection()) {
byte[] tokenKey = toKey(OBS_TKN, token.getBytes());
// fetch the observation by token
byte[] serializedObs = j.get(tokenKey);
byte[] serializedObs = connection.get(tokenKey);
if (serializedObs == null)
return;
org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs);
String registrationId = ObserveUtil.extractRegistrationId(obs);
Registration registration = getRegistration(j, registrationId);
Registration registration = getRegistration(connection, registrationId);
if (registration == null) {
LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(),
registrationId);
@ -625,14 +591,17 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
}
String endpoint = registration.getEndpoint();
byte[] lockValue = null;
byte[] lockKey = toKey(LOCK_EP, endpoint);
Lock lock = null;
String lockKey = toLockKey(endpoint);
try {
lockValue = lock.acquire(j, lockKey);
lock = redisLock.obtain(lockKey);
lock.lock();
unsafeRemoveObservation(j, registrationId, token.getBytes());
unsafeRemoveObservation(connection, registrationId, token.getBytes());
} finally {
lock.release(j, lockKey, lockValue);
if (lock != null) {
lock.unlock();
}
}
}
@ -640,8 +609,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public org.eclipse.californium.core.observe.Observation get(Token token) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] obs = j.get(toKey(OBS_TKN, token.getBytes()));
try (var connection = connectionFactory.getConnection()) {
byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes()));
if (obs == null) {
return null;
} else {
@ -652,12 +621,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
/* *************** Observation utility functions **************** */
private Registration getRegistration(Jedis j, String registrationId) {
byte[] ep = j.get(toRegIdKey(registrationId));
private Registration getRegistration(RedisConnection connection, String registrationId) {
byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) {
return null;
}
byte[] data = j.get(toEndpointKey(ep));
byte[] data = connection.get(toEndpointKey(ep));
if (data == null) {
return null;
}
@ -665,25 +634,25 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
return deserializeReg(data);
}
private void unsafeRemoveObservation(Jedis j, String registrationId, byte[] observationId) {
if (j.del(toKey(OBS_TKN, observationId)) > 0L) {
j.lrem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId);
private void unsafeRemoveObservation(RedisConnection connection, String registrationId, byte[] observationId) {
if (connection.del(toKey(OBS_TKN, observationId)) > 0L) {
connection.lRem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId);
}
}
private Collection<Observation> unsafeRemoveAllObservations(Jedis j, String registrationId) {
private Collection<Observation> unsafeRemoveAllObservations(RedisConnection connection, String registrationId) {
Collection<Observation> removed = new ArrayList<>();
byte[] regIdKey = toKey(OBS_TKNS_REGID_IDX, registrationId);
// fetch all observations by token
for (byte[] token : j.lrange(regIdKey, 0, -1)) {
byte[] obs = j.get(toKey(OBS_TKN, token));
for (byte[] token : connection.lRange(regIdKey, 0, -1)) {
byte[] obs = connection.get(toKey(OBS_TKN, token));
if (obs != null) {
removed.add(build(deserializeObs(obs)));
}
j.del(toKey(OBS_TKN, token));
connection.del(toKey(OBS_TKN, token));
}
j.del(regIdKey);
connection.del(regIdKey);
return removed;
}
@ -754,14 +723,14 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override
public void run() {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
Set<byte[]> endpointsExpired = j.zrangeByScore(EXP_EP, Double.NEGATIVE_INFINITY,
try (var connection = connectionFactory.getConnection()) {
Set<byte[]> endpointsExpired = connection.zRangeByScore(EXP_EP, Double.NEGATIVE_INFINITY,
System.currentTimeMillis(), 0, cleanLimit);
for (byte[] endpoint : endpointsExpired) {
Registration r = deserializeReg(j.get(toEndpointKey(endpoint)));
Registration r = deserializeReg(connection.get(toEndpointKey(endpoint)));
if (!r.isAlive(gracePeriod)) {
Deregistration dereg = removeRegistration(j, r.getId(), true);
Deregistration dereg = removeRegistration(connection, r.getId(), true);
if (dereg != null)
expirationListener.registrationExpired(dereg.getRegistration(), dereg.getObservations());
}

64
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java

@ -20,13 +20,15 @@ import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.server.security.SecurityStoreListener;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
private static final String SEC_EP = "SEC#EP#";
@ -42,8 +44,8 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override
public SecurityInfo getByEndpoint(String endpoint) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] data = j.get((SEC_EP + endpoint).getBytes());
try (var connection = connectionFactory.getConnection()) {
byte[] data = connection.get((SEC_EP + endpoint).getBytes());
if (data == null) {
return null;
} else {
@ -54,12 +56,12 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override
public SecurityInfo getByIdentity(String identity) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
String ep = j.hget(PSKID_SEC, identity);
try (var connection = connectionFactory.getConnection()) {
byte[] ep = connection.hGet(PSKID_SEC.getBytes(), identity.getBytes());
if (ep == null) {
return null;
} else {
byte[] data = j.get((SEC_EP + ep).getBytes());
byte[] data = connection.get((SEC_EP + new String(ep)).getBytes());
if (data == null) {
return null;
} else {
@ -71,18 +73,24 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override
public Collection<SecurityInfo> getAll() {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
ScanParams params = new ScanParams().match(SEC_EP + "*").count(100);
try (var connection = connectionFactory.getConnection()) {
Collection<SecurityInfo> list = new LinkedList<>();
String cursor = "0";
do {
ScanResult<byte[]> res = j.scan(cursor.getBytes(), params);
for (byte[] key : res.getResult()) {
byte[] element = j.get(key);
ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build();
List<Cursor<byte[]>> scans = new ArrayList<>();
if (connection instanceof RedisClusterConnection) {
((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> {
scans.add(((RedisClusterConnection) connection).scan(node, scanOptions));
});
} else {
scans.add(connection.scan(scanOptions));
}
scans.forEach(scan -> {
scan.forEachRemaining(key -> {
byte[] element = connection.get(key);
list.add(deserialize(element));
}
cursor = res.getCursor();
} while (!"0".equals(cursor));
});
});
return list;
}
}
@ -90,21 +98,21 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override
public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException {
byte[] data = serialize(info);
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
try (var connection = connectionFactory.getConnection()) {
if (info.getIdentity() != null) {
// populate the secondary index (security info by PSK id)
String oldEndpoint = j.hget(PSKID_SEC, info.getIdentity());
if (oldEndpoint != null && !oldEndpoint.equals(info.getEndpoint())) {
String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes()));
if (!oldEndpoint.equals(info.getEndpoint())) {
throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used");
}
j.hset(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes());
connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes());
}
byte[] previousData = j.getSet((SEC_EP + info.getEndpoint()).getBytes(), data);
byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data);
SecurityInfo previous = previousData == null ? null : deserialize(previousData);
String previousIdentity = previous == null ? null : previous.getIdentity();
if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) {
j.hdel(PSKID_SEC, previousIdentity);
connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes());
}
return previous;
@ -113,15 +121,15 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override
public SecurityInfo remove(String endpoint, boolean infosAreCompromised) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
byte[] data = j.get((SEC_EP + endpoint).getBytes());
try (var connection = connectionFactory.getConnection()) {
byte[] data = connection.get((SEC_EP + endpoint).getBytes());
if (data != null) {
SecurityInfo info = deserialize(data);
if (info.getIdentity() != null) {
j.hdel(PSKID_SEC.getBytes(), info.getIdentity().getBytes());
connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes());
}
j.del((SEC_EP + endpoint).getBytes());
connection.del((SEC_EP + endpoint).getBytes());
if (listener != null) {
listener.securityInfoRemoved(infosAreCompromised, info);
}

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

@ -20,12 +20,16 @@ import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.server.security.SecurityStoreListener;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import java.util.Collection;
@Slf4j
@Component
@TbLwM2mTransportComponent
public class TbLwM2mSecurityStore implements EditableSecurityStore {
private final LwM2mClientContext clientContext;

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java

@ -26,6 +26,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import java.util.Optional;
@ -37,6 +38,9 @@ public class TbLwM2mStoreFactory {
@Autowired(required = false)
private Optional<TBRedisCacheConfiguration> redisConfiguration;
@Autowired
private LwM2MTransportServerConfig config;
@Autowired
@Lazy
private LwM2mClientContext clientContext;
@ -47,7 +51,7 @@ public class TbLwM2mStoreFactory {
@Bean
private CaliforniumRegistrationStore registrationStore() {
return redisConfiguration.isPresent() && useRedis ?
new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore();
new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec());
}
@Bean
@ -56,4 +60,10 @@ public class TbLwM2mStoreFactory {
new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new InMemorySecurityStore());
}
@Bean
private TbLwM2MDtlsSessionStore sessionStore() {
return redisConfiguration.isPresent() && useRedis ?
new TbLwM2MDtlsSessionRedisStore(redisConfiguration.get().redisConnectionFactory()) : new TbL2M2MDtlsSessionInMemoryStore();
}
}

3
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -468,9 +468,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
deviceSessionCtx.getPayloadAdaptor()
.convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type)
.ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
if (firmwareChunk != null && chunkSize != firmwareChunk.length) {
scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.trace("[{}] Failed to send firmware response!", sessionId, e);
}

4
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -44,7 +44,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
/**
@ -156,7 +156,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) {
return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
}
public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {

4
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java

@ -39,7 +39,7 @@ import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionConte
import java.util.Optional;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT;
@Component
@Slf4j
@ -169,7 +169,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) throws AdaptorException {
return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk));
}
@Override

9
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java

@ -256,9 +256,12 @@ public class GatewaySessionHandler {
log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType);
SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo();
transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx);
transportService.process(deviceSessionInfo, DefaultTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null);
transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null);
transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder()
.setSessionInfo(deviceSessionInfo)
.setSessionEvent(DefaultTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN))
.setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build())
.setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().build())
.build(), null);
}
futureToSet.set(devices.get(deviceName));
deviceFutures.remove(deviceName);

5
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.service.SessionMetaData;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
@ -79,7 +80,7 @@ public interface TransportService {
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback);
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
@ -112,6 +113,8 @@ public interface TransportService {
void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
void process(TransportToDeviceActorMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback<GetFirmwareResponseMsg> callback);
SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);

63
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -226,21 +226,33 @@ public class JsonConverter {
}
private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) {
if (value.getAsString().contains(".")) {
return KeyValueProto.newBuilder()
.setKey(key)
.setType(KeyValueType.DOUBLE_V)
.setDoubleV(value.getAsDouble())
.build();
} else {
String valueAsString = value.getAsString();
KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key);
var bd = new BigDecimal(valueAsString);
if (bd.stripTrailingZeros().scale() <= 0 && !isSimpleDouble(valueAsString)) {
try {
long longValue = Long.parseLong(value.getAsString());
return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V)
.setLongV(longValue).build();
} catch (NumberFormatException e) {
return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build();
} catch (ArithmeticException e) {
if (isTypeCastEnabled) {
return builder.setType(KeyValueType.STRING_V).setStringV(bd.toPlainString()).build();
} else {
throw new JsonSyntaxException("Big integer values are not supported!");
}
}
} else {
if (bd.scale() <= 16) {
return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build();
} else if (isTypeCastEnabled) {
return builder.setType(KeyValueType.STRING_V).setStringV(bd.toPlainString()).build();
} else {
throw new JsonSyntaxException("Big integer values are not supported!");
}
}
}
private static boolean isSimpleDouble(String valueAsString) {
return valueAsString.contains(".") && !valueAsString.contains("E") && !valueAsString.contains("e");
}
public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException {
@ -251,24 +263,23 @@ public class JsonConverter {
private static void parseNumericValue(List<KvEntry> result, Entry<String, JsonElement> valueEntry, JsonPrimitive value) {
String valueAsString = value.getAsString();
String key = valueEntry.getKey();
if (valueAsString.contains("e") || valueAsString.contains("E")) {
var bd = new BigDecimal(valueAsString);
if (bd.stripTrailingZeros().scale() <= 0) {
try {
result.add(new LongDataEntry(key, bd.longValueExact()));
} catch (ArithmeticException e) {
result.add(new DoubleDataEntry(key, bd.doubleValue()));
var bd = new BigDecimal(valueAsString);
if (bd.stripTrailingZeros().scale() <= 0 && !isSimpleDouble(valueAsString)) {
try {
result.add(new LongDataEntry(key, bd.longValueExact()));
} catch (ArithmeticException e) {
if (isTypeCastEnabled) {
result.add(new StringDataEntry(key, bd.toPlainString()));
} else {
throw new JsonSyntaxException("Big integer values are not supported!");
}
} else {
result.add(new DoubleDataEntry(key, bd.doubleValue()));
}
} else if (valueAsString.contains(".")) {
result.add(new DoubleDataEntry(key, value.getAsDouble()));
} else {
try {
long longValue = Long.parseLong(value.getAsString());
result.add(new LongDataEntry(key, longValue));
} catch (NumberFormatException e) {
if (bd.scale() <= 16) {
result.add(new DoubleDataEntry(key, bd.doubleValue()));
} else if (isTypeCastEnabled) {
result.add(new StringDataEntry(key, bd.toPlainString()));
} else {
throw new JsonSyntaxException("Big integer values are not supported!");
}
}

35
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -348,11 +348,25 @@ public class DefaultTransportService implements TransportService {
}
@Override
public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback<TransportProtos.ValidateDeviceCredentialsResponseMsg> callback) {
log.trace("Processing msg: {}", msg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(msg).build());
AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg),
response -> callback.onSuccess(response.getValue().getValidateCredResponseMsg()), callback::onError, transportCallbackExecutor);
public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg requestMsg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
log.trace("Processing msg: {}", requestMsg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(requestMsg).build());
ListenableFuture<ValidateDeviceCredentialsResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg();
ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder();
if (msg.hasDeviceInfo()) {
result.credentials(msg.getCredentialsBody());
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (!profileBody.isEmpty()) {
DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
result.deviceProfile(profile);
}
}
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
@ -372,7 +386,7 @@ public class DefaultTransportService implements TransportService {
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) {
if (!profileBody.isEmpty()) {
DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
if (transportType != DeviceTransportType.DEFAULT
&& profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) {
@ -456,6 +470,15 @@ public class DefaultTransportService implements TransportService {
}
}
@Override
public void process(TransportToDeviceActorMsg msg, TransportServiceCallback<Void> callback) {
TransportProtos.SessionInfoProto sessionInfo = msg.getSessionInfo();
if (checkLimits(sessionInfo, msg, callback)) {
reportActivityInternal(sessionInfo);
sendToDeviceActor(sessionInfo, msg, callback);
}
}
@Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
int dataPoints = 0;

50
common/transport/transport-api/src/test/java/JsonConverterTest.java

@ -15,17 +15,26 @@
*/
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import java.util.ArrayList;
@RunWith(MockitoJUnitRunner.class)
public class JsonConverterTest {
private static final JsonParser JSON_PARSER = new JsonParser();
@Before
public void before() {
JsonConverter.setTypeCastEnabled(true);
}
@Test
public void testParseBigDecimalAsLong() {
var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E+1}"), 0L);
@ -38,6 +47,18 @@ public class JsonConverterTest {
Assert.assertEquals(10.1, result.get(0L).get(0).getDoubleValue().get(), 0.0);
}
@Test
public void testParseAttributesBigDecimalAsLong() {
var result = new ArrayList<>(JsonConverter.convertToAttributes(JSON_PARSER.parse("{\"meterReadingDelta\": 1E1}")));
Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue());
}
@Test
public void testParseAsDoubleWithZero() {
var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 42.0}"), 0L);
Assert.assertEquals(42.0, result.get(0L).get(0).getDoubleValue().get(), 0.0);
}
@Test
public void testParseAsDouble() {
var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L);
@ -50,4 +71,33 @@ public class JsonConverterTest {
Assert.assertEquals(11L, result.get(0L).get(0).getLongValue().get().longValue());
}
@Test
public void testParseBigDecimalAsStringOutOfLongRange() {
var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L);
Assert.assertEquals("99701010061400066000", result.get(0L).get(0).getStrValue().get());
}
@Test
public void testParseBigDecimalAsStringOutOfLongRange2() {
var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 99701010061400066001}"), 0L);
Assert.assertEquals("99701010061400066001", result.get(0L).get(0).getStrValue().get());
}
@Test
public void testParseBigDecimalAsStringOutOfLongRange3() {
var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E19}"), 0L);
Assert.assertEquals("10000000000000000000", result.get(0L).get(0).getStrValue().get());
}
@Test(expected = JsonSyntaxException.class)
public void testParseBigDecimalOutOfLongRangeWithoutParsing() {
JsonConverter.setTypeCastEnabled(false);
JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 89701010051400054084}"), 0L);
}
@Test(expected = JsonSyntaxException.class)
public void testParseBigDecimalOutOfLongRangeWithoutParsing2() {
JsonConverter.setTypeCastEnabled(false);
JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L);
}
}

51
dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.device;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
@ -23,8 +24,12 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -33,7 +38,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.common.util.JacksonUtil;
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -76,7 +80,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
}
private DeviceCredentials saveOrUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) {
if(deviceCredentials.getCredentialsType() == null){
if (deviceCredentials.getCredentialsType() == null) {
throw new DataValidationException("Device credentials type should be specified");
}
switch (deviceCredentials.getCredentialsType()) {
@ -131,7 +135,6 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials));
}
private void formatCertData(DeviceCredentials deviceCredentials) {
String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
@ -140,7 +143,49 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
}
private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) {
LwM2MClientCredentials clientCredentials;
ObjectNode json;
try {
json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class);
if (json == null) {
throw new IllegalArgumentException();
}
clientCredentials = JacksonUtil.convertValue(json.get("client"), LwM2MClientCredentials.class);
if (clientCredentials == null) {
throw new IllegalArgumentException();
}
} catch (IllegalArgumentException e) {
throw new DataValidationException("Invalid credentials body for LwM2M credentials!");
}
String credentialsId = null;
switch (clientCredentials.getSecurityConfigClientMode()) {
case NO_SEC:
case RPK:
credentialsId = clientCredentials.getEndpoint();
break;
case PSK:
credentialsId = ((PSKClientCredentials) clientCredentials).getIdentity();
break;
case X509:
X509ClientCredentials x509Config = (X509ClientCredentials) clientCredentials;
if (x509Config.getCert() != null) {
String cert = EncryptionUtil.trimNewLines(x509Config.getCert());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
x509Config.setCert(cert);
((ObjectNode) json.get("client")).put("cert", cert);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(json));
credentialsId = sha3Hash;
} else {
credentialsId = x509Config.getEndpoint();
}
break;
}
if (credentialsId == null) {
throw new DataValidationException("Invalid credentials body for LwM2M credentials!");
}
deviceCredentials.setCredentialsId(credentialsId);
}
@Override

61
dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java

@ -31,6 +31,9 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
@ -43,7 +46,11 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -110,6 +117,36 @@ public class BaseFirmwareService implements FirmwareService {
}
}
@Override
public String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) {
if (data == null || !data.hasArray() || data.array().length == 0) {
throw new DataValidationException("Firmware data should be specified!");
}
return getHashFunction(checksumAlgorithm).hashBytes(data.array()).toString();
}
private HashFunction getHashFunction(ChecksumAlgorithm checksumAlgorithm) {
switch (checksumAlgorithm) {
case MD5:
return Hashing.md5();
case SHA256:
return Hashing.sha256();
case SHA384:
return Hashing.sha384();
case SHA512:
return Hashing.sha512();
case CRC32:
return Hashing.crc32();
case MURMUR3_32:
return Hashing.murmur3_32();
case MURMUR3_128:
return Hashing.murmur3_128();
default:
throw new DataValidationException("Unknown checksum algorithm!");
}
}
@Override
public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) {
log.trace("Executing findFirmwareById [{}]", firmwareId);
@ -210,34 +247,16 @@ public class BaseFirmwareService implements FirmwareService {
throw new DataValidationException("Firmware content type should be specified!");
}
ByteBuffer data = firmware.getData();
if (data == null || !data.hasArray() || data.array().length == 0) {
throw new DataValidationException("Firmware data should be specified!");
}
if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) {
if (firmware.getChecksumAlgorithm() == null) {
throw new DataValidationException("Firmware checksum algorithm should be specified!");
}
if (StringUtils.isEmpty(firmware.getChecksum())) {
throw new DataValidationException("Firmware checksum should be specified!");
}
HashFunction hashFunction;
switch (firmware.getChecksumAlgorithm()) {
case "sha256":
hashFunction = Hashing.sha256();
break;
case "md5":
hashFunction = Hashing.md5();
break;
case "crc32":
hashFunction = Hashing.crc32();
break;
default:
throw new DataValidationException("Unknown checksum algorithm!");
}
String currentChecksum;
String currentChecksum = hashFunction.hashBytes(data.array()).toString();
currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData());
if (!currentChecksum.equals(firmware.getChecksum())) {
throw new DataValidationException("Wrong firmware file!");

4
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java

@ -117,6 +117,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
this.setCreatedTime(deviceProfile.getCreatedTime());
this.name = deviceProfile.getName();
this.type = deviceProfile.getType();
this.image = deviceProfile.getImage();
this.transportType = deviceProfile.getTransportType();
this.provisionType = deviceProfile.getProvisionType();
this.description = deviceProfile.getDescription();
@ -125,6 +126,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
if (deviceProfile.getDefaultRuleChainId() != null) {
this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId();
}
if (deviceProfile.getDefaultDashboardId() != null) {
this.defaultDashboardId = deviceProfile.getDefaultDashboardId().getId();
}
this.defaultQueueName = deviceProfile.getDefaultQueueName();
this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
if (deviceProfile.getFirmwareId() != null) {

4
dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java

@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
@ -82,8 +83,9 @@ public class FirmwareEntity extends BaseSqlEntity<Firmware> implements SearchTex
@Column(name = FIRMWARE_CONTENT_TYPE_COLUMN)
private String contentType;
@Enumerated(EnumType.STRING)
@Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN)
private String checksumAlgorithm;
private ChecksumAlgorithm checksumAlgorithm;
@Column(name = FIRMWARE_CHECKSUM_COLUMN)
private String checksum;

6
dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java

@ -22,6 +22,7 @@ import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
@ -81,8 +82,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity<FirmwareInfo> implements S
@Column(name = FIRMWARE_CONTENT_TYPE_COLUMN)
private String contentType;
@Enumerated(EnumType.STRING)
@Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN)
private String checksumAlgorithm;
private ChecksumAlgorithm checksumAlgorithm;
@Column(name = FIRMWARE_CHECKSUM_COLUMN)
private String checksum;
@ -123,7 +125,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity<FirmwareInfo> implements S
}
public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, FirmwareType type, String title, String version,
String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize,
String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
Object additionalInfo, boolean hasData) {
this.id = id;
this.createdTime = createdTime;

13
dao/src/main/resources/sql/schema-ts-psql.sql

@ -63,7 +63,7 @@ BEGIN
into max_customer_ttl;
max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl);
if max_ttl IS NOT NULL AND max_ttl > 0 THEN
date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - (max_ttl / 1000));
date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl);
partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date);
RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date;
IF partition_by_max_ttl_date IS NOT NULL THEN
@ -104,11 +104,12 @@ BEGIN
END IF;
END IF;
END IF;
END IF;
IF partition_to_delete IS NOT NULL THEN
RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;
EXECUTE format('DROP TABLE %I', partition_to_delete);
deleted := deleted + 1;
IF partition_to_delete IS NOT NULL THEN
RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;
EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete);
partition_to_delete := NULL;
deleted := deleted + 1;
END IF;
END IF;
END LOOP;
END IF;

3
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -106,7 +107,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
firmware.setVersion("v1.0");
firmware.setFileName("test.txt");
firmware.setContentType("text/plain");
firmware.setChecksumAlgorithm("sha256");
firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
firmware.setData(ByteBuffer.wrap(new byte[]{1}));
Firmware savedFirmware = firmwareService.saveFirmware(firmware);

5
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -196,7 +197,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
firmware.setVersion("v1.0");
firmware.setFileName("test.txt");
firmware.setContentType("text/plain");
firmware.setChecksumAlgorithm("sha256");
firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
firmware.setData(ByteBuffer.wrap(new byte[]{1}));
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
@ -230,7 +231,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
firmware.setVersion("v1.0");
firmware.setFileName("test.txt");
firmware.setContentType("text/plain");
firmware.setChecksumAlgorithm("sha256");
firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
firmware.setData(ByteBuffer.wrap(new byte[]{1}));
Firmware savedFirmware = firmwareService.saveFirmware(firmware);

3
dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -47,7 +48,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest {
private static final String FILE_NAME = "filename.txt";
private static final String VERSION = "v1.0";
private static final String CONTENT_TYPE = "text/plain";
private static final String CHECKSUM_ALGORITHM = "sha256";
private static final ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256;
private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});

14
pom.xml

@ -48,6 +48,7 @@
<json-path.version>2.2.0</json-path.version>
<junit.version>4.12</junit.version>
<jupiter.version>5.7.1</jupiter.version>
<hamcrest.version>2.2</hamcrest.version>
<slf4j.version>1.7.7</slf4j.version>
<logback.version>1.2.3</logback.version>
<mockito.version>3.3.3</mockito.version>
@ -94,7 +95,7 @@
<hsqldb.version>2.5.0</hsqldb.version>
<dbunit.version>2.5.3</dbunit.version>
<spring-test-dbunit.version>1.2.1</spring-test-dbunit.version>
<postgresql.driver.version>42.2.16</postgresql.driver.version>
<postgresql.driver.version>42.2.20</postgresql.driver.version>
<sonar.exclusions>org/thingsboard/server/gen/**/*,
org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
</sonar.exclusions>
@ -1371,6 +1372,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
@ -1430,6 +1437,11 @@
<artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java

@ -88,6 +88,9 @@ public class TbKafkaNode implements TbNode {
addMetadataKeyValuesAsKafkaHeaders = BooleanUtils.toBooleanDefaultIfNull(config.isAddMetadataKeyValuesAsKafkaHeaders(), false);
toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8;
try {
// Ugly workaround to fix org.apache.kafka.common.KafkaException: javax.security.auth.login.LoginException: unable to find LoginModule class
// details: https://stackoverflow.com/questions/57574901/kafka-java-client-classloader-doesnt-find-sasl-scram-login-class
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
this.producer = new KafkaProducer<>(properties);
} catch (Exception e) {
throw new TbNodeException(e);

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -207,7 +207,8 @@ public class TbHttpClient {
metaData.putValue(STATUS_CODE, response.getStatusCode().value() + "");
metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase());
response.getHeaders().toSingleValueMap().forEach(metaData::putValue);
return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody());
String body = response.getBody() == null ? "{}" : response.getBody();
return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, body);
}
private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) {

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java

@ -17,6 +17,8 @@ package org.thingsboard.rule.engine.rest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.credentials.AnonymousCredentials;
import org.thingsboard.rule.engine.credentials.ClientCredentials;
@ -51,7 +53,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
TbRestApiCallNodeConfiguration configuration = new TbRestApiCallNodeConfiguration();
configuration.setRestEndpointUrlPattern("http://localhost/api");
configuration.setRequestMethod("POST");
configuration.setHeaders(Collections.emptyMap());
configuration.setHeaders(Collections.singletonMap(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
configuration.setUseSimpleClientHttpFactory(false);
configuration.setReadTimeoutMs(0);
configuration.setMaxParallelRequestsCount(0);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save