Browse Source

Merge remote-tracking branch 'upstream/master' into develop/3.3-edge

pull/3811/head
Volodymyr Babak 5 years ago
parent
commit
df6f4c9924
  1. 39
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 28
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  3. 38
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  4. 90
      application/src/main/java/org/thingsboard/server/controller/ResourceController.java
  5. 10
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  6. 207
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  7. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  8. 72
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  9. 30
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  10. 140
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java
  11. 19
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  12. 7
      application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java
  13. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  14. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  15. 16
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  16. 14
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  17. 44
      application/src/main/resources/thingsboard.yml
  18. 4
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  19. 296
      application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java
  20. 23
      application/src/test/java/org/thingsboard/server/controller/sql/TbResourceControllerSqlTest.java
  21. 73
      common/coap-server/pom.xml
  22. 46
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java
  23. 31
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java
  24. 133
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java
  25. 161
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java
  26. 55
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java
  27. 30
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInfo.java
  28. 162
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
  29. 2
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerMessageDeliverer.java
  30. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java
  31. 23
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ReclaimResult.java
  32. 58
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java
  33. 12
      common/data/pom.xml
  34. 4
      common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
  35. 11
      common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
  36. 6
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  37. 4
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  38. 7
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  39. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  40. 3
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  41. 73
      common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java
  42. 75
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  43. 5
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  44. 3
      common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
  45. 4
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  46. 9
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  47. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java
  48. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java
  49. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java
  50. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java
  51. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  52. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java
  53. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  54. 39
      common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java
  55. 23
      common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mConstants.java
  56. 2
      common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mInstance.java
  57. 1
      common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mObject.java
  58. 4
      common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResourceObserve.java
  59. 2
      common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java
  60. 6
      common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java
  61. 3
      common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java
  62. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java
  63. 34
      common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java
  64. 1
      common/pom.xml
  65. 6
      common/queue/src/main/proto/queue.proto
  66. 8
      common/transport/coap/pom.xml
  67. 15
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java
  68. 238
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  69. 39
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  70. 97
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/NoSecClient.java
  71. 145
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/SecureClientNoAuth.java
  72. 144
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/SecureClientX509.java
  73. 4
      common/transport/lwm2m/pom.xml
  74. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  75. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  76. 15
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  77. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java
  78. 104
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java
  79. 62
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  80. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java
  81. 281
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java
  82. 45
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  83. 82
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  84. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  85. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java
  86. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java
  87. 251
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/LwM2mInMemorySecurityStore.java
  88. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreConfiguration.java
  89. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java
  90. 5
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  91. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  92. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  93. 13
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java
  94. 6
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportResourceCache.java
  95. 11
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  96. 25
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java
  97. 10
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java
  98. 39
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java
  99. 68
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java
  100. 17
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

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

@ -40,6 +40,8 @@ import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
@ -65,6 +67,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -85,7 +88,6 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.msg.TbMsg;
@ -108,6 +110,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -239,6 +242,9 @@ public abstract class BaseController {
@Autowired
protected PartitionService partitionService;
@Autowired
protected TbResourceService resourceService;
@Autowired
protected TbQueueProducerProvider producerProvider;
@ -492,6 +498,9 @@ public abstract class BaseController {
case WIDGET_TYPE:
checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation);
return;
case TB_RESOURCE:
checkResourceId(new TbResourceId(entityId.getId()), operation);
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
}
@ -723,6 +732,30 @@ public abstract class BaseController {
return ruleNode;
}
TbResource checkResourceId(TbResourceId resourceId, Operation operation) throws ThingsboardException {
try {
validateId(resourceId, "Incorrect resourceId " + resourceId);
TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId);
checkNotNull(resource);
accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource);
return resource;
} catch (Exception e) {
throw handleException(e, false);
}
}
TbResourceInfo checkResourceInfoId(TbResourceId resourceId, Operation operation) throws ThingsboardException {
try {
validateId(resourceId, "Incorrect resourceId " + resourceId);
TbResourceInfo resourceInfo = resourceService.findResourceInfoById(getCurrentUser().getTenantId(), resourceId);
checkNotNull(resourceInfo);
accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resourceInfo);
return resourceInfo;
} catch (Exception e) {
throw handleException(e, false);
}
}
@SuppressWarnings("unchecked")
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
@ -1042,8 +1075,8 @@ public abstract class BaseController {
protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException {
String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null;
if(dashboardId != null && !dashboardId.equals("null")) {
if(dashboardService.findDashboardById(getTenantId(), new DashboardId(UUID.fromString(dashboardId))) == null) {
if (dashboardId != null && !dashboardId.equals("null")) {
if (dashboardService.findDashboardById(getTenantId(), new DashboardId(UUID.fromString(dashboardId))) == null) {
additionalInfo.remove(requiredFields);
}
}

28
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -63,6 +63,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.device.claim.ReclaimResult;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -528,6 +529,13 @@ public class DeviceController extends BaseController {
if (result.getResponse().equals(ClaimResponse.SUCCESS)) {
status = HttpStatus.OK;
deferredResult.setResult(new ResponseEntity<>(result, status));
try {
logEntityAction(user, device.getId(), result.getDevice(), customerId, ActionType.ASSIGNED_TO_CUSTOMER, null,
device.getId().toString(), customerId.toString(), customerService.findCustomerById(tenantId, customerId).getName());
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
} else {
status = HttpStatus.BAD_REQUEST;
deferredResult.setResult(new ResponseEntity<>(result.getResponse(), status));
@ -563,14 +571,20 @@ public class DeviceController extends BaseController {
accessControlService.checkPermission(user, Resource.DEVICE, Operation.CLAIM_DEVICES,
device.getId(), device);
ListenableFuture<List<Void>> future = claimDevicesService.reClaimDevice(tenantId, device);
Futures.addCallback(future, new FutureCallback<List<Void>>() {
ListenableFuture<ReclaimResult> result = claimDevicesService.reClaimDevice(tenantId, device);
Futures.addCallback(result, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Void> result) {
if (result != null) {
deferredResult.setResult(new ResponseEntity(HttpStatus.OK));
} else {
deferredResult.setResult(new ResponseEntity(HttpStatus.BAD_REQUEST));
public void onSuccess(ReclaimResult reclaimResult) {
deferredResult.setResult(new ResponseEntity(HttpStatus.OK));
Customer unassignedCustomer = reclaimResult.getUnassignedCustomer();
if (unassignedCustomer != null) {
try {
logEntityAction(user, device.getId(), device, device.getCustomerId(), ActionType.UNASSIGNED_FROM_CUSTOMER, null,
device.getId().toString(), unassignedCustomer.getId().toString(), unassignedCustomer.getName());
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
}
}

38
application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java → application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
@ -30,54 +29,19 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class DeviceLwm2mController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile", params = {"sortOrder", "sortProperty"}, method = RequestMethod.GET)
@ResponseBody
public List<LwM2mObject> getLwm2mListObjects(@RequestParam String sortOrder,
@RequestParam String sortProperty,
@RequestParam(required = false) int[] objectIds,
@RequestParam(required = false) String searchText)
throws ThingsboardException {
try {
return lwM2MModelsRepository.getLwm2mObjects(objectIds, searchText, sortProperty, sortOrder);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/objects", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<LwM2mObject> getLwm2mListObjects(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String searchText,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, searchText, sortProperty, sortOrder);
return checkNotNull(lwM2MModelsRepository.findDeviceLwm2mObjects(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
public class Lwm2mController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)

90
application/src/main/java/org/thingsboard/server/controller/ResourceController.java

@ -1,90 +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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
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 org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Slf4j
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class ResourceController extends BaseController {
private final ResourceService resourceService;
public ResourceController(ResourceService resourceService) {
this.resourceService = resourceService;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.POST)
@ResponseBody
public Resource saveResource(Resource resource) throws ThingsboardException {
try {
resource.setTenantId(getTenantId());
Resource savedResource = checkNotNull(resourceService.saveResource(resource));
tbClusterService.onResourceChange(savedResource, null);
return savedResource;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.GET)
@ResponseBody
public PageData<Resource> getResources(@RequestParam(required = false) boolean system,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
return checkNotNull(resourceService.findResourcesByTenantId(system ? TenantId.SYS_TENANT_ID : getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceType}/{resourceId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("resourceType") ResourceType resourceType,
@PathVariable("resourceId") String resourceId) throws ThingsboardException {
try {
Resource resource = checkNotNull(resourceService.getResource(getTenantId(), resourceType, resourceId));
resourceService.deleteResource(getTenantId(), resourceType, resourceId);
tbClusterService.onResourceDeleted(resource, null);
} catch (Exception e) {
throw handleException(e);
}
}
}

10
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -97,13 +97,9 @@ public class RpcController extends BaseController {
private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
String requestData;
if (rpcRequestBody.get("params").isTextual()) {
requestData = rpcRequestBody.get("params").asText();
} else {
requestData = jsonMapper.writeValueAsString(rpcRequestBody.get("params"));
}
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), requestData);
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
if (rpcRequestBody.has("timeout")) {
cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
}

207
application/src/main/java/org/thingsboard/server/controller/TbResourceController.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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.StringJoiner;
@Slf4j
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class TbResourceController extends BaseController {
public static final String RESOURCE_ID = "resourceId";
private final TbResourceService resourceService;
public TbResourceController(TbResourceService resourceService) {
this.resourceService = resourceService;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadResource(@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
try {
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
TbResource tbResource = checkResourceId(resourceId, Operation.READ);
ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes()));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName())
.header("x-filename", tbResource.getFileName())
.contentLength(resource.contentLength())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/info/{resourceId}", method = RequestMethod.GET)
@ResponseBody
public TbResourceInfo getResourceInfoById(@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
try {
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
return checkResourceInfoId(resourceId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.GET)
@ResponseBody
public TbResource getResourceById(@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
try {
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
return checkResourceId(resourceId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.POST)
@ResponseBody
public List<TbResource> saveResources(@RequestBody List<TbResource> resources) throws ThingsboardException {
try {
List<TbResource> addResources = new ArrayList<>();
StringJoiner noSaveResources = new StringJoiner("; ");
resources.forEach(resource -> {
try {
resource.setTenantId(getTenantId());
checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
addResources.add(addResource(resource));
} catch (Exception e) {
noSaveResources.add(resource.getFileName());
log.warn("Fail save resource: [{}]", resource.getFileName(), e);
}
});
if (noSaveResources.length() > 0) {
throw new ThingsboardException(String.format("Fail save resource: %s", noSaveResources.toString()), ThingsboardErrorCode.INVALID_ARGUMENTS);
}
return addResources;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.GET)
@ResponseBody
public PageData<TbResourceInfo> getResources(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink));
} else {
return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink));
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/resource/lwm2m/page", method = RequestMethod.GET)
@ResponseBody
public List<LwM2mObject> getLwm2mListObjectsPage(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = new PageLink(pageSize, page, textSearch);
return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/resource/lwm2m", method = RequestMethod.GET)
@ResponseBody
public List<LwM2mObject> getLwm2mListObjects(@RequestParam String sortOrder,
@RequestParam String sortProperty,
@RequestParam(required = false) String[] objectIds) throws ThingsboardException {
try {
return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException {
checkParameter("resourceId", strResourceId);
try {
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);
resourceService.deleteResource(getTenantId(), resourceId);
tbClusterService.onResourceDeleted(tbResource, null);
} catch (Exception e) {
throw handleException(e);
}
}
private TbResource addResource(TbResource resource) throws Exception {
checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
TbResource savedResource = checkNotNull(resourceService.saveResource(resource));
tbClusterService.onResourceChange(savedResource, null);
return savedResource;
}
}

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

@ -191,9 +191,6 @@ public class ThingsboardInstallService {
databaseTsUpgradeService.upgradeDatabase("3.2.1");
}
databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
case "3.2.2":
log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
@ -201,6 +198,7 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("3.2.2");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);

72
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -17,35 +17,29 @@ package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@ -103,7 +97,7 @@ public class InstallScripts {
private OAuth2ConfigTemplateService oAuth2TemplateService;
@Autowired
private ResourceService resourceService;
private TbResourceService resourceService;
private Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
@ -217,7 +211,6 @@ public class InstallScripts {
}
public void loadSystemLwm2mResources() throws Exception {
// Path modelsDir = Paths.get("/home/nick/Igor_project/thingsboard_ce_3_2_docker/thingsboard/common/transport/lwm2m/src/main/resources/models/");
Path modelsDir = Paths.get(getDataDir(), MODELS_DIR);
if (Files.isDirectory(modelsDir)) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) {
@ -225,62 +218,19 @@ public class InstallScripts {
path -> {
try {
byte[] fileBytes = Files.readAllBytes(path);
String key = getObjectModelLwm2mValid(fileBytes, path.getFileName().toString(), new DefaultDDFFileValidator());
if (key != null) {
Resource resource = new Resource();
resource.setTenantId(TenantId.SYS_TENANT_ID);
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setResourceId(key);
resource.setValue(Base64.getEncoder().encodeToString(fileBytes));
resourceService.saveResource(resource);
}
TbResource resource = new TbResource();
resource.setFileName(path.getFileName().toString());
resource.setTenantId(TenantId.SYS_TENANT_ID);
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setData(Base64.getEncoder().encodeToString(fileBytes));
resourceService.saveResource(resource);
} catch (Exception e) {
log.error("Unable to load lwm2m model [{}]", path.toString());
throw new RuntimeException("Unable to load lwm2m model", e);
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", path.toString()));
}
}
);
}
}
Path jksPath = Paths.get(getDataDir(), CREDENTIALS_DIR, "serverKeyStore.jks");
try {
Resource resource = new Resource();
resource.setTenantId(TenantId.SYS_TENANT_ID);
resource.setResourceType(ResourceType.JKS);
resource.setResourceId(jksPath.getFileName().toString());
resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(jksPath)));
resourceService.saveResource(resource);
} catch (Exception e) {
log.error("Unable to load lwm2m serverKeyStore [{}]", jksPath.toString());
throw new RuntimeException("Unable to load l2m2m serverKeyStore", e);
}
}
private String getObjectModelLwm2mValid(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator);
ObjectModel objectModel = ddfFileParser.parseEx(new ByteArrayInputStream(xmlByte), streamName).get(0);
return objectModel.id + "##" + objectModel.getVersion();
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML file [{}]", streamName, e);
return null;
}
}
private void removeFile(Path modelsDir, String nameFile, byte[] fileBytes) {
String path = "/home/nick/Igor_project/thingsboard_ce_3_2_docker/thingsboard/common/transport/lwm2m/src/main/resources/models/";
File file = new File(path + nameFile);
if (!file.isDirectory()) {
try {
Files.write(Paths.get(path + "server/" + nameFile), fileBytes);
file.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {

30
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -450,23 +450,27 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource ( " +
"id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY, " +
"created_time bigint NOT NULL, " +
"tenant_id uuid NOT NULL, " +
"title varchar(255) NOT NULL, " +
"resource_type varchar(32) NOT NULL, " +
"resource_key varchar(255) NOT NULL, " +
"search_text varchar(255), " +
"file_name varchar(255) NOT NULL, " +
"data varchar, " +
"CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)" +
");");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
installScripts.loadSystemLwm2mResources();
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE rule_chain ADD COLUMN type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {
}
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource (" +
" tenant_id uuid NOT NULL," +
" resource_type varchar(32) NOT NULL," +
" resource_id varchar(255) NOT NULL," +
" resource_value varchar," +
" CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id)" +
" );");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
installScripts.loadSystemLwm2mResources();
} catch (Exception ignored) {}
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}

140
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java

@ -17,22 +17,13 @@ package org.thingsboard.server.service.lwm2m;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.util.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mInstance;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResource;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import java.math.BigInteger;
@ -48,16 +39,6 @@ import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j
@Service
@ -73,127 +54,6 @@ public class LwM2MModelsRepository {
@Autowired
LwM2MTransportConfigBootstrap contextBootStrap;
/**
* @param objectIds
* @param textSearch
* @return list of LwM2mObject
* Filter by Predicate (uses objectIds, if objectIds is null then it uses textSearch,
* if textSearch is null then it uses AllList from List<ObjectModel>)
*/
public List<LwM2mObject> getLwm2mObjects(int[] objectIds, String textSearch, String sortProperty, String sortOrder) {
if (objectIds == null && textSearch != null && !textSearch.isEmpty()) {
objectIds = getObjectIdFromTextSearch(textSearch);
}
int[] finalObjectIds = objectIds;
return getLwm2mObjects((objectIds != null && objectIds.length > 0 && textSearch != null && !textSearch.isEmpty()) ?
(ObjectModel element) -> IntStream.of(finalObjectIds).anyMatch(x -> x == element.id) || element.name.toLowerCase().contains(textSearch.toLowerCase()) :
(objectIds != null && objectIds.length > 0) ?
(ObjectModel element) -> IntStream.of(finalObjectIds).anyMatch(x -> x == element.id) :
(textSearch != null && !textSearch.isEmpty()) ?
(ObjectModel element) -> element.name.contains(textSearch) :
null,
sortProperty, sortOrder);
}
/**
* @param predicate
* @return list of LwM2mObject
*/
private List<LwM2mObject> getLwm2mObjects(Predicate<? super ObjectModel> predicate, String sortProperty, String sortOrder) {
List<LwM2mObject> lwM2mObjects = new ArrayList<>();
List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValueCommon() :
contextServer.getModelsValueCommon().stream()
.filter(predicate)
.collect(Collectors.toList());
listObjects.forEach(obj -> {
LwM2mObject lwM2mObject = new LwM2mObject();
lwM2mObject.setId(obj.id);
lwM2mObject.setName(obj.name);
lwM2mObject.setMultiple(obj.multiple);
lwM2mObject.setMandatory(obj.mandatory);
LwM2mInstance instance = new LwM2mInstance();
instance.setId(0);
List<LwM2mResource> resources = new ArrayList<>();
obj.resources.forEach((k, v) -> {
if (!v.operations.isExecutable()) {
LwM2mResource resource = new LwM2mResource(k, v.name, false, false, false);
resources.add(resource);
}
});
instance.setResources(resources.stream().toArray(LwM2mResource[]::new));
lwM2mObject.setInstances(new LwM2mInstance[]{instance});
lwM2mObjects.add(lwM2mObject);
});
return lwM2mObjects.size() > 1 ? this.sortList (lwM2mObjects, sortProperty, sortOrder) : lwM2mObjects;
}
private List<LwM2mObject> sortList (List<LwM2mObject> lwM2mObjects, String sortProperty, String sortOrder) {
switch (sortProperty) {
case "name":
switch (sortOrder) {
case "ASC":
lwM2mObjects.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
break;
case "DESC":
lwM2mObjects.stream().sorted(Comparator.comparing(LwM2mObject::getName).reversed());
break;
}
case "id":
switch (sortOrder) {
case "ASC":
lwM2mObjects.sort((o1, o2) -> Long.compare(o1.getId(), o2.getId()));
break;
case "DESC":
lwM2mObjects.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId()));
}
}
return lwM2mObjects;
}
/**
* @param tenantId
* @param pageLink
* @return List of LwM2mObject in PageData format
*/
public PageData<LwM2mObject> findDeviceLwm2mObjects(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validatePageLink(pageLink);
return this.findLwm2mListObjects(pageLink);
}
/**
* @param pageLink
* @return List of LwM2mObject in PageData format, filter == TextSearch
* PageNumber = 1, PageSize = List<LwM2mObject>.size()
*/
public PageData<LwM2mObject> findLwm2mListObjects(PageLink pageLink) {
PageImpl<LwM2mObject> page = new PageImpl<>(getLwm2mObjects(getObjectIdFromTextSearch(pageLink.getTextSearch()),
pageLink.getTextSearch(),
pageLink.getSortOrder().getProperty(),
pageLink.getSortOrder().getDirection().name()));
PageData<LwM2mObject> pageData = new PageData<>(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext());
return pageData;
}
/**
* Filter for id Object
* @param textSearch -
* @return - return Object id only first chartAt in textSearch
*/
private int[] getObjectIdFromTextSearch(String textSearch) {
String filtered = null;
if (textSearch !=null && !textSearch.isEmpty()) {
AtomicInteger a = new AtomicInteger();
filtered = textSearch.chars ()
.mapToObj(chr -> (char) chr)
.filter(i -> Character.isDigit(i) && textSearch.charAt(a.getAndIncrement()) == i)
.collect(Collector.of(StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString));
}
return (filtered != null && !filtered.isEmpty()) ? new int[]{Integer.parseInt(filtered)} : new int[0];
}
/**
* @param securityMode
* @param bootstrapServerIs

19
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId;
@ -251,28 +251,27 @@ public class DefaultTbClusterService implements TbClusterService {
}
@Override
public void onResourceChange(Resource resource, TbQueueCallback callback) {
public void onResourceChange(TbResource resource, TbQueueCallback callback) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceId());
log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceKey());
TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceId(resource.getResourceId())
.setResourceKey(resource.getResourceKey())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceUpdateMsg(resourceUpdateMsg).build();
broadcast(transportMsg, callback);
}
@Override
public void onResourceDeleted(Resource resource, TbQueueCallback callback) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing delete resource", tenantId, resource.getResourceType(), resource.getResourceId());
public void onResourceDeleted(TbResource resource, TbQueueCallback callback) {
log.trace("[{}] Processing delete resource", resource);
TransportProtos.ResourceDeleteMsg resourceUpdateMsg = TransportProtos.ResourceDeleteMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setTenantIdMSB(resource.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(resource.getTenantId().getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceId(resource.getResourceId())
.setResourceKey(resource.getResourceKey())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceDeleteMsg(resourceUpdateMsg).build();
broadcast(transportMsg, callback);

7
application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java

@ -19,7 +19,7 @@ import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EdgeId;
@ -74,10 +74,9 @@ public interface TbClusterService {
void onDeviceDeleted(Device device, TbQueueCallback callback);
void onResourceChange(Resource resource, TbQueueCallback callback);
void onResourceChange(TbResource resource, TbQueueCallback callback);
void onResourceDeleted(Resource resource, TbQueueCallback callback);
void onResourceDeleted(TbResource resource, TbQueueCallback callback);
void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId);
}

1
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -37,6 +37,7 @@ public enum Resource {
TENANT_PROFILE(EntityType.TENANT_PROFILE),
DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
API_USAGE_STATE(EntityType.API_USAGE_STATE),
TB_RESOURCE(EntityType.TB_RESOURCE),
EDGE(EntityType.EDGE);
private final EntityType entityType;

1
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -38,6 +38,7 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker);
put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker);
put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
put(Resource.TB_RESOURCE, systemEntityPermissionChecker);
}
private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {

16
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -41,6 +41,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
put(Resource.EDGE, tenantEntityPermissionChecker);
}
@ -102,4 +103,19 @@ public class TenantAdminPermissions extends AbstractPermissions {
}
};
private static final PermissionChecker tbResourcePermissionChecker = new PermissionChecker() {
@Override
public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return operation == Operation.READ;
}
if (!user.getTenantId().equals(entity.getTenantId())) {
return false;
}
return true;
}
};
}

14
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
@ -56,7 +56,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -108,7 +108,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService;
private final ResourceService resourceService;
private final TbResourceService resourceService;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
@ -117,7 +117,7 @@ public class DefaultTransportApiService implements TransportApiService {
RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService, ResourceService resourceService) {
DeviceProvisionService deviceProvisionService, TbResourceService resourceService) {
this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache;
this.apiUsageStateService = apiUsageStateService;
@ -365,12 +365,12 @@ public class DefaultTransportApiService implements TransportApiService {
private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
String resourceId = requestMsg.getResourceId();
String resourceKey = requestMsg.getResourceKey();
TransportProtos.GetResourceResponseMsg.Builder builder = TransportProtos.GetResourceResponseMsg.newBuilder();
Resource resource = resourceService.getResource(tenantId, resourceType, resourceId);
TbResource resource = resourceService.getResource(tenantId, resourceType, resourceKey);
if (resource == null && !tenantId.equals(TenantId.SYS_TENANT_ID)) {
resource = resourceService.getResource(TenantId.SYS_TENANT_ID, resourceType, resourceId);
resource = resourceService.getResource(TenantId.SYS_TENANT_ID, resourceType, resourceKey);
}
if (resource != null) {

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

@ -594,6 +594,28 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}"
dtls:
# Enable/disable DTLS 1.2 support
enabled: "${COAP_DTLS_ENABLED:false}"
# CoAP DTLS bind address
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port
bind_port: "${COAP_DTLS_BIND_PORT:5684}"
# Secure mode. Allowed values: NO_AUTH, X509
mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
# Path to the key store that holds the certificate
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
x509:
dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
# Local LwM2M transport parameters
lwm2m:
# Enable/disable lvm2m transport protocol.
@ -602,12 +624,9 @@ transport:
# send a Confirmable message to the time when an acknowledgement is no longer expected.
# DEFAULT_TIMEOUT = 2 * 60 * 1000l; 2 min in ms
timeout: "${LWM2M_TIMEOUT:120000}"
# model_path_file: "${LWM2M_MODEL_PATH_FILE:./common/transport/lwm2m/src/main/resources/models/}"
model_path_file: "${LWM2M_MODEL_PATH_FILE:}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}"
request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
@ -616,8 +635,7 @@ transport:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
# Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}"
# key_store_type: "${LWM2M_KEYSTORE_TYPE:PKCS12}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/usr/share/thingsboard/conf/credentials/serverKeyStore.jks}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks"
key_store_path_file: "${KEY_STORE_PATH_FILE:}"
key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}"
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
@ -636,24 +654,26 @@ transport:
# - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}"
public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509:
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
# Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
bootstrap:
enable: "${LWM2M_BOOTSTRAP_ENABLED:true}"
id: "${LWM2M_SERVER_ID:111}"
enable: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}"
secure:
bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SEC_BS:5688}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
# Only for RPK: Public & Private Key. If the keystore file is missing or not working
# - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
# - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d030107034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34]
# - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34],
public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}"
public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" # Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
# Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"

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

@ -467,6 +467,10 @@ public abstract class AbstractWebTest {
return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseType);
}
protected <T, R> R doPostWithTypedResponse(String urlTemplate, T content, TypeReference<R> responseType, ResultMatcher resultMatcher, String... params) throws Exception {
return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseType);
}
protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
}

296
application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java

@ -0,0 +1,296 @@
/**
* 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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseTbResourceControllerTest extends AbstractControllerTest {
private IdComparator<TbResourceInfo> idComparator = new IdComparator<>();
private static final String DEFAULT_FILE_NAME = "test.jks";
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveTbResource() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
TbResource savedResource = save(resource);
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedResource.getTenantId());
Assert.assertEquals(resource.getTitle(), savedResource.getTitle());
Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getFileName());
Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData());
savedResource.setTitle("My new resource");
save(savedResource);
TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class);
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle());
}
@Test
public void testUpdateTbResourceFromDifferentTenant() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
TbResource savedResource = save(resource);
loginDifferentTenant();
doPostWithTypedResponse("/api/resource", Collections.singletonList(savedResource), new TypeReference<>(){}, status().isBadRequest());
deleteDifferentTenant();
}
@Test
public void testFindTbResourceById() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
TbResource savedResource = save(resource);
TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class);
Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource);
}
@Test
public void testDeleteTbResource() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
TbResource savedResource = save(resource);
doDelete("/api/resource/" + savedResource.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/resource/" + savedResource.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testFindTenantTbResources() throws Exception {
List<TbResource> resourcesToSave = new ArrayList<>();
for (int i = 0; i < 173; i++) {
TbResource resource = new TbResource();
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData("Test Data");
resourcesToSave.add(resource);
}
List<TbResourceInfo> resources =save(resourcesToSave).stream().map(TbResourceInfo::new).collect(Collectors.toList());
List<TbResourceInfo> loadedResources = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<TbResourceInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/resource?",
new TypeReference<PageData<TbResourceInfo>>() {
}, pageLink);
loadedResources.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources);
}
@Test
public void testFindSystemTbResources() throws Exception {
loginSysAdmin();
List<TbResourceInfo> resources = new ArrayList<>();
for (int i = 0; i < 173; i++) {
TbResource resource = new TbResource();
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData("Test Data");
resources.add(new TbResourceInfo(save(resource)));
}
List<TbResourceInfo> loadedResources = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<TbResourceInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/resource?",
new TypeReference<PageData<TbResourceInfo>>() {
}, pageLink);
loadedResources.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources);
for (TbResourceInfo resource : resources) {
doDelete("/api/resource/" + resource.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(27);
loadedResources.clear();
do {
pageData = doGetTypedWithPageLink("/api/resource?",
new TypeReference<PageData<TbResourceInfo>>() {
}, pageLink);
loadedResources.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Assert.assertTrue(loadedResources.isEmpty());
}
@Test
public void testFindSystemAndTenantTbResources() throws Exception {
List<TbResourceInfo> systemResources = new ArrayList<>();
List<TbResourceInfo> expectedResources = new ArrayList<>();
for (int i = 0; i < 73; i++) {
TbResource resource = new TbResource();
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData("Test Data");
expectedResources.add(new TbResourceInfo(save(resource)));
}
loginSysAdmin();
for (int i = 0; i < 173; i++) {
TbResource resource = new TbResource();
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData("Test Data");
TbResourceInfo savedResource = new TbResourceInfo(save(resource));
systemResources.add(savedResource);
if (i >= 73) {
expectedResources.add(savedResource);
}
}
login(tenantAdmin.getEmail(), "testPassword1");
List<TbResourceInfo> loadedResources = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<TbResourceInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/resource?",
new TypeReference<PageData<TbResourceInfo>>() {
}, pageLink);
loadedResources.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(expectedResources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(expectedResources, loadedResources);
loginSysAdmin();
for (TbResourceInfo resource : systemResources) {
doDelete("/api/resource/" + resource.getId().getId().toString())
.andExpect(status().isOk());
}
}
private TbResource save(TbResource tbResource) throws Exception {
return save(Collections.singletonList(tbResource)).get(0);
}
private List<TbResource> save(List<TbResource> tbResources) throws Exception {
return doPostWithTypedResponse("/api/resource", tbResources, new TypeReference<>(){});
}
}

23
application/src/test/java/org/thingsboard/server/controller/sql/TbResourceControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* 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.controller.sql;
import org.thingsboard.server.controller.BaseTbResourceControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class TbResourceControllerSqlTest extends BaseTbResourceControllerTest {
}

73
common/coap-server/pom.xml

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
<artifactId>coap-server</artifactId>
<packaging>jar</packaging>
<name>Thingsboard CoAP server</name>
<url>https://thingsboard.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>transport-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>scandium</artifactId>
</dependency>
</dependencies>
</project>

46
common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java

@ -0,0 +1,46 @@
/**
* 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.coapserver;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
@Slf4j
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@Component
public class CoapServerContext {
@Getter
@Value("${transport.coap.bind_address}")
private String host;
@Getter
@Value("${transport.coap.bind_port}")
private Integer port;
@Getter
@Value("${transport.coap.timeout}")
private Long timeout;
@Getter
@Autowired(required = false)
private TbCoapDtlsSettings dtlsSettings;
}

31
common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java

@ -0,0 +1,31 @@
/**
* 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.coapserver;
import org.eclipse.californium.core.CoapServer;
import java.net.UnknownHostException;
import java.util.concurrent.ConcurrentMap;
public interface CoapServerService {
CoapServer getCoapServer() throws UnknownHostException;
ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap();
long getTimeout();
}

133
common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java

@ -0,0 +1,133 @@
/**
* 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.coapserver;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
public class DefaultCoapServerService implements CoapServerService {
@Autowired
private CoapServerContext coapServerContext;
private CoapServer server;
private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier;
private ScheduledExecutorService dtlsSessionsExecutor;
@PostConstruct
public void init() throws UnknownHostException {
createCoapServer();
}
@PreDestroy
public void shutdown() {
if (dtlsSessionsExecutor != null) {
dtlsSessionsExecutor.shutdownNow();
}
log.info("Stopping CoAP server!");
server.destroy();
log.info("CoAP server stopped!");
}
@Override
public CoapServer getCoapServer() throws UnknownHostException {
if (server != null) {
return server;
} else {
return createCoapServer();
}
}
@Override
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionIdsMap() : null;
}
@Override
public long getTimeout() {
return coapServerContext.getTimeout();
}
private CoapServer createCoapServer() throws UnknownHostException {
server = new CoapServer();
CoapEndpoint.Builder noSecCoapEndpointBuilder = new CoapEndpoint.Builder();
InetAddress addr = InetAddress.getByName(coapServerContext.getHost());
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort());
noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr);
noSecCoapEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard());
CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build();
server.addEndpoint(noSecCoapEndpoint);
if (isDtlsEnabled()) {
CoapEndpoint.Builder dtlsCoapEndpointBuilder = new CoapEndpoint.Builder();
TbCoapDtlsSettings dtlsSettings = coapServerContext.getDtlsSettings();
DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig();
DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig);
dtlsCoapEndpointBuilder.setConnector(connector);
CoapEndpoint dtlsCoapEndpoint = dtlsCoapEndpointBuilder.build();
server.addEndpoint(dtlsCoapEndpoint);
if (dtlsConnectorConfig.isClientAuthenticationRequired()) {
tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier();
dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor();
dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS);
}
}
Resource root = server.getRoot();
TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
server.setMessageDeliverer(messageDeliverer);
server.start();
return server;
}
private boolean isDtlsEnabled() {
return coapServerContext.getDtlsSettings() != null;
}
private void evictTimeoutSessions() {
tbDtlsCertificateVerifier.evictTimeoutSessions();
}
private long getDtlsSessionReportTimeout() {
return tbDtlsCertificateVerifier.getDtlsSessionReportTimeout();
}
}

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

@ -0,0 +1,161 @@
/**
* 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.coapserver;
import lombok.Data;
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.util.ServerNames;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
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.SessionInfoCreator;
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.discovery.TbServiceInfoProvider;
import javax.security.auth.x500.X500Principal;
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.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
@Data
public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage;
private TransportService transportService;
private TbServiceInfoProvider serviceInfoProvider;
private boolean skipValidityCheckForClientCert;
public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) {
this.transportService = transportService;
this.serviceInfoProvider = serviceInfoProvider;
this.skipValidityCheckForClientCert = skipValidityCheckForClientCert;
this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout);
}
@Override
public List<CertificateType> getSupportedCertificateType() {
return Collections.singletonList(CertificateType.X_509);
}
@Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
try {
String credentialsBody = null;
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(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(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();
}
});
latch.await(10, TimeUnit.SECONDS);
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
if (msg != null && strCert.equals(msg.getCredentials())) {
credentialsBody = msg.getCredentials();
DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) {
TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID());
tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile));
}
break;
}
} catch (InterruptedException |
CertificateEncodingException |
CertificateExpiredException |
CertificateNotYetValidException e) {
log.error(e.getMessage(), e);
}
}
if (credentialsBody == null) {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
session.getPeer());
throw new HandshakeException("Certificate chain could not be validated", alert);
} else {
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) {
// empty implementation
}
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap();
}
public void evictTimeoutSessions() {
tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions();
}
public long getDtlsSessionReportTimeout() {
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
}
}

55
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java

@ -0,0 +1,55 @@
/**
* 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.coapserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Slf4j
@Data
public class TbCoapDtlsSessionInMemoryStorage {
private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>();
private long dtlsSessionInactivityTimeout;
private long dtlsSessionReportTimeout;
public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) {
this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout;
this.dtlsSessionReportTimeout = dtlsSessionReportTimeout;
}
public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) {
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime());
dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo);
}
public void evictTimeoutSessions() {
long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout;
dtlsSessionIdMap.entrySet().removeIf(entry -> {
if (entry.getValue().getLastActivityTime() < expTime) {
log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey());
return true;
} else {
return false;
}
});
}
}

30
common/data/src/main/java/org/thingsboard/server/common/data/Resource.java → common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInfo.java

@ -13,29 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
package org.thingsboard.server.coapserver;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import java.io.Serializable;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos;
@Data
public class Resource implements HasTenantId, Serializable {
public class TbCoapDtlsSessionInfo {
private static final long serialVersionUID = 7379609705527272306L;
private TransportProtos.SessionInfoProto sessionInfoProto;
private DeviceProfile deviceProfile;
private long lastActivityTime;
private TenantId tenantId;
private ResourceType resourceType;
private String resourceId;
private String value;
@Override
public String toString() {
return "Resource{" +
"tenantId=" + tenantId +
", resourceType=" + resourceType +
", resourceId='" + resourceId + '\'' +
'}';
public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) {
this.sessionInfoProto = sessionInfoProto;
this.deviceProfile = deviceProfile;
this.lastActivityTime = System.currentTimeMillis();
}
}
}

162
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java

@ -0,0 +1,162 @@
/**
* 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.coapserver;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Optional;
@Slf4j
@ConditionalOnExpression("'${transport.coap.enabled}'=='true'")
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
@Component
public class TbCoapDtlsSettings {
@Value("${transport.coap.dtls.bind_address}")
private String host;
@Value("${transport.coap.dtls.bind_port}")
private Integer port;
@Value("${transport.coap.dtls.mode}")
private String mode;
@Value("${transport.coap.dtls.key_store}")
private String keyStoreFile;
@Value("${transport.coap.dtls.key_store_password}")
private String keyStorePassword;
@Value("${transport.coap.dtls.key_password}")
private String keyPassword;
@Value("${transport.coap.dtls.key_alias}")
private String keyAlias;
@Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
private boolean skipValidityCheckForClientCert;
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
private long dtlsSessionInactivityTimeout;
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
private long dtlsSessionReportTimeout;
@Autowired
private TransportService transportService;
@Autowired
private TbServiceInfoProvider serviceInfoProvider;
public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException {
Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode);
if (securityModeOpt.isEmpty()) {
log.warn("Incorrect configuration of securityMode {}", mode);
throw new RuntimeException("Failed to parse mode property: " + mode + "!");
} else {
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
configBuilder.setAddress(getInetSocketAddress());
String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath();
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
SecurityMode securityMode = securityModeOpt.get();
if (securityMode.equals(SecurityMode.NO_AUTH)) {
configBuilder.setClientAuthenticationRequired(false);
configBuilder.setServerOnly(true);
} else {
configBuilder.setAdvancedCertificateVerifier(
new TbCoapDtlsCertificateVerifier(
transportService,
serviceInfoProvider,
dtlsSessionInactivityTimeout,
dtlsSessionReportTimeout,
skipValidityCheckForClientCert
)
);
}
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
Collections.singletonList(CertificateType.X_509));
return configBuilder.build();
}
}
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
try {
return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(),
keyPassword.toCharArray());
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException("Failed to load serverCredentials due to: ", e);
}
}
private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) {
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
try {
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
keyStoreFilePath, keyAlias,
keyStorePassword.toCharArray());
trustBuilder.setTrustedCertificates(trustedCertificates);
if (trustBuilder.hasTrusts()) {
config.setAdvancedCertificateVerifier(trustBuilder.build());
}
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException("Failed to load trusted certificates due to: ", e);
}
}
private InetSocketAddress getInetSocketAddress() throws UnknownHostException {
InetAddress addr = InetAddress.getByName(host);
return new InetSocketAddress(addr, port);
}
private enum SecurityMode {
X509,
NO_AUTH;
static Optional<SecurityMode> parse(String name) {
SecurityMode mode = null;
if (name != null) {
for (SecurityMode securityMode : SecurityMode.values()) {
if (securityMode.name().equalsIgnoreCase(name)) {
mode = securityMode;
break;
}
}
}
return Optional.ofNullable(mode);
}
}
}

2
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapServerMessageDeliverer.java → common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerMessageDeliverer.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap;
package org.thingsboard.server.coapserver;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.OptionSet;

4
common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java

@ -21,8 +21,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.device.claim.ReclaimResult;
import java.util.List;
import java.util.concurrent.ExecutionException;
public interface ClaimDevicesService {
@ -31,6 +31,6 @@ public interface ClaimDevicesService {
ListenableFuture<ClaimResult> claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException;
ListenableFuture<List<Void>> reClaimDevice(TenantId tenantId, Device device);
ListenableFuture<ReclaimResult> reClaimDevice(TenantId tenantId, Device device);
}

23
common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java → common/dao-api/src/main/java/org/thingsboard/server/dao/device/claim/ReclaimResult.java

@ -13,25 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.resource;
package org.thingsboard.server.dao.device.claim;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.Customer;
@Data
public class Resource implements HasTenantId {
private TenantId tenantId;
private ResourceType resourceType;
private String resourceId;
private String value;
@Override
public String toString() {
return "Resource{" +
"tenantId=" + tenantId +
", resourceType=" + resourceType +
", resourceId='" + resourceId + '\'' +
'}';
}
@AllArgsConstructor
public class ReclaimResult {
Customer unassignedCustomer;
}

58
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java

@ -0,0 +1,58 @@
/**
* 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.dao.resource;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.io.IOException;
import java.util.List;
public interface TbResourceService {
TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException;
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
List<LwM2mObject> findLwM2mObject(TenantId tenantId,
String sortOrder,
String sortProperty,
String[] objectIds);
List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId,
String sortProperty,
String sortOrder,
PageLink pageLink);
void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResourcesByTenantId(TenantId tenantId);
}

12
common/data/pom.xml

@ -36,6 +36,14 @@
</properties>
<dependencies>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@ -79,6 +87,10 @@
<groupId>org.thingsboard</groupId>
<artifactId>protobuf-dynamic</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
</dependency>
</dependencies>
<build>

4
common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java

@ -18,11 +18,13 @@ package org.thingsboard.server.common.data;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.validation.NoXss;
public class AdminSettings extends BaseData<AdminSettingsId> {
private static final long serialVersionUID = -7670322981725511892L;
@NoXss
private String key;
private transient JsonNode jsonValue;

11
common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java

@ -17,19 +17,28 @@ package org.thingsboard.server.common.data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
private static final long serialVersionUID = 5047448057830660988L;
@NoXss
protected String country;
@NoXss
protected String state;
@NoXss
protected String city;
@NoXss
protected String address;
@NoXss
protected String address2;
@NoXss
protected String zip;
@NoXss
protected String phone;
@NoXss
protected String email;
public ContactBased() {

6
common/data/src/main/java/org/thingsboard/server/common/data/Customer.java

@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.validation.NoXss;
public class Customer extends ContactBased<CustomerId> implements HasTenantId {
private static final long serialVersionUID = -1599722990298929275L;
@NoXss
private String title;
private TenantId tenantId;

4
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -36,8 +37,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
private TenantId tenantId;
private CustomerId customerId;
@NoXss
private String name;
@NoXss
private String type;
@NoXss
private String label;
private DeviceProfileId deviceProfileId;
private transient DeviceData deviceData;

7
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId {
private TenantId tenantId;
@NoXss
private String name;
@NoXss
private String description;
private boolean isDefault;
private DeviceProfileType type;
private DeviceTransportType transportType;
private DeviceProfileProvisionType provisionType;
private RuleChainId defaultRuleChainId;
@NoXss
private String defaultQueueName;
@Valid
private transient DeviceProfileData profileData;
@JsonIgnore
private byte[] profileDataBytes;
@NoXss
private String provisionDeviceKey;
public DeviceProfile() {

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, EDGE;
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, EDGE;
}

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

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.common.data.validation.NoXss;
/**
* Created by Victor Basanets on 8/27/2017.
@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
private EntityId entityId;
private TenantId tenantId;
private CustomerId customerId;
@NoXss
private String name;
@NoXss
private String type;
private TelemetryEntityView keys;
private long startTimeMs;

73
common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java

@ -0,0 +1,73 @@
/**
* 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;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TbResourceId;
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class TbResource extends TbResourceInfo {
private static final long serialVersionUID = 7379609705527272306L;
private String fileName;
private String data;
public TbResource() {
super();
}
public TbResource(TbResourceId id) {
super(id);
}
public TbResource(TbResourceInfo resourceInfo) {
super(resourceInfo);
}
public TbResource(TbResource resource) {
super(resource);
this.data = resource.getData();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Resource [tenantId=");
builder.append(getTenantId());
builder.append(", id=");
builder.append(getUuidId());
builder.append(", createdTime=");
builder.append(createdTime);
builder.append(", title=");
builder.append(getTitle());
builder.append(", resourceType=");
builder.append(getResourceType());
builder.append(", resourceKey=");
builder.append(getResourceKey());
builder.append(", fileName=");
builder.append(fileName);
builder.append(", data=");
builder.append(data);
builder.append("]");
return builder.toString();
}
}

75
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -0,0 +1,75 @@
/**
* 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;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
private TenantId tenantId;
private String title;
private ResourceType resourceType;
private String resourceKey;
private String searchText;
public TbResourceInfo() {
super();
}
public TbResourceInfo(TbResourceId id) {
super(id);
}
public TbResourceInfo(TbResourceInfo resourceInfo) {
super(resourceInfo);
this.tenantId = resourceInfo.getTenantId();
this.title = resourceInfo.getTitle();
this.resourceType = resourceInfo.getResourceType();
this.resourceKey = resourceInfo.getResourceKey();
this.searchText = resourceInfo.getSearchText();
}
@Override
public String getSearchText() {
return searchText != null ? searchText : title;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ResourceInfo [tenantId=");
builder.append(tenantId);
builder.append(", id=");
builder.append(getUuidId());
builder.append(", createdTime=");
builder.append(createdTime);
builder.append(", title=");
builder.append(title);
builder.append(", resourceType=");
builder.append(resourceType);
builder.append(", resourceKey=");
builder.append(resourceKey);
builder.append("]");
return builder.toString();
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java

@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class Tenant extends ContactBased<TenantId> implements HasTenantId {
private static final long serialVersionUID = 8057243243859922101L;
@NoXss
private String title;
@NoXss
private String region;
private TenantProfileId tenantProfileId;

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

@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
@Slf4j
public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName {
@NoXss
private String name;
@NoXss
private String description;
private boolean isDefault;
private boolean isolatedTbCore;

4
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
private CustomerId customerId;
private String email;
private Authority authority;
@NoXss
private String firstName;
@NoXss
private String lastName;
public User() {

9
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -16,10 +16,14 @@
package org.thingsboard.server.common.data.asset;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
@ -28,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
private TenantId tenantId;
private CustomerId customerId;
@NoXss
private String name;
@NoXss
private String type;
@NoXss
private String label;
public Asset() {

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

@ -17,15 +17,16 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.thingsboard.server.common.data.query.KeyFilter;
import java.io.Serializable;
import javax.validation.Valid;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AlarmCondition implements Serializable {
@Valid
private List<AlarmConditionFilter> condition;
private AlarmConditionSpec spec;

6
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java

@ -18,15 +18,21 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.io.Serializable;
@Data
public class AlarmConditionFilter implements Serializable {
@Valid
private AlarmConditionFilterKey key;
private EntityKeyValueType valueType;
@NoXss
private Object value;
@Valid
private KeyFilterPredicate predicate;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.Serializable;
@ -23,6 +24,7 @@ import java.io.Serializable;
public class AlarmConditionFilterKey implements Serializable {
private final AlarmConditionKeyType type;
@NoXss
private final String key;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java

@ -16,15 +16,20 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.io.Serializable;
@Data
public class AlarmRule implements Serializable {
@Valid
private AlarmCondition condition;
private AlarmSchedule schedule;
// Advanced
@NoXss
private String alarmDetails;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -17,8 +17,10 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.Serializable;
import javax.validation.Valid;
import java.util.List;
import java.util.TreeMap;
@ -26,9 +28,12 @@ import java.util.TreeMap;
public class DeviceProfileAlarm implements Serializable {
private String id;
@NoXss
private String alarmType;
@Valid
private TreeMap<AlarmSeverity, AlarmRule> createRules;
@Valid
private AlarmRule clearRule;
// Hidden in advanced settings

2
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import java.io.Serializable;
import javax.validation.Valid;
import java.util.List;
@Data
@ -26,6 +27,7 @@ public class DeviceProfileData implements Serializable {
private DeviceProfileConfiguration configuration;
private DeviceProfileTransportConfiguration transportConfiguration;
private DeviceProfileProvisionConfiguration provisionConfiguration;
@Valid
private List<DeviceProfileAlarm> alarms;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -69,6 +69,8 @@ public class EntityIdFactory {
return new TenantProfileId(uuid);
case API_USAGE_STATE:
return new ApiUsageStateId(uuid);
case TB_RESOURCE:
return new TbResourceId(uuid);
case EDGE:
return new EdgeId(uuid);
}

39
common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java

@ -0,0 +1,39 @@
/**
* 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.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class TbResourceId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public TbResourceId(@JsonProperty("id") UUID id) {
super(id);
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.TB_RESOURCE;
}
}

23
common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mConstants.java

@ -0,0 +1,23 @@
/**
* 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.lwm2m;
public interface LwM2mConstants {
String LWM2M_SEPARATOR_PATH = "/";
String LWM2M_SEPARATOR_KEY = "_";
String LWM2M_SEPARATOR_SEARCH_TEXT = ":";
}

2
common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mInstance.java

@ -20,6 +20,6 @@ import lombok.Data;
@Data
public class LwM2mInstance {
int id;
LwM2mResource [] resources;
LwM2mResourceObserve[] resources;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mObject.java

@ -20,6 +20,7 @@ import lombok.Data;
@Data
public class LwM2mObject {
int id;
String keyId;
String name;
boolean multiple;
boolean mandatory;

4
common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResource.java → common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResourceObserve.java

@ -22,7 +22,7 @@ import java.util.stream.Stream;
@Data
@AllArgsConstructor
public class LwM2mResource {
public class LwM2mResourceObserve {
int id;
String name;
boolean observe;
@ -30,7 +30,7 @@ public class LwM2mResource {
boolean telemetry;
String keyName;
public LwM2mResource(int id, String name, boolean observe, boolean attribute, boolean telemetry) {
public LwM2mResourceObserve(int id, String name, boolean observe, boolean attribute, boolean telemetry) {
this.id = id;
this.name = name;
this.observe = observe;

2
common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.Serializable;
@ -29,6 +30,7 @@ public class DynamicValue<T> implements Serializable {
private T resolvedValue;
private final DynamicValueSourceType sourceType;
@NoXss
private final String sourceAttribute;
private final boolean inherit;

6
common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java

@ -20,6 +20,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.io.Serializable;
@ -27,10 +30,13 @@ import java.io.Serializable;
public class FilterPredicateValue<T> implements Serializable {
@Getter
@NoXss
private final T defaultValue;
@Getter
@NoXss
private final T userValue;
@Getter
@Valid
private final DynamicValue<T> dynamicValue;
public FilterPredicateValue(T defaultValue) {

3
common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java

@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query;
import lombok.Data;
import javax.validation.Valid;
@Data
public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> {
private StringOperation operation;
@Valid
private FilterPredicateValue<String> value;
private boolean ignoreCase;

2
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
@EqualsAndHashCode(callSuper = true)
@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
private static final long serialVersionUID = -5656679015121935465L;
private TenantId tenantId;
@NoXss
private String name;
private RuleChainType type;
private RuleNodeId firstRuleNodeId;

34
common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.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.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = {})
public @interface NoXss {
String message() default "field value is malformed";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

1
common/pom.xml

@ -43,6 +43,7 @@
<module>dao-api</module>
<module>stats</module>
<module>cache</module>
<module>coap-server</module>
<module>edge-api</module>
</modules>

6
common/queue/src/main/proto/queue.proto

@ -205,7 +205,7 @@ message GetResourceRequestMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceType = 3;
string resourceId = 4;
string resourceKey = 4;
}
message GetResourceResponseMsg {
@ -257,14 +257,14 @@ message ResourceUpdateMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceType = 3;
string resourceId = 4;
string resourceKey = 4;
}
message ResourceDeleteMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceType = 3;
string resourceId = 4;
string resourceKey = 4;
}
message SessionCloseNotificationProto {

8
common/transport/coap/pom.xml

@ -40,10 +40,18 @@
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>transport-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>coap-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>scandium</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>

15
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java

@ -18,13 +18,12 @@ package org.thingsboard.server.transport.coap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.transport.coap.efento.adaptor.EfentoCoapAdaptor;
import org.thingsboard.server.transport.coap.adaptors.JsonCoapAdaptor;
import org.thingsboard.server.transport.coap.adaptors.ProtoCoapAdaptor;
import org.thingsboard.server.transport.coap.efento.adaptor.EfentoCoapAdaptor;
/**
@ -35,18 +34,6 @@ import org.thingsboard.server.transport.coap.adaptors.ProtoCoapAdaptor;
@Component
public class CoapTransportContext extends TransportContext {
@Getter
@Value("${transport.coap.bind_address}")
private String host;
@Getter
@Value("${transport.coap.bind_port}")
private Integer port;
@Getter
@Value("${transport.coap.timeout}")
private Long timeout;
@Getter
@Autowired
private JsonCoapAdaptor jsonCoapAdaptor;

238
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -27,6 +27,9 @@ import org.eclipse.californium.core.observe.ObserveRelation;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.core.server.resources.ResourceObserver;
import org.springframework.util.StringUtils;
import org.thingsboard.server.coapserver.CoapServerService;
import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
@ -53,11 +56,8 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ -66,15 +66,24 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private static final int FEATURE_TYPE_POSITION = 4;
private static final int REQUEST_ID_POSITION = 5;
private static final int FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST = 3;
private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4;
private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID";
private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>();
private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet();
public CoapTransportResource(CoapTransportContext coapTransportContext, String name) {
private ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap;
private long timeout;
public CoapTransportResource(CoapTransportContext coapTransportContext, CoapServerService coapServerService, String name) {
super(coapTransportContext, name);
this.setObservable(true); // enable observing
this.addObserver(new CoapResourceObserver());
this.dtlsSessionIdMap = coapServerService.getDtlsSessionsMap();
this.timeout = coapServerService.getTimeout();
// this.setObservable(false); // disable observing
// this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs
// this.getAttributes().setObservable(); // mark observable in the Link-Format
@ -190,111 +199,135 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
Exchange advanced = exchange.advanced();
Request request = advanced.getRequest();
String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
if (!StringUtils.isEmpty(dtlsSessionIdStr)) {
if (dtlsSessionIdMap != null) {
TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
.computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
return dtlsSessionInfo;
});
if (tbCoapDtlsSessionInfo != null) {
processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
} else {
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
}
} else {
processAccessTokenRequest(exchange, type, request);
}
} else {
processAccessTokenRequest(exchange, type, request);
}
}
private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) {
Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
if (credentials.isEmpty()) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
return;
}
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> {
UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
try {
TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());
switch (type) {
case POST_ATTRIBUTES_REQUEST:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToPostAttributes(sessionId, request,
transportConfigurationContainer.getAttributesMsgDescriptor()),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
break;
case POST_TELEMETRY_REQUEST:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToPostTelemetry(sessionId, request,
transportConfigurationContainer.getTelemetryMsgDescriptor()),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
break;
case CLAIM_REQUEST:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
break;
case SUBSCRIBE_ATTRIBUTES_REQUEST:
TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
if (currentAttrSession == null) {
attributeSubscriptions.add(sessionId);
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
transportService.process(sessionInfo,
TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
}
break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
if (attrSession != null) {
UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());
attributeSubscriptions.remove(attrSessionId);
transportService.process(attrSession,
TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
closeAndDeregister(sessionInfo, sessionId);
}
break;
case SUBSCRIBE_RPC_COMMANDS_REQUEST:
TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
if (currentRpcSession == null) {
rpcSubscriptions.add(sessionId);
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
transportService.process(sessionInfo,
TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
new CoapNoOpCallback(exchange));
} else {
UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());
reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));
}
break;
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
if (rpcSession != null) {
UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());
rpcSubscriptions.remove(rpcSessionId);
transportService.process(rpcSession,
TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
closeAndDeregister(sessionInfo, sessionId);
}
break;
case TO_DEVICE_RPC_RESPONSE:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
break;
case TO_SERVER_RPC_REQUEST:
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
transportService.process(sessionInfo,
coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
new CoapNoOpCallback(exchange));
break;
case GET_ATTRIBUTES_REQUEST:
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
transportService.process(sessionInfo,
coapTransportAdaptor.convertToGetAttributes(sessionId, request),
new CoapNoOpCallback(exchange));
break;
}
} catch (AdaptorException e) {
log.trace("[{}] Failed to decode message: ", sessionId, e);
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
}
processRequest(exchange, type, request, sessionInfo, deviceProfile);
}));
}
private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
try {
TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());
switch (type) {
case POST_ATTRIBUTES_REQUEST:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToPostAttributes(sessionId, request,
transportConfigurationContainer.getAttributesMsgDescriptor()),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
break;
case POST_TELEMETRY_REQUEST:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToPostTelemetry(sessionId, request,
transportConfigurationContainer.getTelemetryMsgDescriptor()),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
break;
case CLAIM_REQUEST:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
break;
case SUBSCRIBE_ATTRIBUTES_REQUEST:
TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
if (currentAttrSession == null) {
attributeSubscriptions.add(sessionId);
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
transportService.process(sessionInfo,
TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
}
break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
if (attrSession != null) {
UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());
attributeSubscriptions.remove(attrSessionId);
transportService.process(attrSession,
TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
closeAndDeregister(sessionInfo, sessionId);
}
break;
case SUBSCRIBE_RPC_COMMANDS_REQUEST:
TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
if (currentRpcSession == null) {
rpcSubscriptions.add(sessionId);
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
transportService.process(sessionInfo,
TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
new CoapNoOpCallback(exchange));
} else {
UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());
reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));
}
break;
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
if (rpcSession != null) {
UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());
rpcSubscriptions.remove(rpcSessionId);
transportService.process(rpcSession,
TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
closeAndDeregister(sessionInfo, sessionId);
}
break;
case TO_DEVICE_RPC_RESPONSE:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
break;
case TO_SERVER_RPC_REQUEST:
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), timeout);
transportService.process(sessionInfo,
coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
new CoapNoOpCallback(exchange));
break;
case GET_ATTRIBUTES_REQUEST:
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), timeout);
transportService.process(sessionInfo,
coapTransportAdaptor.convertToGetAttributes(sessionId, request),
new CoapNoOpCallback(exchange));
break;
}
} catch (AdaptorException e) {
log.trace("[{}] Failed to decode message: ", sessionId, e);
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
}
}
private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) {
tokenToNotificationCounterMap.remove(token);
return tokenToSessionIdMap.remove(token);
}
private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, String token) {
@ -314,7 +347,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
List<String> uriPath = request.getOptions().getUriPath();
if (uriPath.size() >= ACCESS_TOKEN_POSITION) {
if (uriPath.size() > ACCESS_TOKEN_POSITION) {
return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
} else {
return Optional.empty();
@ -326,8 +359,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
try {
if (uriPath.size() >= FEATURE_TYPE_POSITION) {
return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
} else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) {
return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
} else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) {
if (uriPath.contains(DataConstants.PROVISION)) {
return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
}
return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST - 1).toUpperCase()));
}
} catch (RuntimeException e) {
log.warn("Failed to decode feature type: {}", uriPath);
@ -340,6 +376,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
try {
if (uriPath.size() >= REQUEST_ID_POSITION) {
return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1)));
} else {
return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION_CERTIFICATE_REQUEST - 1)));
}
} catch (RuntimeException e) {
log.warn("Failed to decode feature type: {}", uriPath);

39
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -24,12 +24,11 @@ import org.eclipse.californium.core.server.resources.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.coapserver.CoapServerService;
import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@Service("CoapTransportService")
@ -42,50 +41,32 @@ public class CoapTransportService {
private static final String EFENTO = "efento";
private static final String MEASUREMENTS = "m";
@Autowired
private CoapServerService coapServerService;
@Autowired
private CoapTransportContext coapTransportContext;
private CoapServer server;
private CoapServer coapServer;
@PostConstruct
public void init() throws UnknownHostException {
log.info("Starting CoAP transport...");
log.info("Starting CoAP transport server");
this.server = new CoapServer();
createResources();
Resource root = this.server.getRoot();
TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
this.server.setMessageDeliverer(messageDeliverer);
InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
CoapEndpoint.Builder coapEndpoitBuilder = new CoapEndpoint.Builder();
coapEndpoitBuilder.setInetSocketAddress(sockAddr);
CoapEndpoint coapEndpoint = coapEndpoitBuilder.build();
server.addEndpoint(coapEndpoint);
server.start();
log.info("CoAP transport started!");
}
private void createResources() {
coapServer = coapServerService.getCoapServer();
CoapResource api = new CoapResource(API);
api.add(new CoapTransportResource(coapTransportContext, V1));
api.add(new CoapTransportResource(coapTransportContext, coapServerService, V1));
CoapResource efento = new CoapResource(EFENTO);
CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS);
efento.add(efentoMeasurementsTransportResource);
server.add(api);
server.add(efento);
coapServer.add(api);
coapServer.add(efento);
log.info("CoAP transport started!");
}
@PreDestroy
public void shutdown() {
log.info("Stopping CoAP transport!");
this.server.destroy();
log.info("CoAP transport stopped!");
}
}

97
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/NoSecClient.java

@ -0,0 +1,97 @@
/**
* 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.coap.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.elements.DtlsEndpointContext;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.exception.ConnectorException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NoSecClient {
private ExecutorService executor = Executors.newFixedThreadPool(1);
private CoapClient coapClient;
public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
this.coapClient = new CoapClient(uri);
}
public void test() {
executor.submit(() -> {
try {
while (!Thread.interrupted()) {
CoapResponse response = null;
try {
response = coapClient.get();
} catch (ConnectorException | IOException e) {
System.err.println("Error occurred while sending request: " + e);
System.exit(-1);
}
if (response != null) {
System.out.println(response.getCode() + " - " + response.getCode().name());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println();
System.out.println("ADVANCED:");
EndpointContext context = response.advanced().getSourceContext();
Principal identity = context.getPeerIdentity();
if (identity != null) {
System.out.println(context.getPeerIdentity());
} else {
System.out.println("anonymous");
}
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Thread.sleep(5000);
}
} catch (Exception e) {
System.out.println("Error occurred while sending COAP requests.");
}
});
}
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
}
public static void main(String[] args) throws URISyntaxException {
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " +
"host port accessToken clientKeys sharedKeys");
String host = args[0];
int port = Integer.parseInt(args[1]);
String accessToken = args[2];
String clientKeys = args[3];
String sharedKeys = args[4];
NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys);
client.test();
}
}

145
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/SecureClientNoAuth.java

@ -0,0 +1,145 @@
/**
* 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.coap.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.elements.DtlsEndpointContext;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SecureClientNoAuth {
private final DTLSConnector dtlsConnector;
private ExecutorService executor = Executors.newFixedThreadPool(1);
private CoapClient coapClient;
public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
this.dtlsConnector = dtlsConnector;
this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys);
}
public void test() {
executor.submit(() -> {
try {
while (!Thread.interrupted()) {
CoapResponse response = null;
try {
response = coapClient.get();
} catch (ConnectorException | IOException e) {
System.err.println("Error occurred while sending request: " + e);
System.exit(-1);
}
if (response != null) {
System.out.println(response.getCode() + " - " + response.getCode().name());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println();
System.out.println("ADVANCED:");
EndpointContext context = response.advanced().getSourceContext();
Principal identity = context.getPeerIdentity();
if (identity != null) {
System.out.println(context.getPeerIdentity());
} else {
System.out.println("anonymous");
}
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Thread.sleep(5000);
}
} catch (Exception e) {
System.out.println("Error occurred while sending COAP requests.");
}
});
}
private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
CoapClient client = new CoapClient(uri);
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setConnector(dtlsConnector);
client.setEndpoint(builder.build());
return client;
}
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
}
public static void main(String[] args) throws URISyntaxException {
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " +
"host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
String host = args[0];
int port = Integer.parseInt(args[1]);
String accessToken = args[2];
String clientKeys = args[7];
String sharedKeys = args[8];
String keyStoreUriPath = args[3];
String keyStoreAlias = args[4];
String trustedAliasPattern = args[5];
String keyStorePassword = args[6];
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys);
client.test();
}
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
try {
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
trustBuilder.setTrustedCertificates(trustedCertificates);
config.setAdvancedCertificateVerifier(trustBuilder.build());
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
} catch (GeneralSecurityException e) {
System.err.println("certificates are invalid!");
throw new IllegalArgumentException(e.getMessage());
} catch (IOException e) {
System.err.println("certificates are missing!");
throw new IllegalArgumentException(e.getMessage());
}
}
}

144
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/SecureClientX509.java

@ -0,0 +1,144 @@
/**
* 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.coap.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.elements.DtlsEndpointContext;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SecureClientX509 {
private final DTLSConnector dtlsConnector;
private ExecutorService executor = Executors.newFixedThreadPool(1);
private CoapClient coapClient;
public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException {
this.dtlsConnector = dtlsConnector;
this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys);
}
public void test() {
executor.submit(() -> {
try {
while (!Thread.interrupted()) {
CoapResponse response = null;
try {
response = coapClient.get();
} catch (ConnectorException | IOException e) {
System.err.println("Error occurred while sending request: " + e);
System.exit(-1);
}
if (response != null) {
System.out.println(response.getCode() + " - " + response.getCode().name());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println();
System.out.println("ADVANCED:");
EndpointContext context = response.advanced().getSourceContext();
Principal identity = context.getPeerIdentity();
if (identity != null) {
System.out.println(context.getPeerIdentity());
} else {
System.out.println("anonymous");
}
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Thread.sleep(5000);
}
} catch (Exception e) {
System.out.println("Error occurred while sending COAP requests.");
}
});
}
private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException {
URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys));
CoapClient client = new CoapClient(uri);
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setConnector(dtlsConnector);
client.setEndpoint(builder.build());
return client;
}
private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) {
return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
}
public static void main(String[] args) throws URISyntaxException {
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " +
"host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
String host = args[0];
int port = Integer.parseInt(args[1]);
String clientKeys = args[6];
String sharedKeys = args[7];
String keyStoreUriPath = args[2];
String keyStoreAlias = args[3];
String trustedAliasPattern = args[4];
String keyStorePassword = args[5];
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys);
client.test();
}
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
try {
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
trustBuilder.setTrustedCertificates(trustedCertificates);
config.setAdvancedCertificateVerifier(trustBuilder.build());
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
} catch (GeneralSecurityException e) {
System.err.println("certificates are invalid!");
throw new IllegalArgumentException(e.getMessage());
} catch (IOException e) {
System.err.println("certificates are missing!");
throw new IllegalArgumentException(e.getMessage());
}
}
}

4
common/transport/lwm2m/pom.xml

@ -40,6 +40,10 @@
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>transport-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>

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

@ -166,13 +166,13 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
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());
context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
context.sendParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, 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());
context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
context.sendParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
return null;
}
}

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

@ -26,6 +26,8 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate;
import java.util.Collection;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
@Slf4j
public class LwM2mServerListener {
@ -90,7 +92,7 @@ public class LwM2mServerListener {
public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
if (registration != null) {
try {
service.onObservationResponse(registration, observation.getPath().toString(), response);
service.onObservationResponse(registration, convertToIdVerFromObjectId(observation.getPath().toString(), registration), response);
} catch (Exception e) {
log.error("[{}] onResponse", e.toString());

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

@ -20,10 +20,11 @@ import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
@ -85,4 +86,16 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
public void operationComplete(Future<? super Void> future) throws Exception {
log.info("[{}] operationComplete", future);
}
public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) {
this.service.onResourceUpdate(resourceUpdateMsgOpt);
}
}
public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) {
this.service.onResourceDelete(resourceDeleteMsgOpt);
}
}
}

14
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java

@ -89,12 +89,12 @@ public class LwM2mTransportContextServer extends TransportContext {
}
/**
* Sent to Thingsboard Attribute || Telemetry
* send to Thingsboard Attribute || Telemetry
*
* @param msg - JsonObject: [{name: value}]
* @return - dummy
*/
private <T> TransportServiceCallback<Void> getPubAckCallbackSentAttrTelemetry(final T msg) {
private <T> TransportServiceCallback<Void> getPubAckCallbackSendAttrTelemetry(final T msg) {
return new TransportServiceCallback<>() {
@Override
public void onSuccess(Void dummy) {
@ -108,16 +108,16 @@ public class LwM2mTransportContextServer extends TransportContext {
};
}
public void sentParametersOnThingsboard(JsonElement msg, String topicName, SessionInfoProto sessionInfo) {
public void sendParametersOnThingsboard(JsonElement msg, String topicName, SessionInfoProto sessionInfo) {
try {
if (topicName.equals(LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) {
PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSentAttrTelemetry(call));
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call));
} else if (topicName.equals(LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC)) {
PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(call));
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call));
}
} catch (AdaptorException e) {
log.error("[{}] Failed to process publish msg [{}]", topicName, e);

104
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java

@ -22,6 +22,7 @@ import com.google.gson.JsonSyntaxException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNode;
@ -32,6 +33,7 @@ import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.registration.Registration;
import org.nustaq.serialization.FSTConfiguration;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
@ -47,14 +49,12 @@ import java.util.Date;
import java.util.LinkedList;
import java.util.Optional;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
@Slf4j
//@Component("LwM2MTransportHandler")
//@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2mTransportHandler {
// We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to
// send a Confirmable message to the time when an acknowledgement is no longer expected.
public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String ATTRIBUTE = "attribute";
public static final String TELEMETRY = "telemetry";
@ -84,6 +84,8 @@ public class LwM2mTransportHandler {
public static final String LOG_LW2M_ERROR = "error";
public static final String LOG_LW2M_WARN = "warn";
public static final int LWM2M_STRATEGY_1 = 1;
public static final int LWM2M_STRATEGY_2 = 2;
public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
@ -110,39 +112,6 @@ public class LwM2mTransportHandler {
public static final String SERVICE_CHANNEL = "SERVICE";
public static final String RESPONSE_CHANNEL = "RESP";
// @Autowired
// @Qualifier("LeshanServerCert")
// private LeshanServer lhServerCert;
//
// @Autowired
// @Qualifier("LeshanServerNoSecPskRpk")
// private LeshanServer lhServerNoSecPskRpk;
// @Autowired
// @Qualifier("ServerListenerCert")
// private LwM2mServerListener serverListenerCert;
//
// @Autowired
// @Qualifier("ServerListenerNoSecPskRpk")
// private LwM2mServerListener serverListenerNoSecPskRpk;
// @PostConstruct
// public void init() {
// try {
// serverListenerCert.init(lhServerCert);
// this.lhServerCert.getRegistrationService().addListener(serverListenerCert.registrationListener);
// this.lhServerCert.getPresenceService().addListener(serverListenerCert.presenceListener);
// this.lhServerCert.getObservationService().addListener(serverListenerCert.observationListener);
// serverListenerNoSecPskRpk.init(lhServerNoSecPskRpk);
// this.lhServerNoSecPskRpk.getRegistrationService().addListener(serverListenerNoSecPskRpk.registrationListener);
// this.lhServerNoSecPskRpk.getPresenceService().addListener(serverListenerNoSecPskRpk.presenceListener);
// this.lhServerNoSecPskRpk.getObservationService().addListener(serverListenerNoSecPskRpk.observationListener);
// } catch (Exception e) {
// log.error("init [{}]", e.toString());
// }
// }
public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) {
NetworkConfig coapConfig;
File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME);
@ -202,10 +171,10 @@ public class LwM2mTransportHandler {
/**
* @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard
* Example:
* Example:
* property: {"clientLwM2mSettings": {
* clientUpdateValueAfterConnect: false;
* }
* clientUpdateValueAfterConnect: false;
* }
* property: "observeAttr"
* {"keyName": {
* "/3/0/1": "modelNumber",
@ -222,7 +191,7 @@ public class LwM2mTransportHandler {
try {
ObjectMapper mapper = new ObjectMapper();
String profileStr = mapper.writeValueAsString(profile);
JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
} catch (IOException e) {
log.error("", e);
@ -246,9 +215,9 @@ public class LwM2mTransportHandler {
return null;
}
public static boolean getClientOnlyObserveAfterConnect (LwM2mClientProfile profile) {
return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientOnlyObserveAfterConnect") &&
profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientOnlyObserveAfterConnect").getAsBoolean();
public static int getClientOnlyObserveAfterConnect(LwM2mClientProfile profile) {
return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientOnlyObserveAfterConnect") ?
profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientOnlyObserveAfterConnect").getAsInt() : 1;
}
private static boolean getValidateCredentialsBodyFromThingsboard(JsonObject objectMsg) {
@ -346,4 +315,49 @@ public class LwM2mTransportHandler {
}
};
}
public static String convertToObjectIdFromIdVer(String key) {
try {
String[] keyArray = key.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) {
keyArray[1] = keyArray[1].split(LWM2M_SEPARATOR_KEY)[0];
return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH);
} else {
return key;
}
} catch (Exception e) {
return null;
}
}
public static String convertToIdVerFromObjectId(String path, Registration registration) {
String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId());
try {
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1) {
keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver;
return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH);
} else {
return path;
}
} catch (Exception e) {
return null;
}
}
public static Integer validateObjectIdFromKey(String key) {
try {
return Integer.parseInt(key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[0]);
} catch (Exception e) {
return null;
}
}
public static String validateObjectVerFromKey(String key) {
try {
return (key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[1]);
} catch (Exception e) {
return ObjectModel.DEFAULT_VERSION;
}
}
}

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

@ -72,6 +72,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
@Slf4j
@Service
@ -81,7 +83,7 @@ public class LwM2mTransportRequest {
private LwM2mValueConverterImpl converter;
private final LwM2mTransportContextServer context;
private final LwM2mTransportContextServer lwM2mTransportContextServer;
private final LwM2mClientContext lwM2mClientContext;
@ -89,8 +91,8 @@ public class LwM2mTransportRequest {
private final LwM2mTransportServiceImpl serviceImpl;
public LwM2mTransportRequest(LwM2mTransportContextServer context, LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, LwM2mTransportServiceImpl serviceImpl) {
this.context = context;
public LwM2mTransportRequest(LwM2mTransportContextServer lwM2mTransportContextServer, LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, LwM2mTransportServiceImpl serviceImpl) {
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
this.lwM2mClientContext = lwM2mClientContext;
this.leshanServer = leshanServer;
this.serviceImpl = serviceImpl;
@ -99,7 +101,7 @@ public class LwM2mTransportRequest {
@PostConstruct
public void init() {
this.converter = LwM2mValueConverterImpl.getInstance();
executorResponse = Executors.newFixedThreadPool(this.context.getLwM2MTransportConfigServer().getRequestPoolSize(),
executorResponse = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResponsePoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL)));
}
@ -107,18 +109,20 @@ public class LwM2mTransportRequest {
* Device management and service enablement, including Read, Write, Execute, Discover, Create, Delete and Write-Attributes
*
* @param registration -
* @param target -
* @param targetIdVer -
* @param typeOper -
* @param contentFormatParam -
* @param observation -
*/
public void sendAllRequest(Registration registration, String target, String typeOper,
public void sendAllRequest(Registration registration, String targetIdVer, String typeOper,
String contentFormatParam, Observation observation, Object params, long timeoutInMs) {
String target = convertToObjectIdFromIdVer(targetIdVer);
LwM2mPath resultIds = new LwM2mPath(target);
if (registration != null && resultIds.getObjectId() >= 0) {
DownlinkRequest request = null;
ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null;
ResourceModel resource = serviceImpl.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResourceModel(registration, resultIds);
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
ResourceModel resource = lwM2MClient.getResourceModel(target);
timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
switch (typeOper) {
case GET_TYPE_OPER_READ:
@ -149,11 +153,13 @@ public class LwM2mTransportRequest {
case POST_TYPE_OPER_WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
if (resource != null && contentFormat != null) {
if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
// if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
if (contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration);
}
// Mode.REPLACE && Request to write a <b>String Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON)
else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
// else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
else if (!contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration);
}
}
@ -213,7 +219,10 @@ public class LwM2mTransportRequest {
}
if (request != null) {
this.sendRequest(registration, request, timeoutInMs);
this.sendRequest(registration, lwM2MClient, request, timeoutInMs);
}
else {
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer);
}
}
}
@ -226,12 +235,10 @@ public class LwM2mTransportRequest {
*/
@SuppressWarnings("unchecked")
private void sendRequest(Registration registration, DownlinkRequest request, long timeoutInMs) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs) {
leshanServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, request.getPath().toString());
lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration));
}
if (isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request);
@ -239,23 +246,23 @@ public class LwM2mTransportRequest {
String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client",
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString());
serviceImpl.sentLogsToThingsboard(msg, registration);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue());
}
} else {
String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR,
((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
serviceImpl.sentLogsToThingsboard(msg, registration);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.error("[{}], [{}] - [{}] [{}] error SendRequest", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
}
}, e -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, request.getPath().toString());
lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration));
}
String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client",
LOG_LW2M_ERROR, request.getPath().toString(), e.toString());
serviceImpl.sentLogsToThingsboard(msg, registration);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString());
});
@ -287,7 +294,7 @@ 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.sentLogsToThingsboard(msg, registration);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString());
return null;
}
@ -310,21 +317,22 @@ public class LwM2mTransportRequest {
* @param response -
*/
private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request) {
String pathIdVer = convertToIdVerFromObjectId(path, registration);
if (response instanceof ReadResponse) {
serviceImpl.onObservationResponse(registration, path, (ReadResponse) response);
serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response);
} else if (response instanceof CancelObservationResponse) {
log.info("[{}] Path [{}] CancelObservationResponse 3_Send", path, response);
log.info("[{}] Path [{}] CancelObservationResponse 3_Send", pathIdVer, response);
} else if (response instanceof DeleteResponse) {
log.info("[{}] Path [{}] DeleteResponse 5_Send", path, response);
log.info("[{}] Path [{}] DeleteResponse 5_Send", pathIdVer, response);
} else if (response instanceof DiscoverResponse) {
log.info("[{}] Path [{}] DiscoverResponse 6_Send", path, response);
log.info("[{}] Path [{}] DiscoverResponse 6_Send", pathIdVer, response);
} else if (response instanceof ExecuteResponse) {
log.info("[{}] Path [{}] ExecuteResponse 7_Send", path, response);
log.info("[{}] Path [{}] ExecuteResponse 7_Send", pathIdVer, response);
} else if (response instanceof WriteAttributesResponse) {
log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", path, response);
log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", pathIdVer, response);
} else if (response instanceof WriteResponse) {
log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", path, response);
serviceImpl.onWriteResponseOk(registration, path, (WriteRequest) request);
log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", pathIdVer, response);
serviceImpl.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request);
}
}
}

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

@ -47,6 +47,10 @@ public interface LwM2mTransportService {
void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt);
void onResourceUpdate (Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt);
void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt);
void doTrigger(Registration registration, String path);
void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);

281
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java

@ -52,7 +52,6 @@ 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 org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
@ -73,9 +72,6 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject;
@ -88,10 +84,15 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectIdFromKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey;
@Slf4j
@Service
@ -102,8 +103,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private ExecutorService executorUpdateRegistered;
private ExecutorService executorUnRegistered;
private LwM2mValueConverterImpl converter;
protected final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
protected final Lock writeLock = readWriteLock.writeLock();
private final TransportService transportService;
@ -157,15 +156,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (lwM2MClient != null) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (sessionInfo != null) {
lwM2MClient.setDeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
lwM2MClient.setProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
lwM2MClient.setDeviceName(sessionInfo.getDeviceName());
lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType());
this.initLwM2mClient(lwM2MClient, sessionInfo);
transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo));
transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration);
this.initLwM2mFromClientValue(registration, lwM2MClient);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration);
} else {
log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null);
}
@ -189,6 +185,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (sessionInfo != null) {
this.checkInactivity(sessionInfo);
LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2MClient(sessionInfo);
if (lwM2MClient.getDeviceId() == null && lwM2MClient.getProfileId() == null) {
initLwM2mClient(lwM2MClient, sessionInfo);
}
log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
} else {
log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null);
@ -208,7 +209,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
executorUnRegistered.submit(() -> {
try {
this.setCancelObservations(registration);
this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration);
this.closeClientSession(registration);
} catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t);
@ -216,6 +217,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
});
}
private void initLwM2mClient(LwM2mClient lwM2MClient, SessionInfoProto sessionInfo) {
lwM2MClient.setDeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
lwM2MClient.setProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
lwM2MClient.setDeviceName(sessionInfo.getDeviceName());
lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType());
}
private void closeClientSession(Registration registration) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (sessionInfo != null) {
@ -278,7 +286,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* Update - sent request in change value resources in Client
* Update - send request in change value resources in Client
* Path to resources from profile equal keyName or from ModelObject equal name
* Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase)
* Delete - nothing *
@ -290,26 +298,26 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (msg.getSharedUpdatedCount() > 0) {
JsonElement el = JsonConverter.toJson(msg);
el.getAsJsonObject().entrySet().forEach(de -> {
String path = this.getPathAttributeUpdate(sessionInfo, de.getKey());
String pathIdVer = this.getPathAttributeUpdate(sessionInfo, de.getKey());
String value = de.getValue().getAsString();
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));
LwM2mClientProfile clientProfile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
if (path != null && !path.isEmpty() && (this.validatePathInAttrProfile(clientProfile, path) || this.validatePathInTelemetryProfile(clientProfile, path))) {
ResourceModel resourceModel = lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(path));
if (pathIdVer != null && !pathIdVer.isEmpty() && (this.validatePathInAttrProfile(clientProfile, pathIdVer) || this.validatePathInTelemetryProfile(clientProfile, pathIdVer))) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer);
if (resourceModel != null && resourceModel.operations.isWritable()) {
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE,
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), pathIdVer, POST_TYPE_OPER_WRITE_REPLACE,
ContentFormat.TLV.getName(), null, value, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout());
} else {
log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value);
log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, value);
String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated",
LOG_LW2M_ERROR, path, value);
this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
LOG_LW2M_ERROR, pathIdVer, value);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
}
} else {
log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value);
String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated",
LOG_LW2M_ERROR, de.getKey(), value);
this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
}
});
} else if (msg.getSharedDeletedCount() > 0) {
@ -347,8 +355,28 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* Trigger Server path = "/1/0/8"
*
* @param resourceUpdateMsgOpt -
*/
@Override
public void onResourceUpdate (Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
String idVer = resourceUpdateMsgOpt.get().getResourceKey();
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
}
/**
*
* @param resourceDeleteMsgOpt -
*/
@Override
public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey();
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
}
/**
* Trigger Server path = "/1/0/8"
* <p>
* Trigger bootStrap path = "/1/0/9" - have to implemented on client
*/
@Override
@ -417,7 +445,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param msg - text msg
* @param registration - Id of Registration LwM2M Client
*/
public void sentLogsToThingsboard(String msg, Registration registration) {
public void sendLogsToThingsboard(String msg, Registration registration) {
if (msg != null) {
JsonObject telemetries = new JsonObject();
telemetries.addProperty(LOG_LW2M_TELEMETRY, msg);
@ -428,7 +456,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* // !!! Ok
* Prepare Sent to Thigsboard callback - Attribute or Telemetry
* Prepare send to Thigsboard callback - Attribute or Telemetry
*
* @param msg - JsonArray: [{name: value}]
* @param topicName - Api Attribute or Telemetry
@ -437,7 +465,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (sessionInfo != null) {
lwM2mTransportContextServer.sentParametersOnThingsboard(msg, topicName, sessionInfo);
lwM2mTransportContextServer.sendParametersOnThingsboard(msg, topicName, sessionInfo);
} else {
log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null);
}
@ -458,7 +486,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
Set<String> clientObjects = this.getAllOjectsInClient(registration);
if (clientObjects != null && !LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
if (clientObjects != null && LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
// #2
lwM2MClient.getPendingRequests().addAll(clientObjects);
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(),
@ -499,20 +527,23 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* #1 Return old Value Resource from LwM2MClient
* #2 Update new Resources (replace old Resource Value on new Resource Value)
*
* @param registration - Registration LwM2M Client
* @param registration - Registration LwM2M Client
* @param lwM2mResource - LwM2mSingleResource response.getContent()
* @param path - resource
* @param path - resource
*/
private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
lwM2MClient.updateResourceValue(path, lwM2mResource);
Set<String> paths = new HashSet<>();
paths.add(path);
this.updateAttrTelemetry(registration, paths);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())) {
Set<String> paths = new HashSet<>();
paths.add(path);
this.updateAttrTelemetry(registration, paths);
} else {
log.error("Fail update Resource [{}]", lwM2mResource);
}
}
/**
* Sent Attribute and Telemetry to Thingsboard
* send Attribute and Telemetry to Thingsboard
* #1 - get AttrName/TelemetryName with value from LwM2MClient:
* -- resourceId == path from LwM2MClientProfile.postAttributeProfile/postTelemetryProfile/postObserveProfile
* -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId)
@ -524,12 +555,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
JsonObject attributes = new JsonObject();
JsonObject telemetries = new JsonObject();
try {
writeLock.lock();
this.getParametersFromProfile(attributes, telemetries, registration, paths);
} catch (Exception e) {
log.error("UpdateAttrTelemetry", e);
} finally {
writeLock.unlock();
}
if (attributes.getAsJsonObject().entrySet().size() > 0)
this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration);
@ -539,13 +567,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* @param clientProfile -
* @param path -
* @param path -
* @return true if path isPresent in postAttributeProfile
*/
private boolean validatePathInAttrProfile(LwM2mClientProfile clientProfile, String path) {
try {
List<String> attributesSet = new Gson().fromJson(clientProfile.getPostAttributeProfile(), new TypeToken<>() {
}.getType());
List<String> attributesSet = new Gson().fromJson(clientProfile.getPostAttributeProfile(),
new TypeToken<List<String>>() {
}.getType());
return attributesSet.stream().anyMatch(p -> p.equals(path));
} catch (Exception e) {
log.error("Fail Validate Path [{}] ClientProfile.Attribute", path, e);
@ -555,12 +584,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* @param clientProfile -
* @param path -
* @param path -
* @return true if path isPresent in postAttributeProfile
*/
private boolean validatePathInTelemetryProfile(LwM2mClientProfile clientProfile, String path) {
try {
List<String> telemetriesSet = new Gson().fromJson(clientProfile.getPostTelemetryProfile(), new TypeToken<>() {
List<String> telemetriesSet = new Gson().fromJson(clientProfile.getPostTelemetryProfile(), new TypeToken<List<String>>() {
}.getType());
return telemetriesSet.stream().anyMatch(p -> p.equals(path));
} catch (Exception e) {
@ -581,22 +610,25 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
Set<String> clientInstances = this.getAllInstancesInClient(registration);
Set<String> result;
if (GET_TYPE_OPER_READ.equals(typeOper)) {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() {});
result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() {}));
result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() {
});
result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() {
}));
} else {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() {});
result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() {
});
}
Set<String> pathSent = ConcurrentHashMap.newKeySet();
Set<String> pathSend = ConcurrentHashMap.newKeySet();
result.forEach(target -> {
// #1.1
String[] resPath = target.split("/");
String instance = "/" + resPath[1] + "/" + resPath[2];
if (clientInstances != null && clientInstances.size() > 0 && clientInstances.contains(instance)) {
pathSent.add(target);
pathSend.add(target);
}
});
lwM2MClient.getPendingRequests().addAll(pathSent);
pathSent.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
lwM2MClient.getPendingRequests().addAll(pathSend);
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
lwM2MClient.initValue(this, null);
@ -646,7 +678,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
LwM2mPath pathIds = new LwM2mPath(url.getUrl());
if (pathIds.isObjectInstance()) {
clientInstances.add(url.getUrl());
clientInstances.add(convertToIdVerFromObjectId(url.getUrl(), registration));
}
});
return (clientInstances.size() > 0) ? clientInstances : null;
@ -656,26 +688,22 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param attributes - new JsonObject
* @param telemetry - new JsonObject
* @param registration - Registration LwM2M Client
* @param path -
* @param path -
*/
private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set<String> path) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
lwM2MClientProfile.getPostAttributeProfile().forEach(p -> {
LwM2mPath pathIds = new LwM2mPath(p.getAsString());
if (pathIds.isResource()) {
if (path == null || path.contains(p.getAsString())) {
this.addParameters(p.getAsString(), attributes, registration);
if (path != null && path.size() > 0) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
lwM2MClientProfile.getPostAttributeProfile().forEach(idVer -> {
if (path.contains(idVer.getAsString())) {
this.addParameters(idVer.getAsString(), attributes, registration);
}
}
});
lwM2MClientProfile.getPostTelemetryProfile().forEach(p -> {
LwM2mPath pathIds = new LwM2mPath(p.getAsString());
if (pathIds.isResource()) {
if (path == null || path.contains(p.getAsString())) {
this.addParameters(p.getAsString(), telemetry, registration);
});
lwM2MClientProfile.getPostTelemetryProfile().forEach(idVer -> {
if (path.contains(idVer.getAsString())) {
this.addParameters(idVer.getAsString(), telemetry, registration);
}
}
});
});
}
}
/**
@ -685,7 +713,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private void addParameters(String path, JsonObject parameters, Registration registration) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile();
String resName = String.valueOf(names.get(path));
String resName = names.get(path).getAsString();
if (resName != null && !resName.isEmpty()) {
try {
String resValue = this.getResourceValueToString(lwM2MClient, path);
@ -703,22 +731,21 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @return - value of Resource or null
*/
private String getResourceValueToString(LwM2mClient lwM2MClient, String path) {
LwM2mPath pathIds = new LwM2mPath(path);
ResourceValue resourceValue = this.returnResourceValueFromLwM2MClient(lwM2MClient, pathIds);
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(path));
LwM2mResource resourceValue = this.returnResourceValueFromLwM2MClient(lwM2MClient, path);
return resourceValue == null ? null :
this.converter.convertValue(resourceValue.getResourceValue(), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResourceModelType(lwM2MClient.getRegistration(), pathIds), ResourceModel.Type.STRING, pathIds).toString();
this.converter.convertValue(resourceValue.isMultiInstances() ? resourceValue.getValues() : resourceValue.getValue(), resourceValue.getType(), ResourceModel.Type.STRING, pathIds).toString();
}
/**
*
* @param lwM2MClient -
* @param pathIds -
* @param path -
* @return - return value of Resource by idPath
*/
private ResourceValue returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, LwM2mPath pathIds) {
ResourceValue resourceValue = null;
if (pathIds.isResource()) {
resourceValue = lwM2MClient.getResources().get(pathIds.toString());
private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
LwM2mResource resourceValue = null;
if (new LwM2mPath(convertToObjectIdFromIdVer(path)).isResource()) {
resourceValue = lwM2MClient.getResources().get(path).getLwM2mResource();
}
return resourceValue;
}
@ -742,15 +769,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* #3.2 Telemetry isChange (add&del)
* #3.3 KeyName isChange (add)
* #4 update
* #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and sent Value to thingsboard
* #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard
* #4.2 del
* -- if add attributes includes del telemetry - result del for observe
* #5
* #5.1 Observe isChange (add&del)
* #5.2 Observe.add
* -- path Attr/Telemetry includes newObserve and does not include oldObserve: sent Request observe to Client
* -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client
* #5.3 Observe.del
* -- different between newObserve and oldObserve: sent Request cancel observe to client
* -- different between newObserve and oldObserve: send Request cancel observe to client
*
* @param registrationIds -
* @param deviceProfile -
@ -775,20 +802,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile();
// #3
ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters();
ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters();
// #3.1
if (!attributeOld.equals(attributeNew)) {
ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, new TypeToken<Set<String>>() {
}.getType()), attributeSetNew);
sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
}
// #3.2
if (!telemetryOld.equals(telemetryNew)) {
ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, new TypeToken<Set<String>>() {
}.getType()), telemetrySetNew);
sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
}
// #3.3
if (!keyNameOld.equals(keyNameNew)) {
@ -796,52 +823,53 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}.getType()),
new Gson().fromJson(keyNameNew.toString(), new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType()));
sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
}
// #4.1 add
if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
// update value in Resources
registrationIds.forEach(registrationId -> {
// LeshanServer lwServer = leshanServer;
Registration registration = lwM2mClientContext.getRegistration(registrationId);
this.readResourceValueObserve(registration, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ);
// sent attr/telemetry to tingsboard for new path
this.updateAttrTelemetry(registration, sentAttrToThingsboard.getPathPostParametersAdd());
this.readResourceValueObserve(registration, sendAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ);
// send attr/telemetry to tingsboard for new path
this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd());
});
}
// #4.2 del
if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) {
ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel());
sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel());
if (sendAttrToThingsboard.getPathPostParametersDel().size() > 0) {
ResultsAnalyzerParameters sendAttrToThingsboardDel = this.getAnalyzerParameters(sendAttrToThingsboard.getPathPostParametersAdd(), sendAttrToThingsboard.getPathPostParametersDel());
sendAttrToThingsboard.setPathPostParametersDel(sendAttrToThingsboardDel.getPathPostParametersDel());
}
// #5.1
if (!observeOld.equals(observeNew)) {
Set<String> observeSetOld = new Gson().fromJson(observeOld, new TypeToken<>() {}.getType());
Set<String> observeSetNew = new Gson().fromJson(observeNew, new TypeToken<>() {}.getType());
Set<String> observeSetOld = new Gson().fromJson(observeOld, new TypeToken<Set<String>>() {
}.getType());
Set<String> observeSetNew = new Gson().fromJson(observeNew, new TypeToken<Set<String>>() {
}.getType());
//#5.2 add
// path Attr/Telemetry includes newObserve
attributeSetOld.addAll(telemetrySetOld);
ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe
ResultsAnalyzerParameters sendObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe
attributeSetNew.addAll(telemetrySetNew);
ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe
ResultsAnalyzerParameters sendObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe
// does not include oldObserve
ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd());
// sent Request observe to Client
ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd());
// send Request observe to Client
registrationIds.forEach(registrationId -> {
Registration registration = lwM2mClientContext.getRegistration(registrationId);
this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE);
// 5.3 del
// sent Request cancel observe to Client
// send Request cancel observe to Client
this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel());
});
}
}
}
private Set <String> convertJsonArrayToSet (JsonArray jsonArray) {
List<String> attributeListOld = new Gson().fromJson(jsonArray, new TypeToken<>() {
private Set<String> convertJsonArrayToSet(JsonArray jsonArray) {
List<String> attributeListOld = new Gson().fromJson(jsonArray, new TypeToken<List<String>>() {
}.getType());
return Sets.newConcurrentHashSet(attributeListOld);
}
@ -874,14 +902,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* Update Resource value after change RezAttrTelemetry in config Profile
* sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue()
* send response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue()
*
* @param registration - Registration LwM2M Client
* @param targets - path Resources == [ "/2/0/0", "/2/0/1"]
*/
private void readResourceValueObserve(Registration registration, Set<String> targets, String typeOper) {
targets.forEach(target -> {
LwM2mPath pathIds = new LwM2mPath(target);
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(target));
if (pathIds.isResource()) {
if (GET_TYPE_OPER_READ.equals(typeOper)) {
lwM2mTransportRequest.sendAllRequest(registration, target, typeOper,
@ -907,8 +935,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private void cancelObserveIsValue(Registration registration, Set<String> paramAnallyzer) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
paramAnallyzer.forEach(p -> {
if (this.returnResourceValueFromLwM2MClient(lwM2MClient, new LwM2mPath(p)) != null) {
this.setCancelObservationRecourse(registration, p);
if (this.returnResourceValueFromLwM2MClient(lwM2MClient, p) != null) {
this.setCancelObservationRecourse(registration, convertToObjectIdFromIdVer(p));
}
}
);
@ -953,8 +981,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) {
LwM2mClientProfile profile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
LwM2mClient lwM2mClient = lwM2mClientContext.getLwM2MClient(sessionInfo);
return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream()
.filter(e -> e.getValue().getAsString().equals(name)).findFirst().map(Map.Entry::getKey)
.filter(e -> e.getValue().getAsString().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey)
.orElse("");
}
@ -963,7 +992,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* #1 Get path resource by result attributesResponse
* #1.1 If two names have equal path => last time attribute
* #2.1 if there is a difference in values between the current resource values and the shared attribute values
* => sent to client Request Update of value (new value from shared attribute)
* => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path)
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
@ -975,11 +1004,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo);
attributesResponse.getSharedAttributeListList().forEach(attr -> {
String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey());
// #1.1
if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) {
lwM2MClient.getDelayedRequests().put(path, attr);
} else {
lwM2MClient.getDelayedRequests().put(path, attr);
if (path != null) {
// #1.1
if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) {
lwM2MClient.getDelayedRequests().put(path, attr);
} else {
lwM2MClient.getDelayedRequests().put(path, attr);
}
}
});
// #2.1
@ -1057,6 +1088,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* !!! sharedAttr === profileAttr !!!
* If there is a difference in values between the current resource values and the shared attribute values
* when the client connects to the server
* #1 get attributes name from profile include name resources in ModelObject if resource isWritable
@ -1083,24 +1115,37 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* Get names and keyNames from profile shared!!!! attr resources IsWritable
* !!! sharedAttr === profileAttr !!!
* Get names or keyNames from profile: resources IsWritable
*
* @param lwM2MClient -
* @return ArrayList keyNames from profile attr resources shared!!!! && IsWritable
* @return ArrayList keyNames from profile profileAttr && IsWritable
*/
private List<String> getNamesAttrFromProfileIsWritable(LwM2mClient lwM2MClient) {
LwM2mClientProfile profile = lwM2mClientContext.getProfile(lwM2MClient.getProfileId());
Set<String> attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), new TypeToken<>() {}.getType());
ConcurrentMap<String, String> keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), new TypeToken<ConcurrentHashMap<String, String>>() {}.getType());
Set<String> attrSet = new Gson().fromJson(profile.getPostAttributeProfile(),
new TypeToken<HashSet<String>>() {
}.getType());
ConcurrentMap<String, String> keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(),
new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType());
ConcurrentMap<String, String> keyNamesIsWritable = keyNamesMap.entrySet()
.stream()
.filter(e -> (attrSet.contains(e.getKey()) && lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())) != null &&
lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())).operations.isWritable()))
.filter(e -> (attrSet.contains(e.getKey()) && validateResourceInModel(lwM2MClient, e.getKey(), true)))
.collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue));
Set<String> namesIsWritable = ConcurrentHashMap.newKeySet();
namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values()));
return new ArrayList<>(namesIsWritable);
}
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathKey, boolean isWritable) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathKey);
Integer objectId = validateObjectIdFromKey(pathKey);
String objectVer = validateObjectVerFromKey(pathKey);
return resourceModel != null && (isWritable ?
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
}
}

45
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java

@ -22,6 +22,7 @@ import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
@ -30,8 +31,10 @@ import java.util.Base64;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import static org.thingsboard.server.common.data.ResourceType.LWM2M_MODEL;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
@Slf4j
public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
@ -49,12 +52,9 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
this.lwM2mClientContext = lwM2mClientContext;
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
}
private String getIdVer(ObjectModel objectModel) {
return objectModel.id + "##" + ((objectModel.getVersion() == null || objectModel.getVersion().isEmpty()) ? ObjectModel.DEFAULT_VERSION : objectModel.getVersion());
}
private String getIdVer(Integer objectId, String version) {
return objectId != null ? objectId + "##" + ((version == null || version.isEmpty()) ? ObjectModel.DEFAULT_VERSION : version) : null;
private String getKeyIdVer(Integer objectId, String version) {
return objectId != null ? objectId + LWM2M_SEPARATOR_KEY + ((version == null || version.isEmpty()) ? ObjectModel.DEFAULT_VERSION : version) : null;
}
/**
@ -65,8 +65,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
*/
@Override
public LwM2mModel getObjectModel(Registration registration) {
return new DynamicModel(registration
);
return new DynamicModel(registration);
}
private class DynamicModel implements LwM2mModel {
@ -86,6 +85,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
if (objectModel != null)
return objectModel.resources.get(resourceId);
else
log.warn("TbResources (Object model) with id [{}/0/{}] not found on the server", objectId, resourceId);
return null;
} catch (Exception e) {
log.error("", e);
@ -105,12 +105,11 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
@Override
public Collection<ObjectModel> getObjectModels() {
Map<Integer, String> supportedObjects = this.registration.getSupportedObject();
Collection<ObjectModel> result = new ArrayList(supportedObjects.size());
Iterator i$ = supportedObjects.entrySet().iterator();
Collection<ObjectModel> result = new ArrayList<>(supportedObjects.size());
Iterator<Map.Entry<Integer, String>> i$ = supportedObjects.entrySet().iterator();
while (i$.hasNext()) {
Map.Entry<Integer, String> supportedObject = (Map.Entry) i$.next();
ObjectModel objectModel = this.getObjectModelDynamic((Integer) supportedObject.getKey(), (String) supportedObject.getValue());
Map.Entry<Integer, String> supportedObject = i$.next();
ObjectModel objectModel = this.getObjectModelDynamic(supportedObject.getKey(), supportedObject.getValue());
if (objectModel != null) {
result.add(objectModel);
}
@ -119,18 +118,16 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
}
private ObjectModel getObjectModelDynamic(Integer objectId, String version) {
String key = getIdVer(objectId, version);
String xmlB64 = lwM2mTransportContextServer.getTransportResourceCache().get(
this.tenantId,
LWM2M_MODEL,
key).
getValue();
return xmlB64 != null && !xmlB64.isEmpty() ?
lwM2mTransportContextServer.parseFromXmlToObjectModel(
Base64.getDecoder().decode(xmlB64),
key + ".xml",
new DefaultDDFFileValidator()) :
null;
String key = getKeyIdVer(objectId, version);
Optional<TbResource> tbResource = lwM2mTransportContextServer
.getTransportResourceCache()
.get(this.tenantId, LWM2M_MODEL, key);
return tbResource.map(resource -> lwM2mTransportContextServer.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()),
key + ".xml",
new DefaultDDFFileValidator())).orElse(null);
}
}
}

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

@ -17,9 +17,10 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -28,9 +29,14 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
@Slf4j
@Data
@ -67,21 +73,79 @@ public class LwM2mClient implements Cloneable {
this.init = false;
}
public void updateResourceValue(String pathRez, LwM2mResource rez) {
if (rez instanceof LwM2mMultipleResource) {
this.resources.put(pathRez, new ResourceValue(rez.getValues(), null, true));
} else if (rez instanceof LwM2mSingleResource) {
this.resources.put(pathRez, new ResourceValue(null, rez.getValue(), false));
public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) {
if (this.resources.get(pathRez) != null && this.resources.get(pathRez).getResourceModel() != null) {
this.resources.get(pathRez).setLwM2mResource(rez);
return true;
} else {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) {
this.resources.put(pathRez, new ResourceValue(rez, resourceModel));
return true;
} else {
return false;
}
}
}
public ResourceModel getResourceModel(String pathRez) {
if (this.getResources().get(pathRez) != null) {
return this.getResources().get(pathRez).getResourceModel();
} else {
return null;
}
}
public void initValue(LwM2mTransportServiceImpl lwM2MTransportService, String path) {
/**
*
* @param pathIdVer == "3_1.0"
* @param modelProvider -
*/
public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) {
Set key = getKeysEqualsIdVer(pathIdVer);
key.forEach(pathRez -> {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez.toString()));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) {
this.resources.get(pathRez).setResourceModel(resourceModel);
}
else {
this.resources.remove(pathRez);
}
});
}
/**
*
* @param idVer -
* @param modelProvider -
*/
public void updateResourceModel(String idVer, LwM2mModelProvider modelProvider) {
Set key = getKeysEqualsIdVer(idVer);
key.forEach(k -> this.saveResourceModel(k.toString(), modelProvider));
}
private void saveResourceModel(String pathRez, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
this.resources.get(pathRez).setResourceModel(resourceModel);
}
private Set getKeysEqualsIdVer(String idVer) {
return this.resources.keySet()
.stream()
.filter(e -> idVer.equals(e.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toSet());
}
public void initValue(LwM2mTransportServiceImpl serviceImpl, String path) {
if (path != null) {
this.pendingRequests.remove(path);
}
if (this.pendingRequests.size() == 0) {
this.init = true;
lwM2MTransportService.putDelayedUpdateResourcesThingsboard(this);
serviceImpl.putDelayedUpdateResourcesThingsboard(this);
}
}

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

@ -36,6 +36,7 @@ import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO
@Service
@TbLwM2mTransportComponent
public class LwM2mClientContextImpl implements LwM2mClientContext {
private static final boolean INFOS_ARE_COMPROMISED = false;
private final Map<String /** registrationId */, LwM2mClient> lwM2mClients = new ConcurrentHashMap<>();
@ -51,10 +52,10 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
}
public void delRemoveSessionAndListener(String registrationId) {
LwM2mClient lwM2MClient = lwM2mClients.get(registrationId);
LwM2mClient lwM2MClient = this.lwM2mClients.get(registrationId);
if (lwM2MClient != null) {
securityStore.remove(lwM2MClient.getEndpoint(), INFOS_ARE_COMPROMISED);
lwM2mClients.remove(registrationId);
this.securityStore.remove(lwM2MClient.getEndpoint(), INFOS_ARE_COMPROMISED);
this.lwM2mClients.remove(registrationId);
}
}

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java

@ -34,25 +34,25 @@ public class LwM2mClientProfile {
/**
* {"keyName": {
* "/3/0/1": "modelNumber",
* "/3/0/0": "manufacturer",
* "/3/0/2": "serialNumber"
* "/3_1.0/0/1": "modelNumber",
* "/3_1.0/0/0": "manufacturer",
* "/3_1.0/0/2": "serialNumber"
* }
**/
private JsonObject postKeyNameProfile;
/**
* [ "/2/0/0", "/2/0/1"]
* [ "/3_1.0/0/0", "/3_1.0/0/1"]
*/
private JsonArray postAttributeProfile;
/**
* [ "/2/0/0", "/2/0/1"]
* [ "/3_1.0/0/0", "/3_1.0/0/2"]
*/
private JsonArray postTelemetryProfile;
/**
* [ "/2/0/0", "/2/0/1"]
* [ "/3_1.0/0", "/3_1.0/0/1, "/3_1.0/0/2"]
*/
private JsonArray postObserveProfile;

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

@ -16,23 +16,16 @@
package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data;
import java.util.Map;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mResource;
@Data
public class ResourceValue {
Map<Integer, ?> values;
Object value;
boolean multiInstances;
public ResourceValue(Map<Integer, ?> values, Object value, boolean multiInstances) {
this.values = values;
this.value = value;
this.multiInstances = multiInstances;
}
private LwM2mResource lwM2mResource;
private ResourceModel resourceModel;
public Object getResourceValue() {
return this.multiInstances ? this.values : this.value;
public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) {
this.lwM2mResource = lwM2mResource;
this.resourceModel = resourceModel;
}
}

251
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/LwM2mInMemorySecurityStore.java

@ -1,251 +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.server.store;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.InMemorySecurityStore;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.server.security.SecurityStoreListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
@Slf4j
//@Service("LwM2mInMemorySecurityStore")
//@TbLwM2mTransportComponent
@Deprecated
public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
private static final boolean INFOS_ARE_COMPROMISED = false;
// lock for the two maps
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private final Map<String /** registrationId */, LwM2mClient> sessions = new ConcurrentHashMap<>();
private Map<UUID /** profileUUid */, LwM2mClientProfile> profiles = new ConcurrentHashMap<>();
private SecurityStoreListener listener;
@Autowired
LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator;
/**
* Start after DefaultAuthorizer or LwM2mPskStore
* @param endPoint -
* @return SecurityInfo
*/
@Override
public SecurityInfo getByEndpoint(String endPoint) {
readLock.lock();
try {
String registrationId = this.getRegistrationId(endPoint, null);
return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ?
sessions.get(registrationId).getSecurityInfo() : this.addLwM2MClientToSession(endPoint);
} finally {
readLock.unlock();
}
}
/**
* Start after LwM2mPskStore
* @param identity -
* @return SecurityInfo
*/
@Override
public SecurityInfo getByIdentity(String identity) {
readLock.lock();
try {
String integrationId = this.getRegistrationId(null, identity);
return (integrationId != null) ? sessions.get(integrationId).getSecurityInfo() : this.addLwM2MClientToSession(identity);
} finally {
readLock.unlock();
}
}
@Override
public Collection<SecurityInfo> getAll() {
readLock.lock();
try {
return this.sessions.values().stream().map(LwM2mClient::getSecurityInfo).collect(Collectors.toUnmodifiableList());
} finally {
readLock.unlock();
}
}
/**
* Removed registration Client from sessions and listener
* @param registrationId if Client
*/
public void delRemoveSessionAndListener(String registrationId) {
writeLock.lock();
try {
LwM2mClient lwM2MClient = (sessions.get(registrationId) != null) ? sessions.get(registrationId) : null;
if (lwM2MClient != null) {
if (listener != null) {
listener.securityInfoRemoved(INFOS_ARE_COMPROMISED, lwM2MClient.getSecurityInfo());
}
sessions.remove(registrationId);
}
} finally {
writeLock.unlock();
}
}
@Override
public void setListener(SecurityStoreListener listener) {
this.listener = listener;
}
public LwM2mClient getLwM2MClient(String endPoint, String identity) {
Map.Entry<String, LwM2mClient> modelClients = endPoint != null ?
this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndpoint())).findAny().orElse(null) :
this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).findAny().orElse(null);
return modelClients != null ? modelClients.getValue() : null;
}
public LwM2mClient getLwM2MClientWithReg(Registration registration, String registrationId) {
return registrationId != null ?
this.sessions.get(registrationId) :
this.sessions.containsKey(registration.getId()) ?
this.sessions.get(registration.getId()) :
this.sessions.get(registration.getEndpoint());
}
public LwM2mClient getLwM2MClient(TransportProtos.SessionInfoProto sessionInfo) {
return this.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue();
}
/**
* Update in sessions (LwM2MClient for key registration_Id) after starting registration LwM2MClient in LwM2MTransportServiceImpl
* Remove from sessions LwM2MClient with key registration_Endpoint
* @param registration -
* @return LwM2MClient after adding it to session
*/
public LwM2mClient updateInSessionsLwM2MClient(Registration registration) {
writeLock.lock();
try {
if (this.sessions.get(registration.getEndpoint()) == null) {
this.addLwM2MClientToSession(registration.getEndpoint());
}
LwM2mClient lwM2MClient = this.sessions.get(registration.getEndpoint());
lwM2MClient.setRegistration(registration);
// lwM2MClient.getAttributes().putAll(registration.getAdditionalRegistrationAttributes());
this.sessions.remove(registration.getEndpoint());
this.sessions.put(registration.getId(), lwM2MClient);
return lwM2MClient;
} finally {
writeLock.unlock();
}
}
private String getRegistrationId(String endPoint, String identity) {
List<String> registrationIds = (endPoint != null) ?
this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndpoint())).map(Map.Entry::getKey).collect(Collectors.toList()) :
this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).map(Map.Entry::getKey).collect(Collectors.toList());
return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null;
}
public Registration getByRegistration(String registrationId) {
return this.sessions.get(registrationId).getRegistration();
}
/**
* Add new LwM2MClient to session
* @param identity-
* @return SecurityInfo. If error - SecurityInfoError
* and log:
* - FORBIDDEN - if there is no authorization
* - profileUuid - if the device does not have a profile
* - device - if the thingsboard does not have a device with a name equal to the identity
*/
private SecurityInfo addLwM2MClientToSession(String identity) {
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.CLIENT);
if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null;
if (store.getSecurityInfo() != null && profileUuid != null) {
String endpoint = store.getSecurityInfo().getEndpoint();
sessions.put(endpoint, new LwM2mClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid, UUID.randomUUID()));
} else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) {
sessions.put(identity, new LwM2mClient(identity, null, null, store.getMsg(), profileUuid, UUID.randomUUID()));
} else {
log.error("Registration failed: FORBIDDEN/profileUuid/device [{}] , endpointId: [{}]", profileUuid, identity);
/**
* Return Error securityInfo
*/
byte[] preSharedKey = Hex.decodeHex("0A0B".toCharArray());
SecurityInfo infoError = SecurityInfo.newPreSharedKeyInfo("error", "error_identity", preSharedKey);
return infoError;
}
}
return store.getSecurityInfo();
}
public Map<String, LwM2mClient> getSession(UUID sessionUuId) {
return this.sessions.entrySet().stream()
.filter(e -> e.getValue().getSessionId().equals(sessionUuId))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
public Map<String, LwM2mClient> getSessions() {
return this.sessions;
}
public Map<UUID, LwM2mClientProfile> getProfiles() {
return this.profiles;
}
public LwM2mClientProfile getProfile(UUID profileUuId) {
return this.profiles.get(profileUuId);
}
public LwM2mClientProfile getProfile(String registrationId) {
UUID profileUUid = this.getSessions().get(registrationId).getProfileId();
return this.getProfiles().get(profileUUid);
}
public Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles) {
return this.profiles = profiles;
}
public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandler.getLwM2MClientProfileFromThingsboard(deviceProfile);
if (lwM2MClientProfile != null) {
profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile);
return true;
}
return false;
}
}

11
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreConfiguration.java

@ -29,6 +29,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
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;
@ -89,10 +90,14 @@ public class TbLwM2mStoreConfiguration {
}
@Override
public SecurityInfo getByEndpoint(String endpoint) {
SecurityInfo securityInfo = securityStore.getByEndpoint(endpoint);
public SecurityInfo getByEndpoint(String endPoint) {
SecurityInfo securityInfo = securityStore.getByEndpoint(endPoint);
if (securityInfo == null) {
securityInfo = clientContext.addLwM2mClientToSession(endpoint).getSecurityInfo();
LwM2mClient lwM2mClient = clientContext.getLwM2MClient(endPoint, null);
if (lwM2mClient != null && lwM2mClient.getRegistration() != null && !lwM2mClient.getRegistration().getIdentity().isSecure()){
return null;
}
securityInfo = clientContext.addLwM2mClientToSession(endPoint).getSecurityInfo();
try {
if (securityInfo != null) {
add(securityInfo);

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

@ -145,6 +145,8 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
}
DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
return formatter.format(new Date(timeValue));
case OPAQUE:
return Hex.encodeHexString((byte[])value);
default:
break;
}
@ -155,7 +157,7 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
log.debug("Trying to convert hexadecimal string [{}] to byte array", value);
// TODO check if we shouldn't instead assume that the string contains Base64 encoded data
try {
return Hex.decodeHex(((String) value).toCharArray());
return Hex.decodeHex(((String)value).toCharArray());
} catch (IllegalArgumentException e) {
throw new CodecException("Unable to convert hexastring [%s] to byte array for resource %s", value,
resourcePath);

5
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java

@ -30,7 +30,7 @@ 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.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.util.SslUtil;
import org.thingsboard.server.common.transport.util.SslUtil;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
@ -56,7 +55,7 @@ import java.util.concurrent.TimeUnit;
*/
@Slf4j
@Component("MqttSslHandlerProvider")
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.mqtt.enabled}'=='true')")
@ConditionalOnExpression("'${transport.mqtt.enabled}'=='true'")
@ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
public class MqttSslHandlerProvider {

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

@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
import org.thingsboard.server.transport.mqtt.util.SslUtil;
import org.thingsboard.server.common.transport.util.SslUtil;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.security.cert.Certificate;

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

@ -146,7 +146,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), rpcRequest.toByteArray()));
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest)));
}
@Override

13
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java

@ -18,11 +18,11 @@ package org.thingsboard.server.common.transport;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import java.util.Optional;
@ -44,9 +44,12 @@ public interface SessionMsgListener {
default void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto toTransportUpdateCredentials){}
default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {
}
default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {}
default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device,
Optional<DeviceProfile> deviceProfileOpt) {}
default void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {}
default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
}
default void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceUpdateMsgOpt) {}
}

6
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportResourceCache.java

@ -15,13 +15,15 @@
*/
package org.thingsboard.server.common.transport;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Optional;
public interface TransportResourceCache {
Resource get(TenantId tenantId, ResourceType resourceType, String resourceId);
Optional<TbResource> get(TenantId tenantId, ResourceType resourceType, String resourceId);
void update(TenantId tenantId, ResourceType resourceType, String resourceI);

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

@ -22,7 +22,6 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.MalformedJsonException;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
@ -161,15 +160,7 @@ public class JsonConverter {
result.addProperty("id", msg.getRequestId());
}
result.addProperty("method", msg.getMethodName());
try {
result.add("params", JSON_PARSER.parse(msg.getParams()));
} catch (JsonSyntaxException ex) {
if (ex.getCause() instanceof MalformedJsonException) {
result.addProperty("params", msg.getParams());
} else {
throw ex;
}
}
result.add("params", JSON_PARSER.parse(msg.getParams()));
return result;
}

25
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java

@ -15,7 +15,9 @@
*/
package org.thingsboard.server.common.transport.adaptor;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
@ -167,4 +169,27 @@ public class ProtoConverter {
});
return kvList;
}
public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
TransportProtos.ToDeviceRpcRequestMsg.Builder toDeviceRpcRequestMsgBuilder = toDeviceRpcRequestMsg.newBuilderForType();
toDeviceRpcRequestMsgBuilder.mergeFrom(toDeviceRpcRequestMsg);
toDeviceRpcRequestMsgBuilder.setParams(parseParams(toDeviceRpcRequestMsg));
TransportProtos.ToDeviceRpcRequestMsg result = toDeviceRpcRequestMsgBuilder.build();
return result.toByteArray();
}
private static String parseParams(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
String params = toDeviceRpcRequestMsg.getParams();
JsonElement jsonElementParams = JSON_PARSER.parse(params);
if (!jsonElementParams.isJsonPrimitive()) {
return params;
} else {
JsonPrimitive primitiveParams = jsonElementParams.getAsJsonPrimitive();
if (jsonElementParams.getAsJsonPrimitive().isString()) {
return primitiveParams.getAsString();
} else {
return params;
}
}
}
}

10
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java

@ -25,7 +25,15 @@ import java.util.UUID;
public class SessionInfoCreator {
public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) {
return TransportProtos.SessionInfoProto.newBuilder().setNodeId(context.getNodeId())
return getSessionInfoProto(msg, context.getNodeId(), sessionId);
}
public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
return getSessionInfoProto(msg, nodeId, sessionId);
}
private static TransportProtos.SessionInfoProto getSessionInfoProto(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
return TransportProtos.SessionInfoProto.newBuilder().setNodeId(nodeId)
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())

39
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java

@ -18,11 +18,7 @@ package org.thingsboard.server.common.transport.lwm2m;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
@ -38,16 +34,12 @@ import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportConfigServer {
@Getter
private String MODEL_PATH_DEFAULT = "models";
@Getter
private String KEY_STORE_DEFAULT_RESOURCE_PATH = "credentials";
@ -79,10 +71,6 @@ public class LwM2MTransportConfigServer {
// private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$";
private String PATH_DATA = "data";
@Getter
@Setter
private List<ObjectModel> modelsValueCommon;
@Getter
@Setter
private LwM2mModelProvider modelProvider;
@ -95,10 +83,6 @@ public class LwM2MTransportConfigServer {
@Value("${transport.sessions.report_timeout}")
private long sessionReportTimeout;
@Getter
@Value("${transport.lwm2m.model_path_file:}")
private String modelPathFile;
@Getter
@Value("${transport.lwm2m.recommended_ciphers:}")
private boolean recommendedCiphers;
@ -108,12 +92,8 @@ public class LwM2MTransportConfigServer {
private boolean recommendedSupportedGroups;
@Getter
@Value("${transport.lwm2m.request_pool_size:}")
private int requestPoolSize;
@Getter
@Value("${transport.lwm2m.request_error_pool_size:}")
private int requestErrorPoolSize;
@Value("${transport.lwm2m.response_pool_size:}")
private int responsePoolSize;
@Getter
@Value("${transport.lwm2m.registered_pool_size:}")
@ -232,19 +212,4 @@ public class LwM2MTransportConfigServer {
}
return FULL_FILE_PATH.toUri().getPath();
}
public ResourceModel getResourceModel(Registration registration, LwM2mPath pathIds) {
return this.modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
}
public ResourceModel.Type getResourceModelType(Registration registration, LwM2mPath pathIds) {
ResourceModel resource = this.getResourceModel(registration, pathIds);
return (resource == null) ? null : resource.type;
}
public ResourceModel.Operations getOperation(Registration registration, LwM2mPath pathIds) {
ResourceModel resource = this.getResourceModel(registration, pathIds);
return (resource == null) ? ResourceModel.Operations.NONE : resource.operations;
}
}

68
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java

@ -19,8 +19,8 @@ import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.TransportResourceCache;
import org.thingsboard.server.common.transport.TransportService;
@ -42,8 +42,8 @@ import java.util.concurrent.locks.ReentrantLock;
public class DefaultTransportResourceCache implements TransportResourceCache {
private final Lock resourceFetchLock = new ReentrantLock();
private final ConcurrentMap<ResourceKey, Resource> resources = new ConcurrentHashMap<>();
private final Set<ResourceKey> keys = ConcurrentHashMap.newKeySet();
private final ConcurrentMap<ResourceCompositeKey, TbResource> resources = new ConcurrentHashMap<>();
private final Set<ResourceCompositeKey> keys = ConcurrentHashMap.newKeySet();
private final DataDecodingEncodingService dataDecodingEncodingService;
private final TransportService transportService;
@ -53,49 +53,49 @@ public class DefaultTransportResourceCache implements TransportResourceCache {
}
@Override
public Resource get(TenantId tenantId, ResourceType resourceType, String resourceId) {
ResourceKey resourceKey = new ResourceKey(tenantId, resourceType, resourceId);
Resource resource;
public Optional<TbResource> get(TenantId tenantId, ResourceType resourceType, String resourceKey) {
ResourceCompositeKey compositeKey = new ResourceCompositeKey(tenantId, resourceType, resourceKey);
TbResource resource;
if (keys.contains(resourceKey)) {
resource = resources.get(resourceKey);
if (keys.contains(compositeKey)) {
resource = resources.get(compositeKey);
if (resource == null) {
resource = resources.get(resourceKey.getSystemKey());
resource = resources.get(compositeKey.getSystemKey());
}
} else {
resourceFetchLock.lock();
try {
if (keys.contains(resourceKey)) {
resource = resources.get(resourceKey);
if (keys.contains(compositeKey)) {
resource = resources.get(compositeKey);
if (resource == null) {
resource = resources.get(resourceKey.getSystemKey());
resource = resources.get(compositeKey.getSystemKey());
}
} else {
resource = fetchResource(resourceKey);
keys.add(resourceKey);
resource = fetchResource(compositeKey);
keys.add(compositeKey);
}
} finally {
resourceFetchLock.unlock();
}
}
return resource;
return Optional.ofNullable(resource);
}
private Resource fetchResource(ResourceKey resourceKey) {
UUID tenantId = resourceKey.getTenantId().getId();
private TbResource fetchResource(ResourceCompositeKey compositeKey) {
UUID tenantId = compositeKey.getTenantId().getId();
TransportProtos.GetResourceRequestMsg.Builder builder = TransportProtos.GetResourceRequestMsg.newBuilder();
builder
.setTenantIdLSB(tenantId.getLeastSignificantBits())
.setTenantIdMSB(tenantId.getMostSignificantBits())
.setResourceType(resourceKey.resourceType.name())
.setResourceId(resourceKey.resourceId);
.setResourceType(compositeKey.resourceType.name())
.setResourceKey(compositeKey.resourceKey);
TransportProtos.GetResourceResponseMsg responseMsg = transportService.getResource(builder.build());
Optional<Resource> optionalResource = dataDecodingEncodingService.decode(responseMsg.getResource().toByteArray());
Optional<TbResource> optionalResource = dataDecodingEncodingService.decode(responseMsg.getResource().toByteArray());
if (optionalResource.isPresent()) {
Resource resource = optionalResource.get();
resources.put(new ResourceKey(resource.getTenantId(), resource.getResourceType(), resource.getResourceId()), resource);
TbResource resource = optionalResource.get();
resources.put(new ResourceCompositeKey(resource.getTenantId(), resource.getResourceType(), resource.getResourceKey()), resource);
return resource;
}
@ -103,28 +103,28 @@ public class DefaultTransportResourceCache implements TransportResourceCache {
}
@Override
public void update(TenantId tenantId, ResourceType resourceType, String resourceId) {
ResourceKey resourceKey = new ResourceKey(tenantId, resourceType, resourceId);
if (keys.contains(resourceKey) || resources.containsKey(resourceKey)) {
fetchResource(resourceKey);
public void update(TenantId tenantId, ResourceType resourceType, String resourceKey) {
ResourceCompositeKey compositeKey = new ResourceCompositeKey(tenantId, resourceType, resourceKey);
if (keys.contains(compositeKey) || resources.containsKey(compositeKey)) {
fetchResource(compositeKey);
}
}
@Override
public void evict(TenantId tenantId, ResourceType resourceType, String resourceId) {
ResourceKey resourceKey = new ResourceKey(tenantId, resourceType, resourceId);
keys.remove(resourceKey);
resources.remove(resourceKey);
public void evict(TenantId tenantId, ResourceType resourceType, String resourceKey) {
ResourceCompositeKey compositeKey = new ResourceCompositeKey(tenantId, resourceType, resourceKey);
keys.remove(compositeKey);
resources.remove(compositeKey);
}
@Data
private static class ResourceKey {
private static class ResourceCompositeKey {
private final TenantId tenantId;
private final ResourceType resourceType;
private final String resourceId;
private final String resourceKey;
public ResourceKey getSystemKey() {
return new ResourceKey(TenantId.SYS_TENANT_ID, resourceType, resourceId);
public ResourceCompositeKey getSystemKey() {
return new ResourceCompositeKey(TenantId.SYS_TENANT_ID, resourceType, resourceKey);
}
}
}

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

@ -288,7 +288,7 @@ public class DefaultTransportService implements TransportService {
}
@Override
public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback<TransportProtos.ValidateDeviceCredentialsResponseMsg> callback) {
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),
@ -708,14 +708,23 @@ public class DefaultTransportService implements TransportService {
TransportProtos.ResourceUpdateMsg msg = toSessionMsg.getResourceUpdateMsg();
TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
ResourceType resourceType = ResourceType.valueOf(msg.getResourceType());
String resourceId = msg.getResourceId();
String resourceId = msg.getResourceKey();
transportResourceCache.update(tenantId, resourceType, resourceId);
sessions.forEach((id, mdRez) -> {
log.warn("ResourceUpdate - [{}] [{}]", id, mdRez);
transportCallbackExecutor.submit(() -> mdRez.getListener().onResourceUpdate(Optional.ofNullable(msg)));
});
} else if (toSessionMsg.hasResourceDeleteMsg()) {
TransportProtos.ResourceDeleteMsg msg = toSessionMsg.getResourceDeleteMsg();
TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
ResourceType resourceType = ResourceType.valueOf(msg.getResourceType());
String resourceId = msg.getResourceId();
String resourceId = msg.getResourceKey();
transportResourceCache.evict(tenantId, resourceType, resourceId);
sessions.forEach((id, mdRez) -> {
log.warn("ResourceDelete - [{}] [{}]", id, mdRez);
transportCallbackExecutor.submit(() -> mdRez.getListener().onResourceDelete(Optional.ofNullable(msg)));
});
} else {
//TODO: should we notify the device actor about missed session?
log.debug("[{}] Missing session.", sessionId);
@ -827,7 +836,7 @@ public class DefaultTransportService implements TransportService {
}
private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json,
TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) {
TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) {
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId);
RuleChainId ruleChainId;

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

Loading…
Cancel
Save