Browse Source

Merge pull request #3840 from thingsboard/lwm2m_tests

[3.3] Lwm2m tests
pull/4012/head
Andrew Shvayka 6 years ago
committed by GitHub
parent
commit
b9d3904171
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  2. 68
      application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java
  3. 124
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java
  4. 14
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  5. 52
      application/src/main/resources/thingsboard.yml
  6. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  7. 12
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  8. 2
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java
  9. 1
      common/queue/src/main/proto/queue.proto
  10. 23
      common/transport/lwm2m/pom.xml
  11. 225
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java
  12. 43
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java
  13. 84
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  14. 205
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java
  15. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  16. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java
  17. 49
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  18. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java
  19. 88
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportContextServer.java
  20. 207
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java
  21. 203
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java
  22. 280
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java
  23. 73
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java
  24. 969
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java
  25. 1159
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java
  26. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  27. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ResultIds.java
  28. 39
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java
  29. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java
  30. 70
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java
  31. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java
  32. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java
  33. 234
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java
  34. 136
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java
  35. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java
  36. BIN
      common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks
  37. 309
      common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
  38. 19
      common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties
  39. 208
      common/transport/lwm2m/src/main/resources/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml
  40. 147
      common/transport/lwm2m/src/main/resources/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml
  41. 5
      common/transport/transport-api/pom.xml
  42. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java
  43. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  44. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  45. 53
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java
  46. 134
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java
  47. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  48. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java
  49. 9
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  50. 47
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  51. 7
      netty-mqtt/pom.xml
  52. 28
      pom.xml
  53. 19
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  54. 60
      transport/lwm2m/pom.xml
  55. BIN
      transport/lwm2m/src/main/data/credentials/serverKeyStore.jks
  56. 403
      transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh
  57. 57
      transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties
  58. 167
      transport/lwm2m/src/main/data/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml
  59. 164
      transport/lwm2m/src/main/data/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml
  60. 51
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  61. 5
      ui-ngx/angular.json
  62. 35
      ui-ngx/src/app/core/http/device-profile.service.ts
  63. 3
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.html
  64. 37
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts
  65. 1
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  66. 66
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html
  67. 129
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts
  68. 250
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html
  69. 518
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts
  70. 79
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.html
  71. 22
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.scss
  72. 159
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts
  73. 17
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html
  74. 14
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts
  75. 6
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html
  76. 191
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts
  77. 13
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html
  78. 72
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts
  79. 41
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html
  80. 253
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts
  81. 155
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts
  82. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html
  83. 3
      ui-ngx/src/app/modules/home/pages/device/device.module.ts
  84. 57
      ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html
  85. 74
      ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts
  86. 212
      ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html
  87. 213
      ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.ts
  88. 55
      ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.models.ts
  89. 6
      ui-ngx/src/app/shared/components/json-object-edit.component.ts
  90. 2
      ui-ngx/src/app/shared/models/device.models.ts
  91. 11
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -278,7 +278,6 @@ public class DeviceController extends BaseController {
try { try {
Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS);
DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials)); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials));
//log.info("0 LwM2M CredentialsUpdate start)
tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null); tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null);
logEntityAction(device.getId(), device, logEntityAction(device.getId(), device,
device.getCustomerId(), device.getCustomerId(),

68
application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java

@ -15,17 +15,32 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; 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;
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.exception.ThingsboardException;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List; import java.util.List;
import java.util.Map;
@Slf4j @Slf4j
@RestController @RestController
@ -33,13 +48,16 @@ import java.util.List;
@RequestMapping("/api") @RequestMapping("/api")
public class DeviceLwm2mController extends BaseController { public class DeviceLwm2mController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/{objectIds}", method = RequestMethod.GET) @RequestMapping(value = "/lwm2m/deviceProfile", params = {"sortOrder", "sortProperty"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<LwM2mObject> getLwm2mListObjects(@PathVariable("objectIds") int[] objectIds) throws ThingsboardException { public List<LwM2mObject> getLwm2mListObjects(@RequestParam String sortOrder,
@RequestParam String sortProperty,
@RequestParam(required = false) int[] objectIds,
@RequestParam(required = false) String searchText)
throws ThingsboardException {
try { try {
return lwM2MModelsRepository.getLwm2mObjects(objectIds, null); return lwM2MModelsRepository.getLwm2mObjects(objectIds, searchText, sortProperty, sortOrder);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -50,11 +68,11 @@ public class DeviceLwm2mController extends BaseController {
@ResponseBody @ResponseBody
public PageData<LwM2mObject> getLwm2mListObjects(@RequestParam int pageSize, public PageData<LwM2mObject> getLwm2mListObjects(@RequestParam int pageSize,
@RequestParam int page, @RequestParam int page,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String searchText,
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, searchText, sortProperty, sortOrder);
return checkNotNull(lwM2MModelsRepository.findDeviceLwm2mObjects(getTenantId(), pageLink)); return checkNotNull(lwM2MModelsRepository.findDeviceLwm2mObjects(getTenantId(), pageLink));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -72,4 +90,40 @@ public class DeviceLwm2mController extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/device-credentials", method = RequestMethod.POST)
@ResponseBody
public Device saveDeviceWithCredentials(@RequestBody (required=false) Map<Class<?>, Object> deviceWithDeviceCredentials) throws ThingsboardException {
ObjectMapper mapper = new ObjectMapper();
Device device = checkNotNull(mapper.convertValue(deviceWithDeviceCredentials.get(Device.class), Device.class));
DeviceCredentials credentials = checkNotNull(mapper.convertValue( deviceWithDeviceCredentials.get(DeviceCredentials.class), DeviceCredentials.class));
try {
device.setTenantId(getCurrentUser().getTenantId());
checkEntity(device.getId(), device, Resource.DEVICE);
Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials);
checkNotNull(savedDevice);
tbClusterService.onDeviceChange(savedDevice, null);
tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),
device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedDevice.getId(), savedDevice,
savedDevice.getCustomerId(),
device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
if (device.getId() == null) {
deviceStateService.onDeviceAdded(savedDevice);
} else {
deviceStateService.onDeviceUpdated(savedDevice);
}
return savedDevice;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), device,
null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
} }

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

@ -23,8 +23,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.lwm2m.*;
import org.thingsboard.server.common.data.id.TenantId; 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
@ -33,17 +36,24 @@ import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.*; import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec; import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec; import java.security.spec.ECParameterSpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.ECPoint; import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec; import java.security.spec.KeySpec;
import java.util.List;
import java.util.ArrayList; 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.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -70,22 +80,32 @@ public class LwM2MModelsRepository {
* Filter by Predicate (uses objectIds, if objectIds is null then it uses textSearch, * Filter by Predicate (uses objectIds, if objectIds is null then it uses textSearch,
* if textSearch is null then it uses AllList from List<ObjectModel>) * if textSearch is null then it uses AllList from List<ObjectModel>)
*/ */
public List<LwM2mObject> getLwm2mObjects(int[] objectIds, String textSearch) { public List<LwM2mObject> getLwm2mObjects(int[] objectIds, String textSearch, String sortProperty, String sortOrder) {
return getLwm2mObjects((objectIds != null && objectIds.length > 0) ? if (objectIds == null && textSearch != null && !textSearch.isEmpty()) {
(ObjectModel element) -> IntStream.of(objectIds).anyMatch(x -> x == element.id) : objectIds = getObjectIdFromTextSearch(textSearch);
(textSearch != null && !textSearch.isEmpty()) ? (ObjectModel element) -> element.name.contains(textSearch) : null); }
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 * @param predicate
* @return list of LwM2mObject * @return list of LwM2mObject
*/ */
private List<LwM2mObject> getLwm2mObjects(Predicate<? super ObjectModel> predicate) { private List<LwM2mObject> getLwm2mObjects(Predicate<? super ObjectModel> predicate, String sortProperty, String sortOrder) {
List<LwM2mObject> lwM2mObjects = new ArrayList<>(); List<LwM2mObject> lwM2mObjects = new ArrayList<>();
List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValue() : List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValue() :
contextServer.getModelsValue().stream() contextServer.getModelsValue().stream()
.filter(predicate) .filter(predicate)
.collect(Collectors.toList()); .collect(Collectors.toList());
listObjects.forEach(obj -> { listObjects.forEach(obj -> {
LwM2mObject lwM2mObject = new LwM2mObject(); LwM2mObject lwM2mObject = new LwM2mObject();
lwM2mObject.setId(obj.id); lwM2mObject.setId(obj.id);
@ -105,6 +125,29 @@ public class LwM2MModelsRepository {
lwM2mObject.setInstances(new LwM2mInstance[]{instance}); lwM2mObject.setInstances(new LwM2mInstance[]{instance});
lwM2mObjects.add(lwM2mObject); 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; return lwM2mObjects;
} }
@ -126,13 +169,32 @@ public class LwM2MModelsRepository {
* PageNumber = 1, PageSize = List<LwM2mObject>.size() * PageNumber = 1, PageSize = List<LwM2mObject>.size()
*/ */
public PageData<LwM2mObject> findLwm2mListObjects(PageLink pageLink) { public PageData<LwM2mObject> findLwm2mListObjects(PageLink pageLink) {
PageImpl page = new PageImpl(getLwm2mObjects(null, pageLink.getTextSearch())); PageImpl page = new PageImpl(getLwm2mObjects(getObjectIdFromTextSearch(pageLink.getTextSearch()),
pageLink.getTextSearch(),
pageLink.getSortOrder().getProperty(),
pageLink.getSortOrder().getDirection().name()));
PageData pageData = new PageData(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); PageData pageData = new PageData(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext());
return pageData; 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 securityMode
* @param bootstrapServerIs * @param bootstrapServerIs
* @return ServerSecurityConfig more value is default: Important - port, host, publicKey * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
@ -143,60 +205,60 @@ public class LwM2MModelsRepository {
} }
/** /**
*
* @param bootstrapServerIs * @param bootstrapServerIs
* @param mode * @param mode
* @return ServerSecurityConfig more value is default: Important - port, host, publicKey * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
*/ */
private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) { private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) {
ServerSecurityConfig bsServ = new ServerSecurityConfig(); ServerSecurityConfig bsServ = new ServerSecurityConfig();
bsServ.setBootstrapServerIs(bootstrapServerIs);
if (bootstrapServerIs) { if (bootstrapServerIs) {
bsServ.setServerId(contextBootStrap.getBootstrapServerId());
switch (mode) { switch (mode) {
case NO_SEC: case NO_SEC:
bsServ.setHost(contextBootStrap.getBootstrapHost()); bsServ.setHost(contextBootStrap.getBootstrapHost());
bsServ.setPort(contextBootStrap.getBootstrapPort()); bsServ.setPort(contextBootStrap.getBootstrapPortNoSecPsk());
bsServ.setServerPublicKey(""); bsServ.setServerPublicKey("");
break; break;
case PSK: case PSK:
bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); bsServ.setHost(contextBootStrap.getBootstrapSecureHost());
bsServ.setPort(contextBootStrap.getBootstrapSecurePort()); bsServ.setPort(contextBootStrap.getBootstrapSecurePortPsk());
bsServ.setServerPublicKey(""); bsServ.setServerPublicKey("");
break; break;
case RPK: case RPK:
bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); bsServ.setHost(contextBootStrap.getBootstrapSecureHost());
bsServ.setPort(contextBootStrap.getBootstrapSecurePort()); bsServ.setPort(contextBootStrap.getBootstrapSecurePortRpk());
bsServ.setServerPublicKey(getRPKPublicKey(this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY())); bsServ.setServerPublicKey(getRPKPublicKey(this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY()));
break; break;
case X509: case X509:
bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); bsServ.setHost(contextBootStrap.getBootstrapSecureHost());
bsServ.setPort(contextBootStrap.getBootstrapSecurePortCert()); bsServ.setPort(contextBootStrap.getBootstrapSecurePortX509());
bsServ.setServerPublicKey(getServerPublicKeyX509(contextBootStrap.getBootstrapAlias())); bsServ.setServerPublicKey(getServerPublicKeyX509(contextBootStrap.getBootstrapAlias()));
break; break;
default: default:
break; break;
} }
} else { } else {
bsServ.setBootstrapServerIs(bootstrapServerIs); bsServ.setServerId(contextServer.getServerId());
bsServ.setServerId(123);
switch (mode) { switch (mode) {
case NO_SEC: case NO_SEC:
bsServ.setHost(contextServer.getServerHost()); bsServ.setHost(contextServer.getServerHost());
bsServ.setPort(contextServer.getServerPort()); bsServ.setPort(contextServer.getServerPortNoSecPsk());
bsServ.setServerPublicKey(""); bsServ.setServerPublicKey("");
break; break;
case PSK: case PSK:
bsServ.setHost(contextServer.getServerSecureHost()); bsServ.setHost(contextServer.getServerSecureHost());
bsServ.setPort(contextServer.getServerSecurePort()); bsServ.setPort(contextServer.getServerPortPsk());
bsServ.setServerPublicKey(""); bsServ.setServerPublicKey("");
break; break;
case RPK: case RPK:
bsServ.setHost(contextServer.getServerSecureHost()); bsServ.setHost(contextServer.getServerSecureHost());
bsServ.setPort(contextServer.getServerSecurePort()); bsServ.setPort(contextServer.getServerPortRpk());
bsServ.setServerPublicKey(getRPKPublicKey(this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY())); bsServ.setServerPublicKey(getRPKPublicKey(this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY()));
break; break;
case X509: case X509:
bsServ.setHost(contextServer.getServerSecureHost()); bsServ.setHost(contextServer.getServerSecureHost());
bsServ.setPort(contextServer.getServerSecurePortCert()); bsServ.setPort(contextServer.getServerPortX509());
bsServ.setServerPublicKey(getServerPublicKeyX509(contextServer.getServerAlias())); bsServ.setServerPublicKey(getServerPublicKeyX509(contextServer.getServerAlias()));
break; break;
default: default:
@ -207,25 +269,23 @@ public class LwM2MModelsRepository {
} }
/** /**
*
* @param alias * @param alias
* @return PublicKey format HexString or null * @return PublicKey format HexString or null
*/ */
private String getServerPublicKeyX509 (String alias) { private String getServerPublicKeyX509(String alias) {
try { try {
X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias); X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias);
return Hex.encodeHexString(serverCertificate.getEncoded()); return Hex.encodeHexString(serverCertificate.getEncoded());
} catch (CertificateEncodingException | KeyStoreException e) { } catch (CertificateEncodingException | KeyStoreException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
/** /**
*
* @param publicServerX * @param publicServerX
* @param publicServerY * @param publicServerY
* @return PublicKey format HexString or null * @return PublicKey format HexString or null
*/ */
private String getRPKPublicKey(String publicServerX, String publicServerY) { private String getRPKPublicKey(String publicServerX, String publicServerY) {
try { try {
@ -241,9 +301,9 @@ public class LwM2MModelsRepository {
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec); parameterSpec);
/** Get keys */ /** Get keys */
PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
if (publicKey != null && publicKey.getEncoded().length > 0 ) { if (publicKey != null && publicKey.getEncoded().length > 0) {
return Hex.encodeHexString(publicKey.getEncoded()); return Hex.encodeHexString(publicKey.getEncoded());
} }
} }
} catch (GeneralSecurityException | IllegalArgumentException e) { } catch (GeneralSecurityException | IllegalArgumentException e) {

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

@ -49,34 +49,31 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceProvisionService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@ -326,6 +323,7 @@ public class DefaultTransportApiService implements TransportApiService {
break; break;
case MQTT_BASIC: case MQTT_BASIC:
case X509_CERTIFICATE: case X509_CERTIFICATE:
case LWM2M_CREDENTIALS:
provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsValue()); provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsValue());
break; break;
} }

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

@ -575,7 +575,13 @@ transport:
timeout: "${LWM2M_TIMEOUT:120000}" 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:./common/transport/lwm2m/src/main/resources/models/}"
model_path_file: "${LWM2M_MODEL_PATH_FILE:}" model_path_file: "${LWM2M_MODEL_PATH_FILE:}"
support_deprecated_ciphers_enable: "${LWM2M_SUPPORT_DEPRECATED_CIPHERS_ENABLED:true}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:false}"
request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}"
request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}"
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}"
secure: secure:
# Only Certificate_x509: # Only Certificate_x509:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
@ -588,24 +594,19 @@ transport:
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}" enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}"
server: server:
id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT:5685}" bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_PSK:5685}"
bind_port_cert: "${LWM2M_BIND_PORT_CERT:5687}" bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_RPK:5687}"
bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_X509:5689}"
secure: secure:
start_all: "${START_SERVER_ALL:true}"
#leshan.core (V1_1)
#DTLS security modes:
#0: Pre-Shared Key mode
#1: Raw Public Key mode
#2: Certificate mode X509
#3: NoSec mode *
#OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A (add)
#4: Certificate mode X509 with EST
# If only startAll == false
dtls_mode: "${LWM2M_SECURITY_MODE:1}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_SEC:5686}" start_psk: "${START_SERVER_PSK:true}"
bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT:5688}" start_rpk: "${START_SERVER_RPK:true}"
start_x509: "${START_SERVER_X509:true}"
bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK:5686}"
bind_port_rpk: "${LWM2M_BIND_PORT_SEC_RPK:5688}"
bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509:5690}"
# Only RPK: Public & Private Key # Only RPK: Public & Private Key
# create_rpk: "${CREATE_RPK:}" # create_rpk: "${CREATE_RPK:}"
public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}"
@ -615,23 +616,26 @@ transport:
alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
bootstrap: bootstrap:
enable: "${BOOTSTRAP:true}" enable: "${BOOTSTRAP:true}"
id: "${LWM2M_SERVER_ID:111}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_BS:5689}" bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_BS:5691}"
bind_port_cert: "${LWM2M_BIND_PORT_SER_BS:5691}" bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_BS:5693}"
bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_BS:5695}"
secure: secure:
start_all: "${START_BOOTSTRAP_ALL:true}"
# If only startAll == false
dtls_mode: "${LWM2M_SECURITY_MODE_BS:1}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_SEC_BS:5690}" start_psk: "${START_SERVER_PSK_BS:true}"
bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT_BS:5692}" start_rpk: "${START_SERVER_RPK_BS:true}"
start_x509: "${START_SERVER_X509_BS:true}"
bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK_BS:5692}"
bind_port_rpk: "${LWM2M_BIND_PORT_SER_RPK_BS:5694}"
bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509_BS:5696}"
# Only RPK: Public & Private Key # Only RPK: Public & Private Key
public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}"
public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}"
private_s: "${LWM2M_SERVER_PRIVATE_S_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}" private_s: "${LWM2M_SERVER_PRIVATE_S_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}"
# Only Certificate_x509: # Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}" alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}"
# Redis # Redis
redis_url: "${LWM2M_REDIS_URL:''}" redis_url: "${LWM2M_REDIS_URL:''}"
swagger: swagger:

3
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import java.util.List; import java.util.List;
@ -45,6 +46,8 @@ public interface DeviceService {
Device saveDeviceWithAccessToken(Device device, String accessToken); Device saveDeviceWithAccessToken(Device device, String accessToken);
Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials);
Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId); Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId);
Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId); Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId);

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

@ -20,7 +20,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -64,6 +63,17 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.setDeviceData(device.getDeviceData()); this.setDeviceData(device.getDeviceData());
} }
public Device updateDevice(Device device) {
this.tenantId = device.getTenantId();
this.customerId = device.getCustomerId();
this.name = device.getName();
this.type = device.getType();
this.label = device.getLabel();
this.deviceProfileId = device.getDeviceProfileId();
this.setDeviceData(device.getDeviceData());
return this;
}
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java

@ -21,6 +21,4 @@ public enum DeviceCredentialsType {
X509_CERTIFICATE, X509_CERTIFICATE,
MQTT_BASIC, MQTT_BASIC,
LWM2M_CREDENTIALS LWM2M_CREDENTIALS
} }

1
common/queue/src/main/proto/queue.proto

@ -77,6 +77,7 @@ enum CredentialsType {
ACCESS_TOKEN = 0; ACCESS_TOKEN = 0;
X509_CERTIFICATE = 1; X509_CERTIFICATE = 1;
MQTT_BASIC = 2; MQTT_BASIC = 2;
LWM2M_CREDENTIALS = 3;
} }
message KeyValueProto { message KeyValueProto {

23
common/transport/lwm2m/pom.xml

@ -65,15 +65,10 @@
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</dependency> </dependency>
<!-- lechan start -->
<dependency> <dependency>
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-server-cf</artifactId> <artifactId>leshan-server-cf</artifactId>
</dependency> </dependency>
<!-- <dependency>-->
<!-- <groupId>org.eclipse.leshan</groupId>-->
<!-- <artifactId>leshan-server-cf</artifactId>-->
<!-- </dependency> -->
<dependency> <dependency>
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-client-cf</artifactId> <artifactId>leshan-client-cf</artifactId>
@ -83,12 +78,6 @@
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-server-redis</artifactId> <artifactId>leshan-server-redis</artifactId>
</dependency> </dependency>
<!-- <dependency>-->
<!-- <groupId>org.eclipse.californium</groupId>-->
<!-- <artifactId>californium-core</artifactId>-->
<!-- </dependency>-->
<!-- leshan finish -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
@ -107,14 +96,20 @@
<dependency> <dependency>
<groupId>org.eclipse.californium</groupId> <groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId> <artifactId>californium-core</artifactId>
<version>${californium.version}</version> <type>test-jar</type>
<type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.eclipse.californium</groupId>-->
<!-- <artifactId>scandium</artifactId>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>org.eclipse.californium</groupId> <groupId>org.eclipse.californium</groupId>
<artifactId>element-connector</artifactId> <artifactId>element-connector</artifactId>
<version>${californium.version}</version>
<type>test-jar</type> <type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>

225
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
@ -28,18 +29,42 @@ import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MSetSecurityStoreBootstrap;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
import java.util.Arrays;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')")
public class LwM2MTransportBootstrapServerConfiguration { public class LwM2MTransportBootstrapServerConfiguration {
private PublicKey publicKey;
private PrivateKey privateKey;
@Autowired @Autowired
private LwM2MTransportContextBootstrap contextBs; private LwM2MTransportContextBootstrap contextBs;
@ -53,27 +78,35 @@ public class LwM2MTransportBootstrapServerConfiguration {
@Autowired @Autowired
private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
@Primary @Primary
@Bean(name = "leshanBootstrapCert") @Bean(name = "leshanBootstrapX509")
public LeshanBootstrapServer getLeshanBootstrapServerCert() { @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_x509:false}'=='true')")
log.info("Prepare and start BootstrapServerCert... PostConstruct"); public LeshanBootstrapServer getLeshanBootstrapServerX509() {
return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortCert(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortCert(), X509); log.info("Prepare and start BootstrapServerX509... PostConstruct");
return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecX509(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortX509(), X509);
}
@Bean(name = "leshanBootstrapPsk")
@ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_psk:false}'=='true')")
public LeshanBootstrapServer getLeshanBootstrapServerPsk() {
log.info("Prepare and start BootstrapServerRsk... PostConstruct");
return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecPsk(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortPsk(), PSK);
} }
@Bean(name = "leshanBootstrapRPK") @Bean(name = "leshanBootstrapRpk")
public LeshanBootstrapServer getLeshanBootstrapServerRPK() { @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_rpk:false}'=='true')")
log.info("Prepare and start BootstrapServerRPK... PostConstruct"); public LeshanBootstrapServer getLeshanBootstrapServerRpk() {
return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPort(), this.contextBs.getCtxBootStrap().getBootstrapSecurePort(), RPK); log.info("Prepare and start BootstrapServerRpk... PostConstruct");
return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecRpk(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortRpk(), RPK);
} }
public LeshanBootstrapServer getLeshanBootstrapServer(Integer bootstrapPort, Integer bootstrapSecurePort, LwM2MSecurityMode dtlsMode) { public LeshanBootstrapServer getLeshanBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort, LwM2MSecurityMode dtlsMode) {
LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPort); builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPortNoSec);
builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapSecureHost(), bootstrapSecurePort); builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapSecureHost(), bootstrapSecurePort);
/** Create CoAP Config */ /** Create CoAP Config */
builder.setCoapConfig(getCoapConfig ()); builder.setCoapConfig(getCoapConfig (bootstrapPortNoSec, bootstrapSecurePort));
/** ConfigStore */ /** ConfigStore */
builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
@ -84,13 +117,22 @@ public class LwM2MTransportBootstrapServerConfiguration {
/** Define model provider (Create Models )*/ /** Define model provider (Create Models )*/
builder.setModel(new StaticModel(contextS.getCtxServer().getModelsValue())); builder.setModel(new StaticModel(contextS.getCtxServer().getModelsValue()));
/** Create credentials */
LwM2MSetSecurityStoreBootstrap(builder, dtlsMode);
/** Create and Set DTLS Config */ /** Create and Set DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedCipherSuitesOnly(contextS.getCtxServer().isSupportDeprecatedCiphersEnable()); if (dtlsMode==PSK) {
dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers());
dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getCtxServer().isRecommendedSupportedGroups());
dtlsConfig.setSupportedCipherSuites(TLS_PSK_WITH_AES_128_CBC_SHA256);
}
else {
dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers());
// dtlsConfig.setRecommendedSupportedGroupsOnly(false);
}
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
/** Create credentials */
new LwM2MSetSecurityStoreBootstrap(builder, contextBs, contextS, dtlsMode);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore);
builder.setSessionManager(sessionManager); builder.setSessionManager(sessionManager);
@ -98,4 +140,151 @@ public class LwM2MTransportBootstrapServerConfiguration {
/** Create BootstrapServer */ /** Create BootstrapServer */
return builder.build(); return builder.build();
} }
public void LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MSecurityMode dtlsMode) {
/** Set securityStore with new registrationStore */
switch (dtlsMode) {
/** Use No_Sec only */
case NO_SEC:
setServerWithX509Cert(builder, NO_SEC.code);
break;
/** Use PSK/RPK */
case PSK:
break;
case RPK:
setRPK(builder);
break;
case X509:
setServerWithX509Cert(builder, X509.code);
break;
/** Use X509_EST only */
case X509_EST:
// TODO support sentinel pool and make pool configurable
break;
/** Use ather X509, PSK, No_Sec ?? */
default:
break;
}
}
private void setRPK(LeshanBootstrapServerBuilder builder) {
try {
/** Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) {
/** Get point values */
byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray());
/** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
/** Get keys */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
}
if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) {
/** Get point values */
byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray());
/** Create key specs */
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
/** Get keys */
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
}
if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPublicKey(this.publicKey);
builder.setPrivateKey(this.privateKey);
this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey);
getParamsRPK();
}
} catch (GeneralSecurityException | IllegalArgumentException e) {
log.error("[{}] Failed generate Server PSK/RPK", e.getMessage());
throw new RuntimeException(e);
}
}
private void setServerWithX509Cert(LeshanBootstrapServerBuilder builder, int securityModeCode) {
try {
if (this.contextS.getCtxServer().getKeyStoreValue() != null) {
KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue();
setBuilderX509(builder);
X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias());
if (rootCAX509Cert != null && securityModeCode == X509.code) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates);
} else {
/** by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
}
else {
/** by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.error("Unable to load X509 files for BootStrapServer");
}
} catch (KeyStoreException ex) {
log.error("[{}] Unable to load X509 files server", ex.getMessage());
}
}
private void setBuilderX509(LeshanBootstrapServerBuilder builder) {
/**
* For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
* For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
*/
try {
X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias());
this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray());
if (this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPrivateKey(this.privateKey);
}
if (serverCertificate != null) {
builder.setCertificateChain(new X509Certificate[]{serverCertificate});
this.infoParamsX509(serverCertificate);
}
} catch (Exception ex) {
log.error("[{}] Unable to load KeyStore files server", ex.getMessage());
}
}
private void getParamsRPK() {
if (this.publicKey instanceof ECPublicKey) {
/** Get x coordinate */
byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);
/** Get Y coordinate */
byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);
/** Get Curves params */
String params = ((ECPublicKey) this.publicKey).getParams().toString();
log.info(
" \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]",
params, Hex.encodeHexString(x), Hex.encodeHexString(y),
Hex.encodeHexString(this.publicKey.getEncoded()),
Hex.encodeHexString(this.privateKey.getEncoded()));
} else {
throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported).");
}
}
private void infoParamsX509(X509Certificate certificate) {
try {
log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]",
Hex.encodeHexString(certificate.getEncoded()),
Hex.encodeHexString(this.privateKey.getEncoded()));
} catch (CertificateEncodingException e) {
log.error("", e);
}
}
} }

43
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java

@ -14,57 +14,56 @@
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.bootstrap; package org.thingsboard.server.transport.lwm2m.bootstrap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@Slf4j @Slf4j
@Service @Service
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')")
public class LwM2MTransportBootstrapServerInitializer { public class LwM2MTransportBootstrapServerInitializer {
@Autowired @Autowired(required = false)
@Qualifier("leshanBootstrapCert") @Qualifier("leshanBootstrapX509")
private LeshanBootstrapServer lhBServerCert; private LeshanBootstrapServer lhBServerCert;
@Autowired @Autowired(required = false)
@Qualifier("leshanBootstrapRPK") @Qualifier("leshanBootstrapPsk")
private LeshanBootstrapServer lhBServerRPK; private LeshanBootstrapServer lhBServerPsk;
@Autowired(required = false)
@Qualifier("leshanBootstrapRpk")
private LeshanBootstrapServer lhBServerRpk;
@Autowired @Autowired
private LwM2MTransportContextBootstrap contextBS; private LwM2MTransportContextBootstrap contextBS;
@PostConstruct @PostConstruct
public void init() { public void init() {
if (this.contextBS.getCtxBootStrap().isBootstrapStartAll()) { if (this.contextBS.getCtxBootStrap().getBootstrapStartPsk()) {
this.lhBServerCert.start(); this.lhBServerPsk.start();
this.lhBServerRPK.start(); }
if (this.contextBS.getCtxBootStrap().getBootstrapStartRpk()) {
this.lhBServerRpk.start();
} }
else { if (this.contextBS.getCtxBootStrap().getBootstrapStartX509()) {
if (this.contextBS.getCtxBootStrap().getBootStrapDtlsMode() == LwM2MSecurityMode.X509.code) { this.lhBServerCert.start();
this.lhBServerCert.start();
}
else {
this.lhBServerRPK.start();
}
} }
} }
@PreDestroy @PreDestroy
public void shutdown() throws InterruptedException { public void shutdown() throws InterruptedException {
log.info("Stopping LwM2M transport Bootstrap Server!"); log.info("Stopping LwM2M transport Bootstrap Server!");
try { lhBServerPsk.destroy();
lhBServerCert.destroy(); lhBServerRpk.destroy();
lhBServerRPK.destroy(); lhBServerCert.destroy();
} finally {
}
log.info("LwM2M transport Bootstrap Server stopped!"); log.info("LwM2M transport Bootstrap Server stopped!");
} }
} }

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

@ -29,28 +29,41 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore;
import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityInfo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; 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.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2MSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler;
import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.*; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.BOOTSTRAP_SERVER;
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.LWM2M_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.SERVERS;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getBootstrapParametersFromThingsboard;
@Slf4j @Slf4j
@Component("LwM2MBootstrapSecurityStore") @Service("LwM2MBootstrapSecurityStore")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true' && '${transport.lwm2m.bootstrap.enable}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true' && '${transport.lwm2m.bootstrap.enable}'=='true')")
public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private final EditableBootstrapConfigStore bootstrapConfigStore; private final EditableBootstrapConfigStore bootstrapConfigStore;
@Autowired @Autowired
LwM2MGetSecurityInfo lwM2MGetSecurityInfo; LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator;
@Autowired
public LwM2MTransportContextServer context;
public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore) { public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore) {
this.bootstrapConfigStore = bootstrapConfigStore; this.bootstrapConfigStore = bootstrapConfigStore;
@ -59,8 +72,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override @Override
public List<SecurityInfo> getAllByEndpoint(String endPoint) { public List<SecurityInfo> getAllByEndpoint(String endPoint) {
String endPointKey = endPoint; String endPointKey = endPoint;
ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(endPointKey, TypeServer.BOOTSTRAP); ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPointKey, TypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null) { if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
/** add value to store from BootstrapJson */ /** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store); this.setBootstrapConfigScurityInfo(store);
BootstrapConfig bsConfigNew = store.getBootstrapConfig(); BootstrapConfig bsConfigNew = store.getBootstrapConfig();
@ -73,7 +86,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
} }
bootstrapConfigStore.add(endPoint, bsConfigNew); bootstrapConfigStore.add(endPoint, bsConfigNew);
} catch (InvalidConfigurationException e) { } catch (InvalidConfigurationException e) {
e.printStackTrace(); log.error("", e);
} }
return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo()); return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo());
} }
@ -83,17 +96,16 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override @Override
public SecurityInfo getByIdentity(String identity) { public SecurityInfo getByIdentity(String identity) {
ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.BOOTSTRAP); ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.BOOTSTRAP);
/** add value to store from BootstrapJson */ if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
this.setBootstrapConfigScurityInfo(store); /** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
BootstrapConfig bsConfig = store.getBootstrapConfig(); BootstrapConfig bsConfig = store.getBootstrapConfig();
if (bsConfig.security != null) { if (bsConfig.security != null) {
try { try {
bootstrapConfigStore.add(store.getEndPoint(), bsConfig); bootstrapConfigStore.add(store.getEndPoint(), bsConfig);
} catch (InvalidConfigurationException e) { } catch (InvalidConfigurationException e) {
e.printStackTrace(); log.error("", e);
} }
return store.getSecurityInfo(); return store.getSecurityInfo();
} }
@ -141,28 +153,36 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private LwM2MBootstrapConfig getParametersBootstrap(ReadResultSecurityStore store) { private LwM2MBootstrapConfig getParametersBootstrap(ReadResultSecurityStore store) {
try { try {
JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential();
ObjectMapper mapper = new ObjectMapper(); if (bootstrapJsonCredential != null) {
LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); ObjectMapper mapper = new ObjectMapper();
JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class);
lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile());
LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class);
LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class);
if (getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class);
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); UUID sessionUUiD = UUID.randomUUID();
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); TransportProtos.SessionInfoProto sessionInfo = context.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits());
return lwM2MBootstrapConfig; context.getTransportService().registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(null, sessionInfo));
} if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
else { lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); String logMsg = String.format(LOG_LW2M_INFO + ": getParametersBootstrap: %s Access connect client with bootstrap server.", store.getEndPoint());
String logMsg = String.format(LOG_LW2M_ERROR + " getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint()); context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
// sentLogsToThingsboard(logMsg, store.getEndPoint()); return lwM2MBootstrapConfig;
return null; } else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
String logMsg = String.format(LOG_LW2M_ERROR + ": getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint());
context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
return null;
}
} }
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage()); log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage());
return null; return null;
} }
log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint());
return null;
} }
/** /**
@ -175,7 +195,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
* @return false if not sync between SecurityMode of Bootstrap credential and profile * @return false if not sync between SecurityMode of Bootstrap credential and profile
*/ */
private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) { private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) {
return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) &&
lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode())); lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode()));
} }
} }

205
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java

@ -1,205 +0,0 @@
/**
* Copyright © 2016-2020 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.bootstrap.secure;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.LwM2MTransportContextBootstrap;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.KeyFactory;
import java.security.GeneralSecurityException;
import java.security.KeyStoreException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.ECPoint;
import java.security.spec.KeySpec;
import java.security.spec.ECPrivateKeySpec;
import java.util.Arrays;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
@Slf4j
@Data
public class LwM2MSetSecurityStoreBootstrap {
private KeyStore keyStore;
private PublicKey publicKey;
private PrivateKey privateKey;
private LwM2MTransportContextBootstrap contextBs;
private LwM2MTransportContextServer contextS;
private LeshanBootstrapServerBuilder builder;
EditableSecurityStore securityStore;
public LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MTransportContextBootstrap contextBs, LwM2MTransportContextServer contextS, LwM2MSecurityMode dtlsMode) {
this.builder = builder;
this.contextBs = contextBs;
this.contextS = contextS;
/** Set securityStore with new registrationStore */
switch (dtlsMode) {
/** Use No_Sec only */
case NO_SEC:
setServerWithX509Cert(NO_SEC.code);
break;
/** Use PSK/RPK */
case PSK:
case RPK:
setRPK();
break;
case X509:
setServerWithX509Cert(X509.code);
break;
/** Use X509_EST only */
case X509_EST:
// TODO support sentinel pool and make pool configurable
break;
/** Use ather X509, PSK, No_Sec ?? */
default:
break;
}
}
private void setRPK() {
try {
/** Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) {
/** Get point values */
byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray());
/** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
/** Get keys */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
}
if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) {
/** Get point values */
byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray());
/** Create key specs */
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
/** Get keys */
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
}
if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
this.privateKey != null && this.privateKey.getEncoded().length > 0) {
this.builder.setPublicKey(this.publicKey);
this.builder.setPrivateKey(this.privateKey);
this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey);
getParamsRPK();
}
} catch (GeneralSecurityException | IllegalArgumentException e) {
log.error("[{}] Failed generate Server PSK/RPK", e.getMessage());
throw new RuntimeException(e);
}
}
private void setServerWithX509Cert(int securityModeCode) {
try {
if (this.contextS.getCtxServer().getKeyStoreValue() != null) {
KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue();
setBuilderX509();
X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias());
if (rootCAX509Cert != null && securityModeCode == X509.code) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
this.builder.setTrustedCertificates(trustedCertificates);
} else {
/** by default trust all */
this.builder.setTrustedCertificates(new X509Certificate[0]);
}
}
else {
/** by default trust all */
this.builder.setTrustedCertificates(new X509Certificate[0]);
log.error("Unable to load X509 files for BootStrapServer");
}
} catch (KeyStoreException ex) {
log.error("[{}] Unable to load X509 files server", ex.getMessage());
}
}
private void setBuilderX509() {
/**
* For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
* For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
*/
try {
X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias());
this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray());
if (this.privateKey != null && this.privateKey.getEncoded().length > 0) {
this.builder.setPrivateKey(this.privateKey);
}
if (serverCertificate != null) {
this.builder.setCertificateChain(new X509Certificate[]{serverCertificate});
this.contextBs.getCtxBootStrap().setBootstrapCertificate(serverCertificate);
}
} catch (Exception ex) {
log.error("[{}] Unable to load KeyStore files server", ex.getMessage());
}
}
private void getParamsRPK() {
if (this.publicKey instanceof ECPublicKey) {
/** Get x coordinate */
byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);
/** Get Y coordinate */
byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);
/** Get Curves params */
String params = ((ECPublicKey) this.publicKey).getParams().toString();
log.info(
" \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]",
params, Hex.encodeHexString(x), Hex.encodeHexString(y),
Hex.encodeHexString(this.publicKey.getEncoded()),
Hex.encodeHexString(this.privateKey.getEncoded()));
} else {
throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported).");
}
}
// private void getParamsX509() {
// try {
// log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]",
// Hex.encodeHexString(this.certificate.getEncoded()),
// Hex.encodeHexString(this.privateKey.getEncoded()));
// } catch (CertificateEncodingException e) {
// e.printStackTrace();
// }
// }
}

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

@ -49,7 +49,6 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
this.securityChecker = securityChecker; this.securityChecker = securityChecker;
} }
@Override
public BootstrapSession begin(String endpoint, Identity clientIdentity) { public BootstrapSession begin(String endpoint, Identity clientIdentity) {
boolean authorized; boolean authorized;
if (bsSecurityStore != null) { if (bsSecurityStore != null) {

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

@ -73,15 +73,15 @@ public class LWM2MGenerationPSkRPkECC {
try { try {
kpg = KeyPairGenerator.getInstance(algorithm, provider); kpg = KeyPairGenerator.getInstance(algorithm, provider);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); log.error("", e);
} catch (NoSuchProviderException e) { } catch (NoSuchProviderException e) {
e.printStackTrace(); log.error("", e);
} }
ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec); ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec);
try { try {
kpg.initialize(ecsp); kpg.initialize(ecsp);
} catch (InvalidAlgorithmParameterException e) { } catch (InvalidAlgorithmParameterException e) {
e.printStackTrace(); log.error("", e);
} }
KeyPair kp = kpg.genKeyPair(); KeyPair kp = kpg.genKeyPair();

49
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -39,12 +39,15 @@ import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
@Slf4j @Slf4j
@Component("LwM2MGetSecurityInfo") @Component("LwM2MGetSecurityInfo")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2MGetSecurityInfo { public class LwM2mCredentialsSecurityInfoValidator {
@Autowired @Autowired
public LwM2MTransportContextServer contextS; public LwM2MTransportContextServer contextS;
@ -53,7 +56,13 @@ public class LwM2MGetSecurityInfo {
public LwM2MTransportContextBootstrap contextBS; public LwM2MTransportContextBootstrap contextBS;
public ReadResultSecurityStore getSecurityInfo(String endPoint, TypeServer keyValue) { /**
* Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials
* @param endPoint -
* @param keyValue -
* @return ValidateDeviceCredentialsResponseMsg and SecurityInfo
*/
public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endPoint, TypeServer keyValue) {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1];
contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endPoint).build(), contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endPoint).build(),
@ -61,7 +70,7 @@ public class LwM2MGetSecurityInfo {
@Override @Override
public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
String credentialsBody = msg.getCredentialsBody(); String credentialsBody = msg.getCredentialsBody();
resultSecurityStore[0] = putSecurityInfo(endPoint, msg.getDeviceInfo().getDeviceName(), credentialsBody, keyValue); resultSecurityStore[0] = createSecurityInfo(endPoint, credentialsBody, keyValue);
resultSecurityStore[0].setMsg(msg); resultSecurityStore[0].setMsg(msg);
Optional<DeviceProfile> deviceProfileOpt = LwM2MTransportHandler.decode(msg.getProfileBody().toByteArray()); Optional<DeviceProfile> deviceProfileOpt = LwM2MTransportHandler.decode(msg.getProfileBody().toByteArray());
deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile));
@ -70,20 +79,27 @@ public class LwM2MGetSecurityInfo {
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
log.trace("[{}] Failed to process credentials PSK: {}", endPoint, e); log.trace("[{}] [{}] Failed to process credentials ", endPoint, e);
resultSecurityStore[0] = putSecurityInfo(endPoint, null, null, null); resultSecurityStore[0] = createSecurityInfo(endPoint, null, null);
latch.countDown(); latch.countDown();
} }
}); });
try { try {
latch.await(contextS.getCtxServer().getTimeout(), TimeUnit.MILLISECONDS); latch.await(contextS.getCtxServer().getTimeout(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); log.error("Failed to await credentials!", e);
} }
return resultSecurityStore[0]; return resultSecurityStore[0];
} }
private ReadResultSecurityStore putSecurityInfo(String endPoint, String deviceName, String jsonStr, TypeServer keyValue) { /**
* Create new SecurityInfo
* @param endPoint -
* @param jsonStr -
* @param keyValue -
* @return SecurityInfo
*/
private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, TypeServer keyValue) {
ReadResultSecurityStore result = new ReadResultSecurityStore(); ReadResultSecurityStore result = new ReadResultSecurityStore();
JsonObject objectMsg = LwM2MTransportHandler.validateJson(jsonStr); JsonObject objectMsg = LwM2MTransportHandler.validateJson(jsonStr);
if (objectMsg != null && !objectMsg.isJsonNull()) { if (objectMsg != null && !objectMsg.isJsonNull()) {
@ -99,20 +115,21 @@ public class LwM2MGetSecurityInfo {
if (keyValue.equals(TypeServer.BOOTSTRAP)) { if (keyValue.equals(TypeServer.BOOTSTRAP)) {
result.setBootstrapJsonCredential(object); result.setBootstrapJsonCredential(object);
result.setEndPoint(endPoint); result.setEndPoint(endPoint);
result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code);
} else { } else {
LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase());
switch (lwM2MSecurityMode) { switch (lwM2MSecurityMode) {
case NO_SEC: case NO_SEC:
getClientSecurityInfoNoSec(result); createClientSecurityInfoNoSec(result);
break; break;
case PSK: case PSK:
getClientSecurityInfoPSK(result, endPoint, object); createClientSecurityInfoPSK(result, endPoint, object);
break; break;
case RPK: case RPK:
getClientSecurityInfoRPK(result, endPoint, object); createClientSecurityInfoRPK(result, endPoint, object);
break; break;
case X509: case X509:
getClientSecurityInfoX509(result, endPoint); createClientSecurityInfoX509(result, endPoint);
break; break;
default: default:
break; break;
@ -123,12 +140,12 @@ public class LwM2MGetSecurityInfo {
return result; return result;
} }
private void getClientSecurityInfoNoSec(ReadResultSecurityStore result) { private void createClientSecurityInfoNoSec(ReadResultSecurityStore result) {
result.setSecurityInfo(null); result.setSecurityInfo(null);
result.setSecurityMode(NO_SEC.code); result.setSecurityMode(NO_SEC.code);
} }
private void getClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) { private void createClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) {
/** PSK Deserialization */ /** PSK Deserialization */
String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null; String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null;
if (identity != null && !identity.isEmpty()) { if (identity != null && !identity.isEmpty()) {
@ -148,7 +165,7 @@ public class LwM2MGetSecurityInfo {
} }
} }
private void getClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) { private void createClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) {
try { try {
if (object.has("key") && object.get("key").isJsonPrimitive()) { if (object.has("key") && object.get("key").isJsonPrimitive()) {
byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray());
@ -163,7 +180,7 @@ public class LwM2MGetSecurityInfo {
} }
} }
private void getClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) { private void createClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) {
result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint));
result.setSecurityMode(X509.code); result.setSecurityMode(X509.code);
} }

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

@ -33,17 +33,17 @@ import java.util.Optional;
@Slf4j @Slf4j
public class LwM2MSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener { public class LwM2MSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
private LwM2MTransportService service; private LwM2MTransportServiceImpl service;
private TransportProtos.SessionInfoProto sessionInfo; private TransportProtos.SessionInfoProto sessionInfo;
LwM2MSessionMsgListener(LwM2MTransportService service, TransportProtos.SessionInfoProto sessionInfo) { public LwM2MSessionMsgListener(LwM2MTransportServiceImpl service, TransportProtos.SessionInfoProto sessionInfo) {
this.service = service; this.service = service;
this.sessionInfo = sessionInfo; this.sessionInfo = sessionInfo;
} }
@Override @Override
public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) {
log.info("[{}] attributesResponse", getAttributesResponse); this.service.onGetAttributesResponse(getAttributesResponse, this.sessionInfo);
} }
@Override @Override

88
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportContextServer.java

@ -30,15 +30,26 @@ package org.thingsboard.server.transport.lwm2m.server;
* limitations under the License. * limitations under the License.
*/ */
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_TELEMETRY;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
@ -47,14 +58,87 @@ public class LwM2MTransportContextServer extends TransportContext {
private LwM2MTransportConfigServer ctxServer; private LwM2MTransportConfigServer ctxServer;
@Autowired @Autowired
LwM2MTransportConfigServer lwM2MTransportConfigServer; protected LwM2MTransportConfigServer lwM2MTransportConfigServer;
@Autowired
private TransportService transportService;
@Getter
@Autowired
private LwM2MJsonAdaptor adaptor;
@Autowired
LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore;
@PostConstruct @PostConstruct
public void init() { public void init() {
this.ctxServer = lwM2MTransportConfigServer; this.ctxServer = lwM2MTransportConfigServer;
} }
public LwM2MTransportConfigServer getCtxServer () { public LwM2MTransportConfigServer getCtxServer() {
return this.ctxServer; return this.ctxServer;
} }
/**
* Sent to Thingsboard Attribute || Telemetry
*
* @param msg - JsonObject: [{name: value}]
* @return - dummy
*/
private <T> TransportServiceCallback<Void> getPubAckCallbackSentAttrTelemetry(final T msg) {
return new TransportServiceCallback<Void>() {
@Override
public void onSuccess(Void dummy) {
log.trace("Success to publish msg: {}, dummy: {}", msg, dummy);
}
@Override
public void onError(Throwable e) {
log.trace("[{}] Failed to publish msg: {}", msg, e);
}
};
}
public void sentParametersOnThingsboard(JsonElement msg, String topicName, TransportProtos.SessionInfoProto sessionInfo) {
try {
if (topicName.equals(LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) {
TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSentAttrTelemetry(call));
} else if (topicName.equals(LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(call));
}
} catch (AdaptorException e) {
log.error("[{}] Failed to process publish msg [{}]", topicName, e);
log.info("[{}] Closing current session due to invalid publish", topicName);
}
}
public JsonObject getTelemetryMsgObject(String logMsg) {
JsonObject telemetries = new JsonObject();
telemetries.addProperty(LOG_LW2M_TELEMETRY, logMsg);
return telemetries;
}
/**
* @return - sessionInfo after access connect client
*/
public TransportProtos.SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
return TransportProtos.SessionInfoProto.newBuilder()
.setNodeId(this.getNodeId())
.setSessionIdMSB(mostSignificantBits)
.setSessionIdLSB(leastSignificantBits)
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.setDeviceName(msg.getDeviceInfo().getDeviceName())
.setDeviceType(msg.getDeviceInfo().getDeviceType())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
.build();
}
} }

207
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java

@ -16,7 +16,6 @@
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
@ -24,57 +23,60 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.nustaq.serialization.FSTConfiguration; import org.nustaq.serialization.FSTConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient;
import javax.annotation.PostConstruct;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import java.util.Map;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.Date;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Collections; import java.util.Optional;
@Slf4j @Slf4j
@Component("LwM2MTransportHandler") //@Component("LwM2MTransportHandler")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") //@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2MTransportHandler{ 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 // 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. // send a Confirmable message to the time when an acknowledgement is no longer expected.
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000l; // 2min in ms
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr"; public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String KEYNAME = "keyName";
public static final String ATTRIBUTE = "attribute"; public static final String ATTRIBUTE = "attribute";
public static final String TELEMETRY = "telemetry"; public static final String TELEMETRY = "telemetry";
private static final String REQUEST = "/request";
private static final String RESPONSE = "/response";
private static final String ATTRIBUTES = "/" + ATTRIBUTE;
public static final String TELEMETRIES = "/" + TELEMETRY;
public static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
public static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/";
public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES;
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES;
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
public static final String KEYNAME = "keyName";
public static final String OBSERVE = "observe"; public static final String OBSERVE = "observe";
public static final String BOOTSTRAP = "bootstrap"; public static final String BOOTSTRAP = "bootstrap";
public static final String SERVERS = "servers"; public static final String SERVERS = "servers";
public static final String LWM2M_SERVER = "lwm2mServer"; public static final String LWM2M_SERVER = "lwm2mServer";
public static final String BOOTSTRAP_SERVER = "bootstrapServer"; public static final String BOOTSTRAP_SERVER = "bootstrapServer";
public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes";
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry";
public static final String LOG_LW2M_TELEMETRY = "logLwm2m"; public static final String LOG_LW2M_TELEMETRY = "logLwm2m";
public static final String LOG_LW2M_INFO = "info"; public static final String LOG_LW2M_INFO = "info";
public static final String LOG_LW2M_ERROR = "error"; public static final String LOG_LW2M_ERROR = "error";
@ -91,44 +93,55 @@ public class LwM2MTransportHandler{
/** /**
* Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see * Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see
* section 5.3.3 of the LW M2M spec). * section 5.3.3 of the LW M2M spec).
* if all resources are to be replaced
*/ */
public static final String POST_TYPE_OPER_WRITE_REPLACE = "replace"; public static final String POST_TYPE_OPER_WRITE_REPLACE = "replace";
/** /**
* Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section * Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section
* 5.3.3 of the LW M2M spec). * 5.3.3 of the LW M2M spec).
* if this is a partial update request
*/ */
public static final String PUT_TYPE_OPER_WRITE_UPDATE = "update"; public static final String PUT_TYPE_OPER_WRITE_UPDATE = "update";
public static final String PUT_TYPE_OPER_WRITE_ATTRIBUTES = "wright-attributes"; public static final String PUT_TYPE_OPER_WRITE_ATTRIBUTES = "wright-attributes";
public static final String EVENT_AWAKE = "AWAKE"; public static final String EVENT_AWAKE = "AWAKE";
public static final String SERVICE_CHANNEL = "SERVICE";
public static final String RESPONSE_CHANNEL = "RESP";
private static Gson gson = null; // @Autowired
// @Qualifier("LeshanServerCert")
// private LeshanServer lhServerCert;
//
// @Autowired
// @Qualifier("LeshanServerNoSecPskRpk")
// private LeshanServer lhServerNoSecPskRpk;
@Autowired // @Autowired
@Qualifier("LeshanServerCert") // @Qualifier("ServerListenerCert")
private LeshanServer lhServerCert; // private LwM2mServerListener serverListenerCert;
//
// @Autowired
// @Qualifier("ServerListenerNoSecPskRpk")
// private LwM2mServerListener serverListenerNoSecPskRpk;
@Autowired
@Qualifier("leshanServerNoSecPskRpk")
private LeshanServer lhServerNoSecPskRpk;
@Autowired // @PostConstruct
private LwM2MTransportService service; // 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) {
@PostConstruct
public void init() {
LwM2mServerListener lwM2mServerListener = new LwM2mServerListener(lhServerCert, service);
this.lhServerCert.getRegistrationService().addListener(lwM2mServerListener.registrationListener);
this.lhServerCert.getPresenceService().addListener(lwM2mServerListener.presenceListener);
this.lhServerCert.getObservationService().addListener(lwM2mServerListener.observationListener);
lwM2mServerListener = new LwM2mServerListener(lhServerNoSecPskRpk, service);
this.lhServerNoSecPskRpk.getRegistrationService().addListener(lwM2mServerListener.registrationListener);
this.lhServerNoSecPskRpk.getPresenceService().addListener(lwM2mServerListener.presenceListener);
this.lhServerNoSecPskRpk.getObservationService().addListener(lwM2mServerListener.observationListener);
}
public static NetworkConfig getCoapConfig() {
NetworkConfig coapConfig; NetworkConfig coapConfig;
File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME); File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME);
if (configFile.isFile()) { if (configFile.isFile()) {
@ -138,28 +151,26 @@ public class LwM2MTransportHandler{
coapConfig = LeshanServerBuilder.createDefaultNetworkConfig(); coapConfig = LeshanServerBuilder.createDefaultNetworkConfig();
coapConfig.store(configFile); coapConfig.store(configFile);
} }
coapConfig.setString("COAP_PORT", Integer.toString(serverPortNoSec));
coapConfig.setString("COAP_SECURE_PORT", Integer.toString(serverSecurePort));
return coapConfig; return coapConfig;
} }
public static String getValueTypeToString (Object value, ResourceModel.Type type) { public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException {
switch (type) { switch (type) {
case STRING: // String case BOOLEAN:
case OBJLNK: // ObjectLink case INTEGER:
return value.toString(); case FLOAT:
case INTEGER: // Long return String.valueOf(valueOld).equals(String.valueOf(valueNew));
return Long.toString((long) value); case TIME:
case BOOLEAN: // Boolean return ((Date) valueOld).getTime() == ((Date) valueNew).getTime();
return Boolean.toString((Boolean) value); case STRING:
case FLOAT: // Double case OBJLNK:
return Double.toString((Float)value); return valueOld.equals(valueNew);
case TIME: // Date case OPAQUE:
String DATE_FORMAT = "MMM d, yyyy HH:mm a"; return Hex.decodeHex(((String) valueOld).toCharArray()).equals(Hex.decodeHex(((String) valueNew).toCharArray()));
DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
return formatter.format(new Date((Long) Integer.toUnsignedLong(Integer.valueOf((Integer) value))));
case OPAQUE: // byte[] value, base64
return Hex.encodeHexString((byte[])value);
default: default:
return null; throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type);
} }
} }
@ -182,26 +193,25 @@ public class LwM2MTransportHandler{
attrTelemetryObserveValue.setPostAttributeProfile(profilesConfigData.get(ATTRIBUTE).getAsJsonArray()); attrTelemetryObserveValue.setPostAttributeProfile(profilesConfigData.get(ATTRIBUTE).getAsJsonArray());
attrTelemetryObserveValue.setPostTelemetryProfile(profilesConfigData.get(TELEMETRY).getAsJsonArray()); attrTelemetryObserveValue.setPostTelemetryProfile(profilesConfigData.get(TELEMETRY).getAsJsonArray());
attrTelemetryObserveValue.setPostObserveProfile(profilesConfigData.get(OBSERVE).getAsJsonArray()); attrTelemetryObserveValue.setPostObserveProfile(profilesConfigData.get(OBSERVE).getAsJsonArray());
return attrTelemetryObserveValue; return attrTelemetryObserveValue;
} }
/** /**
* @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard * @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard
* Example: with pathResource (use only pathResource) * Example: with pathResource (use only pathResource)
* property: "observeAttr" * property: "observeAttr"
* {"keyName": { * {"keyName": {
* "/3/0/1": "modelNumber", * "/3/0/1": "modelNumber",
* "/3/0/0": "manufacturer", * "/3/0/0": "manufacturer",
* "/3/0/2": "serialNumber" * "/3/0/2": "serialNumber"
* }, * },
* "attribute":["/2/0/1","/3/0/9"], * "attribute":["/2/0/1","/3/0/9"],
* "telemetry":["/1/0/1","/2/0/1","/6/0/1"], * "telemetry":["/1/0/1","/2/0/1","/6/0/1"],
* "observe":["/2/0","/2/0/0","/4/0/2"]} * "observe":["/2/0","/2/0/0","/4/0/2"]}
*/ */
public static JsonObject getObserveAttrTelemetryFromThingsboard(DeviceProfile deviceProfile) { public static JsonObject getObserveAttrTelemetryFromThingsboard(DeviceProfile deviceProfile) {
if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); // Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
Object observeAttr = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); Object observeAttr = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties();
try { try {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
@ -209,7 +219,7 @@ public class LwM2MTransportHandler{
JsonObject objectMsg = (observeAttrStr != null) ? validateJson(observeAttrStr) : null; JsonObject objectMsg = (observeAttrStr != null) ? validateJson(observeAttrStr) : null;
return (getValidateCredentialsBodyFromThingsboard(objectMsg)) ? objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject() : null; return (getValidateCredentialsBodyFromThingsboard(objectMsg)) ? objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject() : null;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); log.error("", e);
} }
} }
return null; return null;
@ -217,7 +227,6 @@ public class LwM2MTransportHandler{
public static JsonObject getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { public static JsonObject getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) {
if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties();
try { try {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
@ -225,7 +234,7 @@ public class LwM2MTransportHandler{
JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null; JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null;
return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null; return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); log.error("", e);
} }
} }
return null; return null;
@ -276,7 +285,7 @@ public class LwM2MTransportHandler{
jsonValidFlesh = jsonValidFlesh.replaceAll("\n", ""); jsonValidFlesh = jsonValidFlesh.replaceAll("\n", "");
jsonValidFlesh = jsonValidFlesh.replaceAll("\t", ""); jsonValidFlesh = jsonValidFlesh.replaceAll("\t", "");
jsonValidFlesh = jsonValidFlesh.replaceAll(" ", ""); jsonValidFlesh = jsonValidFlesh.replaceAll(" ", "");
String jsonValid = (jsonValidFlesh.substring(0, 1).equals("\"") && jsonValidFlesh.substring(jsonValidFlesh.length() - 1).equals("\"")) ? jsonValidFlesh.substring(1, jsonValidFlesh.length() - 1) : jsonValidFlesh; String jsonValid = (jsonValidFlesh.charAt(0) == '"' && jsonValidFlesh.charAt(jsonValidFlesh.length() - 1) == '"') ? jsonValidFlesh.substring(1, jsonValidFlesh.length() - 1) : jsonValidFlesh;
try { try {
object = new JsonParser().parse(jsonValid).getAsJsonObject(); object = new JsonParser().parse(jsonValid).getAsJsonObject();
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException e) {
@ -288,7 +297,7 @@ public class LwM2MTransportHandler{
public static <T> Optional<T> decode(byte[] byteArray) { public static <T> Optional<T> decode(byte[] byteArray) {
try { try {
FSTConfiguration config = FSTConfiguration.createDefaultConfiguration();; FSTConfiguration config = FSTConfiguration.createDefaultConfiguration();
T msg = (T) config.asObject(byteArray); T msg = (T) config.asObject(byteArray);
return Optional.ofNullable(msg); return Optional.ofNullable(msg);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -297,37 +306,29 @@ public class LwM2MTransportHandler{
} }
} }
/** public static String splitCamelCaseString(String s) {
* Equals to Map for values LinkedList<String> linkedListOut = new LinkedList<>();
* @param map1
* @param map2
* @param <V>
* @return
*/
public static <V extends Comparable<V>> boolean mapsEquals(Map<?,V> map1, Map<?,V> map2) {
List<V> values1 = new ArrayList<V>(map1.values());
List<V> values2 = new ArrayList<V>(map2.values());
Collections.sort(values1);
Collections.sort(values2);
return values1.equals(values2);
}
public static String convertCamelCase (String str) {
str = str.toLowerCase().replace("/[^a-z ]+/g", " ");
str = str.replace("/^(.)|\\s(.)/g", "$1");
str = str.replace("/[^a-zA-Z]+/g", "");
return str;
}
public static String splitCamelCaseString(String s){
LinkedList<String> linkedListOut = new LinkedList<String>();
LinkedList<String> linkedList = new LinkedList<String>((Arrays.asList(s.split(" ")))); LinkedList<String> linkedList = new LinkedList<String>((Arrays.asList(s.split(" "))));
linkedList.stream().forEach(str-> { linkedList.forEach(str -> {
String strOut = str.replaceAll("\\W", "").replaceAll("_", "").toUpperCase(); String strOut = str.replaceAll("\\W", "").replaceAll("_", "").toUpperCase();
if (strOut.length()>1) linkedListOut.add(strOut.substring(0, 1) + strOut.substring(1).toLowerCase()); if (strOut.length() > 1) linkedListOut.add(strOut.charAt(0) + strOut.substring(1).toLowerCase());
else linkedListOut.add(strOut); else linkedListOut.add(strOut);
}); });
linkedListOut.set(0, (linkedListOut.get(0).substring(0, 1).toLowerCase() + linkedListOut.get(0).substring(1))); linkedListOut.set(0, (linkedListOut.get(0).substring(0, 1).toLowerCase() + linkedListOut.get(0).substring(1)));
return StringUtils.join(linkedListOut, ""); return StringUtils.join(linkedListOut, "");
} }
public static <T> TransportServiceCallback<Void> getAckCallback(LwM2MClient lwM2MClient, int requestId, String typeTopic) {
return new TransportServiceCallback<Void>() {
@Override
public void onSuccess(Void dummy) {
log.trace("[{}] [{}] - requestId [{}] - EndPoint , Access AckCallback", typeTopic, requestId, lwM2MClient.getEndPoint());
}
@Override
public void onError(Throwable e) {
log.trace("[{}] Failed to publish msg", e.toString());
}
};
}
} }

203
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java

@ -20,26 +20,28 @@ import org.eclipse.californium.core.coap.Response;
import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CancelObservationRequest;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.CancelObservationRequest;
import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.CancelObservationResponse; import org.eclipse.leshan.core.response.CancelObservationResponse;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.ResponseCallback;
import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.response.WriteResponse;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
@ -50,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
@ -65,32 +68,36 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO;
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_EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; 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;
@Slf4j @Slf4j
@Service("LwM2MTransportRequest") @Service("LwM2MTransportRequest")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2MTransportRequest { public class LwM2MTransportRequest {
private final ExecutorService executorService; private ExecutorService executorResponse;
private static final String RESPONSE_CHANNEL = "THINGSBOARD_RESP"; private ExecutorService executorResponseError;
private LwM2mValueConverterImpl converter;
@Autowired @Autowired
LwM2MTransportService service; LwM2MTransportServiceImpl service;
public LwM2MTransportRequest() {
executorService = Executors.newCachedThreadPool(
new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL)));
}
@Autowired
public LwM2MTransportContextServer context;
@PostConstruct @PostConstruct
public void init() { public void init() {
this.converter = LwM2mValueConverterImpl.getInstance();
executorResponse = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestPoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL)));
executorResponseError = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestErrorPoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel response Error", RESPONSE_CHANNEL)));
} }
public Collection<Registration> doGetRegistrations(LeshanServer lwServer) { public Collection<Registration> doGetRegistrations(LeshanServer lwServer) {
@ -113,16 +120,13 @@ public class LwM2MTransportRequest {
* @param lwM2MClient * @param lwM2MClient
* @param observation * @param observation
*/ */
public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper, public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper, String contentFormatParam,
String contentFormatParam, LwM2MClient lwM2MClient, Observation observation, Object params, long timeoutInMs) { LwM2MClient lwM2MClient, Observation observation, Object params, long timeoutInMs, boolean isDelayedUpdate) {
ResultIds resultIds = new ResultIds(target); LwM2mPath resultIds = new LwM2mPath(target);
if (registration != null && resultIds.getObjectId() >= 0) { if (registration != null && resultIds.getObjectId() >= 0) {
DownlinkRequest request = null; DownlinkRequest request = null;
ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null; ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null;
ResourceModel resource = (resultIds.resourceId >= 0) ? (lwM2MClient != null) ? ResourceModel resource = service.context.getCtxServer().getResourceModel(registration, resultIds);
lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getObjectModel().resources.get(resultIds.resourceId) : null : null;
ResourceModel.Type resType = (resource == null) ? null : resource.type;
boolean resMultiple = (resource == null) ? false : resource.multiple;
timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
switch (typeOper) { switch (typeOper) {
case GET_TYPE_OPER_READ: case GET_TYPE_OPER_READ:
@ -132,10 +136,10 @@ public class LwM2MTransportRequest {
request = new DiscoverRequest(target); request = new DiscoverRequest(target);
break; break;
case GET_TYPE_OPER_OBSERVE: case GET_TYPE_OPER_OBSERVE:
if (resultIds.getResourceId() >= 0) { if (resultIds.isResource()) {
request = new ObserveRequest(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId()); request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.getInstanceId() >= 0) { } else if (resultIds.isObjectInstance()) {
request = new ObserveRequest(resultIds.getObjectId(), resultIds.getInstanceId()); request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else if (resultIds.getObjectId() >= 0) { } else if (resultIds.getObjectId() >= 0) {
request = new ObserveRequest(resultIds.getObjectId()); request = new ObserveRequest(resultIds.getObjectId());
} }
@ -144,27 +148,30 @@ public class LwM2MTransportRequest {
request = new CancelObservationRequest(observation); request = new CancelObservationRequest(observation);
break; break;
case POST_TYPE_OPER_EXECUTE: case POST_TYPE_OPER_EXECUTE:
if (params != null && !resMultiple) { if (params != null && resource != null && !resource.multiple) {
request = new ExecuteRequest(target, LwM2MTransportHandler.getValueTypeToString(params, resType)); request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds));
} else { } else {
request = new ExecuteRequest(target); request = new ExecuteRequest(target);
} }
break; break;
case POST_TYPE_OPER_WRITE_REPLACE: case POST_TYPE_OPER_WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format. // Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
if (contentFormat.equals(ContentFormat.TLV) && !resMultiple) { if (resource != null) {
request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, resType); if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
} 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) && !resMultiple) { // Mode.REPLACE && Request to write a <b>String Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON)
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, resType); else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration);
}
} }
break; break;
case PUT_TYPE_OPER_WRITE_UPDATE: case PUT_TYPE_OPER_WRITE_UPDATE:
if (resultIds.getResourceId() >= 0) { if (resultIds.getResourceId() >= 0) {
ResourceModel resourceModel = lwServer.getModelProvider().getObjectModel(registration).getObjectModel(resultIds.getObjectId()).resources.get(resultIds.getResourceId()); ResourceModel resourceModel = lwServer.getModelProvider().getObjectModel(registration).getObjectModel(resultIds.getObjectId()).resources.get(resultIds.getResourceId());
ResourceModel.Type typeRes = resourceModel.type; ResourceModel.Type typeRes = resourceModel.type;
// request = getWriteRequestResource(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, typeRes); LwM2mNode node = LwM2mSingleResource.newStringResource(resultIds.getResourceId(), (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds));
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, target, node);
} }
break; break;
case PUT_TYPE_OPER_WRITE_ATTRIBUTES: case PUT_TYPE_OPER_WRITE_ATTRIBUTES:
@ -202,55 +209,76 @@ public class LwM2MTransportRequest {
Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.valueOf("1"))); Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.valueOf("1")));
Attribute[] attrs = {pmin}; Attribute[] attrs = {pmin};
AttributeSet attrSet = new AttributeSet(attrs); AttributeSet attrSet = new AttributeSet(attrs);
if (resultIds.getResourceId() >= 0) { if (resultIds.isResource()) {
request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), attrSet); request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), attrSet);
} else if (resultIds.getInstanceId() >= 0) { } else if (resultIds.isObjectInstance()) {
request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getInstanceId(), attrSet); request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), attrSet);
} else if (resultIds.getObjectId() >= 0) { } else if (resultIds.getObjectId() >= 0) {
request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet); request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet);
} }
break; break;
default: default:
} }
if (request != null) sendRequest(lwServer, registration, request, lwM2MClient, timeoutInMs);
if (request != null) {
this.sendRequest(lwServer, registration, request, lwM2MClient, timeoutInMs, isDelayedUpdate);
} else if (isDelayedUpdate) {
String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: Resource path - %s msg No SendRequest to Client", target);
service.sentLogsToThingsboard(msg, registration);
log.error("[{}] - [{}] No SendRequest", target);
this.handleResponseError(registration, target, lwM2MClient, isDelayedUpdate);
}
} }
} }
/** /**
* *
* @param lwServer * @param lwServer -
* @param registration * @param registration -
* @param request * @param request -
* @param lwM2MClient * @param lwM2MClient -
* @param timeoutInMs * @param timeoutInMs -
*/ */
private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, LwM2MClient lwM2MClient, long timeoutInMs) {
private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, LwM2MClient lwM2MClient, long timeoutInMs, boolean isDelayedUpdate) {
lwServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> { lwServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (isSuccess(((Response)response.getCoapResponse()).getCode())) { if (isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request, lwM2MClient); this.handleResponse(registration, request.getPath().toString(), response, request, lwM2MClient, isDelayedUpdate);
if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) { if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) {
String msg = String.format(LOG_LW2M_INFO + " sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", String delayedUpdateStr = "";
((Response)response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), if (isDelayedUpdate) {
((LwM2mSingleResource)((WriteRequest) request).getNode()).getValue().toString()); delayedUpdateStr = " (delayedUpdate) ";
service.sentLogsToThingsboard(msg, registration.getId()); }
log.info("[{}] - [{}] [{}] [{}] Update SendRequest", ((Response)response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), ((LwM2mSingleResource)((WriteRequest) request).getNode()).getValue()); String msg = String.format(LOG_LW2M_INFO + ": sendRequest Replace%s: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client",
delayedUpdateStr, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString());
service.sentLogsToThingsboard(msg, registration);
log.info("[{}] - [{}] [{}] [{}] Update SendRequest[{}]", ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue(), delayedUpdateStr);
}
} else {
String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client",
((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
service.sentLogsToThingsboard(msg, registration);
log.error("[{}] - [{}] [{}] error SendRequest", ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest() && isDelayedUpdate) {
this.handleResponseError(registration, request.getPath().toString(), lwM2MClient, isDelayedUpdate);
} }
} }
else {
String msg = String.format(LOG_LW2M_ERROR + " sendRequest: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client",
((Response)response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
service.sentLogsToThingsboard(msg, registration.getId());
log.error("[{}] - [{}] [{}] error SendRequest", ((Response)response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
}
}, e -> { }, e -> {
String msg = String.format(LOG_LW2M_ERROR + " sendRequest: Resource path - %s msg error - %s SendRequest to Client", String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: Resource path - %s msg error - %s SendRequest to Client",
request.getPath().toString(), e.toString()); request.getPath().toString(), e.toString());
service.sentLogsToThingsboard(msg, registration.getId()); service.sentLogsToThingsboard(msg, registration);
log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString()); log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString());
if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest() && isDelayedUpdate) {
this.handleResponseError(registration, request.getPath().toString(), lwM2MClient, isDelayedUpdate);
}
}); });
} }
private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type) { private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type, Registration registration) {
try { try {
switch (type) { switch (type) {
case STRING: // String case STRING: // String
@ -264,29 +292,38 @@ public class LwM2MTransportRequest {
case FLOAT: // Double case FLOAT: // Double
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.valueOf(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.valueOf(value.toString())); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.valueOf(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.valueOf(value.toString()));
case TIME: // Date case TIME: // Date
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, new Date((Long) Integer.toUnsignedLong(Integer.valueOf(value.toString())))) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, new Date((Long) Integer.toUnsignedLong(Integer.valueOf(value.toString())))); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, new Date(Long.decode(value.toString()))) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, new Date((Long) Integer.toUnsignedLong(Integer.valueOf(value.toString()))));
case OPAQUE: // byte[] value, base64 case OPAQUE: // byte[] value, base64
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray()));
default: default:
} }
return null; return null;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; String patn = "/" + objectId + "/" + instanceId + "/" + resourceId;
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client",
return null; patn, type, value, e.toString());
service.sentLogsToThingsboard(msg, registration);
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString());
return null;
} }
} }
private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient) { private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient, boolean isDelayedUpdate) {
executorService.submit(new Runnable() { executorResponse.submit(() -> {
@Override try {
public void run() { sendResponse(registration, path, response, request, lwM2MClient, isDelayedUpdate);
} catch (Exception e) {
log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", registration.getEndpoint(), path, e);
}
});
}
try { private void handleResponseError(Registration registration, final String path, LwM2MClient lwM2MClient, boolean isDelayedUpdate) {
sendResponse(registration, path, response, request, lwM2MClient); executorResponseError.submit(() -> {
} catch (RuntimeException t) { try {
log.error("[{}] endpoint [{}] path [{}] error Unable to after send response.", registration.getEndpoint(), path, t.toString()); if (isDelayedUpdate) lwM2MClient.onSuccessOrErrorDelayedRequests(path);
} } catch (RuntimeException t) {
log.error("[{}] endpoint [{}] path [{}] RuntimeException Unable to after send response.", registration.getEndpoint(), path, t);
} }
}); });
} }
@ -298,7 +335,7 @@ public class LwM2MTransportRequest {
* @param response - * @param response -
* @param lwM2MClient - * @param lwM2MClient -
*/ */
private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient) { private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient, boolean isDelayedUpdate) {
if (response instanceof ObserveResponse) { if (response instanceof ObserveResponse) {
service.onObservationResponse(registration, path, (ReadResponse) response); service.onObservationResponse(registration, path, (ReadResponse) response);
} else if (response instanceof CancelObservationResponse) { } else if (response instanceof CancelObservationResponse) {
@ -329,7 +366,7 @@ public class LwM2MTransportRequest {
log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", path, response); log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", path, response);
} else if (response instanceof WriteResponse) { } else if (response instanceof WriteResponse) {
log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", path, response); log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", path, response);
service.onAttributeUpdateOk(registration, path, (WriteRequest) request); service.onAttributeUpdateOk(registration, path, (WriteRequest) request, isDelayedUpdate);
} }
} }
} }

280
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java

@ -20,10 +20,16 @@ import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.model.VersionedModelProvider; import org.eclipse.leshan.server.model.VersionedModelProvider;
import org.eclipse.leshan.server.redis.RedisRegistrationStore;
import org.eclipse.leshan.server.redis.RedisSecurityStore;
import org.eclipse.leshan.server.security.DefaultAuthorizer;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -31,11 +37,36 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.server.secure.LwM2MSetSecurityStoreServer;
import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.Pool;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
import java.util.Arrays;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig;
@ -45,6 +76,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle
@Configuration("LwM2MTransportServerConfiguration") @Configuration("LwM2MTransportServerConfiguration")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2MTransportServerConfiguration { public class LwM2MTransportServerConfiguration {
private PublicKey publicKey;
private PrivateKey privateKey;
@Autowired @Autowired
private LwM2MTransportContextServer context; private LwM2MTransportContextServer context;
@ -53,50 +86,257 @@ public class LwM2MTransportServerConfiguration {
private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore;
@Primary @Primary
@Bean(name = "LeshanServerCert") @Bean(name = "leshanServerPsk")
public LeshanServer getLeshanServerCert() { @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_psk:false}'=='true')")
log.info("Starting LwM2M transport ServerCert... PostConstruct"); public LeshanServer getLeshanServerPsk() {
return getLeshanServer(this.context.getCtxServer().getServerPortCert(), this.context.getCtxServer().getServerSecurePortCert(), X509); log.info("Starting LwM2M transport ServerPsk... PostConstruct");
return getLeshanServer(this.context.getCtxServer().getServerPortNoSecPsk(), this.context.getCtxServer().getServerPortPsk(), PSK);
} }
@Bean(name = "leshanServerNoSecPskRpk") @Bean(name = "leshanServerRpk")
public LeshanServer getLeshanServerNoSecPskRpk() { @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_rpk:false}'=='true')")
log.info("Starting LwM2M transport ServerNoSecPskRpk... PostConstruct"); public LeshanServer getLeshanServerRpk() {
return getLeshanServer(this.context.getCtxServer().getServerPort(), this.context.getCtxServer().getServerSecurePort(), RPK); log.info("Starting LwM2M transport ServerRpk... PostConstruct");
return getLeshanServer(this.context.getCtxServer().getServerPortNoSecRpk(), this.context.getCtxServer().getServerPortRpk(), RPK);
} }
private LeshanServer getLeshanServer(Integer serverPort, Integer serverSecurePort, LwM2MSecurityMode dtlsMode) { @Bean(name = "leshanServerX509")
@ConditionalOnExpression("('${transport.lwm2m.server.secure.start_x509:false}'=='true')")
public LeshanServer getLeshanServerX509() {
log.info("Starting LwM2M transport ServerX509... PostConstruct");
return getLeshanServer(this.context.getCtxServer().getServerPortNoSecX509(), this.context.getCtxServer().getServerPortX509(), X509);
}
private LeshanServer getLeshanServer(Integer serverPortNoSec, Integer serverSecurePort, LwM2MSecurityMode dtlsMode) {
LeshanServerBuilder builder = new LeshanServerBuilder(); LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(this.context.getCtxServer().getServerHost(), serverPort); builder.setLocalAddress(this.context.getCtxServer().getServerHost(), serverPortNoSec);
builder.setLocalSecureAddress(this.context.getCtxServer().getServerSecureHost(), serverSecurePort); builder.setLocalSecureAddress(this.context.getCtxServer().getServerSecureHost(), serverSecurePort);
builder.setEncoder(new DefaultLwM2mNodeEncoder()); builder.setEncoder(new DefaultLwM2mNodeEncoder());
LwM2mNodeDecoder decoder = new DefaultLwM2mNodeDecoder(); LwM2mNodeDecoder decoder = new DefaultLwM2mNodeDecoder();
builder.setDecoder(decoder); builder.setDecoder(decoder);
builder.setEncoder(new DefaultLwM2mNodeEncoder(new LwM2mValueConverterImpl())); builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/** Create CoAP Config */ /** Create CoAP Config */
builder.setCoapConfig(getCoapConfig()); builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort));
/** Define model provider (Create Models )*/ /** Define model provider (Create Models )*/
LwM2mModelProvider modelProvider = new VersionedModelProvider(this.context.getCtxServer().getModelsValue()); LwM2mModelProvider modelProvider = new VersionedModelProvider(this.context.getCtxServer().getModelsValue());
builder.setObjectModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider);
/** Create DTLS security mode
* There can be only one DTLS security mode
*/
this.LwM2MSetSecurityStoreServer(builder, dtlsMode);
/** Create DTLS Config */ /** Create DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isSupportDeprecatedCiphersEnable()); if (dtlsMode==PSK) {
dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isRecommendedCiphers());
dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getCtxServer().isRecommendedSupportedGroups());
dtlsConfig.setSupportedCipherSuites(TLS_PSK_WITH_AES_128_CBC_SHA256);
}
else {
dtlsConfig.setRecommendedSupportedGroupsOnly(!this.context.getCtxServer().isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(!this.context.getCtxServer().isRecommendedCiphers());
}
/** Set DTLS Config */ /** Set DTLS Config */
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
/** Use a magic converter to support bad type send by the UI. */ /** Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(new LwM2mValueConverterImpl())); builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/** Create DTLS security mode
* There can be only one DTLS security mode
*/
new LwM2MSetSecurityStoreServer(builder, context, lwM2mInMemorySecurityStore, dtlsMode);
/** Create LWM2M server */ /** Create LWM2M server */
return builder.build(); return builder.build();
} }
private void LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MSecurityMode dtlsMode) {
/** Set securityStore with new registrationStore */
EditableSecurityStore securityStore = lwM2mInMemorySecurityStore;
switch (dtlsMode) {
/** Use PSK only */
case PSK:
generatePSK_RPK();
infoParamsPSK();
break;
/** Use RPK only */
case RPK:
generatePSK_RPK();
if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPublicKey(this.publicKey);
builder.setPrivateKey(this.privateKey);
infoParamsRPK();
}
break;
/** Use x509 only */
case X509:
setServerWithX509Cert(builder);
break;
/** No security */
case NO_SEC:
builder.setTrustedCertificates(new X509Certificate[0]);
break;
/** Use x509 with EST */
case X509_EST:
// TODO support sentinel pool and make pool configurable
break;
case REDIS:
/**
* Set securityStore with new registrationStore (if use redis store)
* Connect to redis
*/
Pool<Jedis> jedis = null;
try {
jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl()));
securityStore = new RedisSecurityStore(jedis);
builder.setRegistrationStore(new RedisRegistrationStore(jedis));
} catch (URISyntaxException e) {
log.error("", e);
}
break;
default:
}
/** Set securityStore with registrationStore (if x509)*/
if (dtlsMode == X509) {
builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
@Override
protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
String expectedX509CommonName) {
return endpoint.startsWith(expectedX509CommonName);
}
}));
}
/** Set securityStore with new registrationStore */
builder.setSecurityStore(securityStore);
}
private void generatePSK_RPK() {
try {
/** Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) {
/** Get point values */
byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray());
/** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
/** Get keys */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
}
if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) {
/** Get point values */
byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray());
/** Create key specs */
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
/** Get keys */
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
}
} catch (GeneralSecurityException | IllegalArgumentException e) {
log.error("[{}] Failed generate Server PSK/RPK", e.getMessage());
throw new RuntimeException(e);
}
}
private void infoParamsPSK() {
log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]",
Hex.encodeHexString(this.privateKey.getEncoded()),
this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerPortPsk()));
}
private void infoParamsRPK() {
if (this.publicKey instanceof ECPublicKey) {
/** Get x coordinate */
byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);
/** Get Y coordinate */
byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);
/** Get Curves params */
String params = ((ECPublicKey) this.publicKey).getParams().toString();
log.info(
" \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]",
params, Hex.encodeHexString(x), Hex.encodeHexString(y),
Hex.encodeHexString(this.publicKey.getEncoded()),
Hex.encodeHexString(this.privateKey.getEncoded()));
} else {
throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported).");
}
}
private void setServerWithX509Cert(LeshanServerBuilder builder) {
try {
if (this.context.getCtxServer().getKeyStoreValue() != null) {
setBuilderX509(builder);
X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias());
if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates);
} else {
/** by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
} else {
/** by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.error("Unable to load X509 files for LWM2MServer");
}
} catch (KeyStoreException ex) {
log.error("[{}] Unable to load X509 files server", ex.getMessage());
}
}
private void setBuilderX509(LeshanServerBuilder builder) {
/**
* For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
* For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
*/
try {
X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias());
PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray());
builder.setPrivateKey(privateKey);
builder.setCertificateChain(new X509Certificate[]{serverCertificate});
this.infoParamsX509(serverCertificate, privateKey);
} catch (Exception ex) {
log.error("[{}] Unable to load KeyStore files server", ex.getMessage());
}
// /**
// * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
// * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
// */
// try {
// X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerPrivateS());
// this.privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray());
// if (this.privateKey != null && this.privateKey.getEncoded().length > 0) {
// builder.setPrivateKey(this.privateKey);
// }
// if (serverCertificate != null) {
// builder.setCertificateChain(new X509Certificate[]{serverCertificate});
// this.infoParamsX509(serverCertificate);
// }
// } catch (Exception ex) {
// log.error("[{}] Unable to load KeyStore files server", ex.getMessage());
// }
}
private void infoParamsX509(X509Certificate certificate, PrivateKey privateKey) {
try {
log.info("Server uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]",
Hex.encodeHexString(certificate.getEncoded()),
Hex.encodeHexString(privateKey.getEncoded()));
} catch (CertificateEncodingException e) {
log.error("", e);
}
}
} }

73
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java

@ -20,24 +20,32 @@ import org.eclipse.leshan.server.californium.LeshanServer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Component;
import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@Slf4j @Slf4j
@Service("LwM2MTransportServerInitializer") @Component("LwM2MTransportServerInitializer")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2MTransportServerInitializer { public class LwM2MTransportServerInitializer {
@Autowired
@Qualifier("LeshanServerCert")
private LeshanServer lhServerCert;
@Autowired @Autowired
@Qualifier("leshanServerNoSecPskRpk") private LwM2MTransportServiceImpl service;
private LeshanServer lhServerNoSecPskRpk;
@Autowired(required = false)
@Qualifier("leshanServerX509")
private LeshanServer lhServerX509;
@Autowired(required = false)
@Qualifier("leshanServerPsk")
private LeshanServer lhServerPsk;
@Autowired(required = false)
@Qualifier("leshanServerRpk")
private LeshanServer lhServerRpk;
@Autowired @Autowired
private LwM2MTransportContextServer context; private LwM2MTransportContextServer context;
@ -45,28 +53,47 @@ public class LwM2MTransportServerInitializer {
@PostConstruct @PostConstruct
public void init() { public void init() {
if (this.context.getCtxServer().getEnableGenPskRpk()) new LWM2MGenerationPSkRPkECC(); if (this.context.getCtxServer().getEnableGenPskRpk()) new LWM2MGenerationPSkRPkECC();
if (this.context.getCtxServer().isServerStartAll()) { if (this.context.getCtxServer().isServerStartPsk()) {
this.lhServerCert.start(); this.startLhServerPsk();
this.lhServerNoSecPskRpk.start(); }
if (this.context.getCtxServer().isServerStartRpk()) {
this.startLhServerRpk();
} }
else { if (this.context.getCtxServer().isServerStartX509()) {
if (this.context.getCtxServer().getServerDtlsMode() == LwM2MSecurityMode.X509.code) { this.startLhServerX509();
this.lhServerCert.start();
}
else {
this.lhServerNoSecPskRpk.start();
}
} }
} }
private void startLhServerPsk() {
this.lhServerPsk.start();
LwM2mServerListener lhServerPskListener = new LwM2mServerListener(this.lhServerPsk, service);
this.lhServerPsk.getRegistrationService().addListener(lhServerPskListener.registrationListener);
this.lhServerPsk.getPresenceService().addListener(lhServerPskListener.presenceListener);
this.lhServerPsk.getObservationService().addListener(lhServerPskListener.observationListener);
}
private void startLhServerRpk() {
this.lhServerRpk.start();
LwM2mServerListener lhServerRpkListener = new LwM2mServerListener(this.lhServerRpk, service);
this.lhServerRpk.getRegistrationService().addListener(lhServerRpkListener.registrationListener);
this.lhServerRpk.getPresenceService().addListener(lhServerRpkListener.presenceListener);
this.lhServerRpk.getObservationService().addListener(lhServerRpkListener.observationListener);
}
private void startLhServerX509() {
this.lhServerX509.start();
LwM2mServerListener lhServerCertListener = new LwM2mServerListener(this.lhServerX509, service);
this.lhServerX509.getRegistrationService().addListener(lhServerCertListener.registrationListener);
this.lhServerX509.getPresenceService().addListener(lhServerCertListener.presenceListener);
this.lhServerX509.getObservationService().addListener(lhServerCertListener.observationListener);
}
@PreDestroy @PreDestroy
public void shutdown() { public void shutdown() {
log.info("Stopping LwM2M transport Server!"); log.info("Stopping LwM2M transport Server!");
try { lhServerPsk.destroy();
lhServerCert.destroy(); lhServerRpk.destroy();
lhServerNoSecPskRpk.destroy(); lhServerX509.destroy();
} finally {
}
log.info("LwM2M transport Server stopped!"); log.info("LwM2M transport Server stopped!");
} }
} }

969
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java

@ -15,985 +15,42 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import com.google.gson.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient;
import org.thingsboard.server.transport.lwm2m.server.client.ModelObject;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore;
import javax.annotation.PostConstruct;
import java.util.Collection; import java.util.Collection;
import java.util.UUID;
import java.util.Random;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; public interface LwM2MTransportService {
import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.*;
@Slf4j void onRegistered(LeshanServer lwServer, Registration registration, Collection<Observation> previousObsersations);
@Service("LwM2MTransportService")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2MTransportService {
@Autowired void updatedReg(LeshanServer lwServer, Registration registration);
private LwM2MJsonAdaptor adaptor;
@Autowired void unReg(LeshanServer lwServer, Registration registration, Collection<Observation> observations);
private TransportService transportService;
@Autowired void onSleepingDev(Registration registration);
public LwM2MTransportContextServer context;
@Autowired void setCancelObservations(LeshanServer lwServer, Registration registration);
private LwM2MTransportRequest lwM2MTransportRequest;
@Autowired void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path);
LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore;
void onObservationResponse(Registration registration, String path, ReadResponse response);
@PostConstruct void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo);
public void init() {
context.getScheduler().scheduleAtFixedRate(() -> checkInactivityAndReportActivity(), new Random().nextInt((int) context.getCtxServer().getSessionReportTimeout()), context.getCtxServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS);
}
/** void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile);
* Start registration device
* Create session: Map<String <registrationId >, LwM2MClient>
* 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint)
* 1.1 When we initialize the registration, we register the session by endpoint.
* 1.2 If the server has incomplete requests (canceling the registration of the previous session),
* delete the previous session only by the previous registration.getId
* 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId
* 1.2 Remove from sessions Model by enpPoint
* Next -> Create new LwM2MClient for current session -> setModelClient...
*
* @param lwServer - LeshanServer
* @param registration - Registration LwM2M Client
* @param previousObsersations - may be null
*/
public void onRegistered(LeshanServer lwServer, Registration registration, Collection<Observation> previousObsersations) {
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(lwServer, registration);
if (lwM2MClient != null) {
lwM2MClient.setLwM2MTransportService(this);
lwM2MClient.setLwM2MTransportService(this);
lwM2MClient.setSessionUuid(UUID.randomUUID());
this.setLwM2MClient(lwServer, registration, lwM2MClient);
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId());
if (sessionInfo != null) {
lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
lwM2MClient.setProfileUuid(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
lwM2MClient.setDeviceName(sessionInfo.getDeviceName());
lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType());
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 registration", registration.getId());
} else {
log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo);
}
} else {
log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), lwM2MClient);
}
}
/** void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt);
* @param lwServer - LeshanServer
* @param registration - Registration LwM2M Client
*/
public void updatedReg(LeshanServer lwServer, Registration registration) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId());
if (sessionInfo != null) {
log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
} else {
log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo);
}
}
/** void doTrigger(LeshanServer lwServer, Registration registration, String path);
* @param registration - Registration LwM2M Client
* @param observations - All paths observations before unReg
* !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect
*/
public void unReg(Registration registration, Collection<Observation> observations) {
this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId());
this.closeClientSession(registration);
}
private void closeClientSession(Registration registration) { void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId());
if (sessionInfo != null) {
transportService.deregisterSession(sessionInfo);
this.doCloseSession(sessionInfo);
lwM2mInMemorySecurityStore.delRemoveSessionAndListener(registration.getId());
if (lwM2mInMemorySecurityStore.getProfiles().size() > 0) {
this.syncSessionsAndProfiles();
}
log.info("Client: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
} else {
log.error("Client: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo);
}
}
public void onSleepingDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
//TODO: associate endpointId with device information.
}
/**
* Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay,
* * if you need to do long time processing use a dedicated thread pool.
*
* @param registration
*/
public void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
//TODO: associate endpointId with device information.
}
/**
* This method is used to sync with sessions
* Removes a profile if not used in sessions
*/
private void syncSessionsAndProfiles() {
Map<UUID, AttrTelemetryObserveValue> profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
profilesClone.forEach((k, v) -> {
String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet()
.stream()
.filter(e -> e.getValue().getProfileUuid().equals(k))
.findFirst()
.map(Map.Entry::getKey) // return the key of the matching entry if found
.orElse("");
if (registrationId.isEmpty()) {
lwM2mInMemorySecurityStore.getProfiles().remove(k);
}
});
}
/**
* Create new LwM2MClient for current session -> setModelClient...
* #1 Add all ObjectLinks (instance) to control the process of executing requests to the client
* to get the client model with current values
* #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse
*
* @param lwServer - LeshanServer
* @param registration - Registration LwM2M Client
* @param lwM2MClient - object with All parameters off client
*/
private void setLwM2MClient(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) {
// #1
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
ResultIds pathIds = new ResultIds(url.getUrl());
if (pathIds.instanceId > -1 && pathIds.resourceId == -1) {
lwM2MClient.getPendingRequests().add(url.getUrl());
}
});
// #2
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
ResultIds pathIds = new ResultIds(url.getUrl());
if (pathIds.instanceId > -1 && pathIds.resourceId == -1) {
lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ,
ContentFormat.TLV.getName(), lwM2MClient, null, null, this.context.getCtxServer().getTimeout());
}
});
}
/**
* @param registrationId - Id of Registration LwM2M Client
* @return - sessionInfo after access connect client
*/
private SessionInfoProto getValidateSessionInfo(String registrationId) {
SessionInfoProto sessionInfo = null;
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registrationId);
if (lwM2MClient != null) {
ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse();
if (msg == null || msg.getDeviceInfo() == null) {
log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED);
this.closeClientSession(lwM2MClient.getRegistration());
} else {
sessionInfo = SessionInfoProto.newBuilder()
.setNodeId(this.context.getNodeId())
.setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits())
.setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.setDeviceName(msg.getDeviceInfo().getDeviceName())
.setDeviceType(msg.getDeviceInfo().getDeviceType())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
.build();
}
}
return sessionInfo;
}
/**
* Add attribute/telemetry information from Client and credentials/Profile to client model and start observe
* !!! if the resource has an observation, but no telemetry or attribute - the observation will not use
* #1 Client`s starting info to send to thingsboard
* #2 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection
* #3 Start observe
*
* @param lwServer - LeshanServer
* @param registration - Registration LwM2M Client
*/
public void updatesAndSentModelParameter(LeshanServer lwServer, Registration registration) {
// #1
// this.setParametersToModelClient(registration, modelClient, deviceProfile);
// #2
this.updateAttrTelemetry(registration, true, null);
// #3
this.onSentObserveToClient(lwServer, registration);
}
/**
* Sent Attribute and Telemetry to Thingsboard
* #1 - get AttrName/TelemetryName with value:
* #1.1 from Client
* #1.2 from LwM2MClient:
* -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile
* -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId)
* #2 - set Attribute/Telemetry
*
* @param registration - Registration LwM2M Client
*/
private void updateAttrTelemetry(Registration registration, boolean start, Set<String> paths) {
JsonObject attributes = new JsonObject();
JsonObject telemetrys = new JsonObject();
if (start) {
// #1.1
JsonObject attributeClient = this.getAttributeClient(registration);
if (attributeClient != null) {
attributeClient.entrySet().forEach(p -> {
attributes.add(p.getKey(), p.getValue());
});
}
}
// #1.2
CountDownLatch cancelLatch = new CountDownLatch(1);
this.getParametersFromProfile(attributes, telemetrys, registration, paths);
cancelLatch.countDown();
try {
cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("[{}] updateAttrTelemetry", e.toString());
}
if (attributes.getAsJsonObject().entrySet().size() > 0)
this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration.getId());
if (telemetrys.getAsJsonObject().entrySet().size() > 0)
this.updateParametersOnThingsboard(telemetrys, DEVICE_TELEMETRY_TOPIC, registration.getId());
}
/**
* get AttrName/TelemetryName with value from Client
*
* @param registration -
* @return - JsonObject, format: {name: value}}
*/
private JsonObject getAttributeClient(Registration registration) {
if (registration.getAdditionalRegistrationAttributes().size() > 0) {
JsonObject resNameValues = new JsonObject();
registration.getAdditionalRegistrationAttributes().entrySet().forEach(entry -> {
resNameValues.addProperty(entry.getKey(), entry.getValue());
});
return resNameValues;
}
return null;
}
/**
* @param attributes - new JsonObject
* @param telemetry - new JsonObject
* @param registration - Registration LwM2M Client
* result: add to JsonObject those resources to which the user is subscribed and they have a value
* if path==null add All resources else only one
* (attributes/telemetry): new {name(Attr/Telemetry):value}
*/
private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set<String> path) {
AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid());
attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> {
ResultIds pathIds = new ResultIds(p.getAsString().toString());
if (pathIds.getResourceId() > -1) {
if (path == null || path.contains(p.getAsString())) {
this.addParameters(pathIds, p.getAsString().toString(), attributes, registration);
}
}
});
attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> {
ResultIds pathIds = new ResultIds(p.getAsString().toString());
if (pathIds.getResourceId() > -1) {
if (path == null || path.contains(p.getAsString())) {
this.addParameters(pathIds, p.getAsString().toString(), telemetry, registration);
}
}
});
}
/**
* @param pathIds - path resource
* @param parameters - JsonObject attributes/telemetry
* @param registration - Registration LwM2M Client
*/
private void addParameters(ResultIds pathIds, String path, JsonObject parameters, Registration registration) {
ModelObject modelObject = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getModelObjects().get(pathIds.getObjectId());
JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()).getPostKeyNameProfile();
String resName = String.valueOf(names.get(path));
if (modelObject != null && resName != null && !resName.isEmpty()) {
String resValue = this.getResourceValue(modelObject, pathIds);
if (resValue != null) {
parameters.addProperty(resName, resValue);
}
}
}
/**
* @param modelObject - ModelObject of Client
* @param pathIds - path resource
* @return - value of Resource or null
*/
private String getResourceValue(ModelObject modelObject, ResultIds pathIds) {
String resValue = null;
if (modelObject.getInstances().get(pathIds.getInstanceId()) != null) {
LwM2mObjectInstance instance = modelObject.getInstances().get(pathIds.getInstanceId());
if (instance.getResource(pathIds.getResourceId()) != null) {
resValue = instance.getResource(pathIds.getResourceId()).getType() == OPAQUE ?
Hex.encodeHexString((byte[]) instance.getResource(pathIds.getResourceId()).getValue()).toLowerCase() :
(instance.getResource(pathIds.getResourceId()).isMultiInstances()) ?
instance.getResource(pathIds.getResourceId()).getValues().toString() :
instance.getResource(pathIds.getResourceId()).getValue().toString();
}
}
return resValue;
}
/**
* Prepare Sent to Thigsboard callback - Attribute or Telemetry
*
* @param msg - JsonArray: [{name: value}]
* @param topicName - Api Attribute or Telemetry
* @param registrationId - Id of Registration LwM2M Client
*/
public void updateParametersOnThingsboard(JsonElement msg, String topicName, String registrationId) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId);
if (sessionInfo != null) {
try {
if (topicName.equals(LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) {
PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(-1, postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, call);
} else if (topicName.equals(LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC)) {
PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(-1, postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(-1, call));
}
} catch (AdaptorException e) {
log.error("[{}] Failed to process publish msg [{}]", topicName, e);
log.info("[{}] Closing current session due to invalid publish", topicName);
}
} else {
log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registrationId, sessionInfo);
}
}
/**
* Sent to Thingsboard Attribute || Telemetry
*
* @param msgId - always == -1
* @param msg - JsonObject: [{name: value}]
* @return - dummy
*/
private <T> TransportServiceCallback<Void> getPubAckCallbackSentAttrTelemetry(final int msgId, final T msg) {
return new TransportServiceCallback<Void>() {
@Override
public void onSuccess(Void dummy) {
log.trace("Success to publish msg: {}, dummy: {}", msg, dummy);
}
@Override
public void onError(Throwable e) {
log.trace("[{}] Failed to publish msg: {}", msg, e);
}
};
}
/**
* Start observe
* #1 - Analyze:
* #1.1 path in observe == (attribute or telemetry)
* #1.2 recourseValue notNull
* #2 Analyze after sent request (response):
* #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation
* #2.2 Next: ObservationListener.onResponse *
*
* @param lwServer - LeshanServer
* @param registration - Registration LwM2M Client
*/
private void onSentObserveToClient(LeshanServer lwServer, Registration registration) {
if (lwServer.getObservationService().getObservations(registration).size() > 0) {
this.setCancelObservations(lwServer, registration);
}
UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid();
AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid);
attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> {
// #1.1
String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ?
p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ?
p.getAsString().toString() : null;
if (target != null) {
// #1.2
ResultIds pathIds = new ResultIds(target);
ModelObject modelObject = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getModelObjects().get(pathIds.getObjectId());
// #2
if (modelObject != null) {
if (getResourceValue(modelObject, pathIds) != null) {
lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE,
null, null, null, null, this.context.getCtxServer().getTimeout());
}
}
}
});
}
public void setCancelObservations(LeshanServer lwServer, Registration registration) {
if (registration != null) {
Set<Observation> observations = lwServer.getObservationService().getObservations(registration);
observations.forEach(observation -> {
this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString());
});
}
}
/**
* lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout());
* At server side this will not remove the observation from the observation store, to do it you need to use
* {@code ObservationService#cancelObservation()}
*/
public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) {
CountDownLatch cancelLatch = new CountDownLatch(1);
lwServer.getObservationService().cancelObservations(registration, path);
cancelLatch.countDown();
try {
cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
}
/**
* @param parameters - JsonArray postAttributeProfile/postTelemetryProfile
* @param path - recourse from postObserveProfile
* @return rez - true if path observe is in attribute/telemetry
*/
private boolean getValidateObserve(JsonElement parameters, String path) {
AtomicBoolean rez = new AtomicBoolean(false);
if (parameters.isJsonArray()) {
parameters.getAsJsonArray().forEach(p -> {
if (p.getAsString().toString().equals(path)) rez.set(true);
}
);
} else if (parameters.isJsonObject()) {
rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString())
.filter(path::equals).findAny().orElse(null) != null);
}
return rez.get();
}
/**
* Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource
*
* @param registration - Registration LwM2M Client
* @param path - observe
* @param response - observe
*/
@SneakyThrows
public void onObservationResponse(Registration registration, String path, ReadResponse response) {
if (response.getContent() != null) {
if (response.getContent() instanceof LwM2mObject) {
LwM2mObject content = (LwM2mObject) response.getContent();
String target = "/" + content.getId();
} else if (response.getContent() instanceof LwM2mObjectInstance) {
LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent();
} else if (response.getContent() instanceof LwM2mSingleResource) {
LwM2mSingleResource content = (LwM2mSingleResource) response.getContent();
this.onObservationSetResourcesValue(registration, content.getValue(), null, path);
} else if (response.getContent() instanceof LwM2mMultipleResource) {
LwM2mSingleResource content = (LwM2mSingleResource) response.getContent();
this.onObservationSetResourcesValue(registration, null, content.getValues(), path);
}
}
}
/**
* Sending observe value of resources to thingsboard
* #1 Return old Resource from ModelObject
* #2 Create new Resource with value from observation
* #3 Create new Resources from old Resources
* #4 Update new Resources (replace old Resource on new Resource)
* #5 Remove old Instance from modelClient
* #6 Create new Instance with new Resources values
* #7 Update modelClient.getModelObjects(idObject) (replace old Instance on new Instance)
*
* @param registration - Registration LwM2M Client
* @param value - LwM2mSingleResource response.getContent()
* @param values - LwM2mSingleResource response.getContent()
* @param path - resource
*/
private void onObservationSetResourcesValue(Registration registration, Object value, Map<Integer, ?> values, String path) {
ResultIds resultIds = new ResultIds(path);
// #1
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId());
ModelObject modelObject = lwM2MClient.getModelObjects().get(resultIds.getObjectId());
Map<Integer, LwM2mObjectInstance> instancesModelObject = modelObject.getInstances();
LwM2mObjectInstance instanceOld = (instancesModelObject.get(resultIds.instanceId) != null) ? instancesModelObject.get(resultIds.instanceId) : null;
Map<Integer, LwM2mResource> resourcesOld = (instanceOld != null) ? instanceOld.getResources() : null;
LwM2mResource resourceOld = (resourcesOld != null && resourcesOld.get(resultIds.getResourceId()) != null) ? resourcesOld.get(resultIds.getResourceId()) : null;
// #2
LwM2mResource resourceNew;
if (resourceOld.isMultiInstances()) {
resourceNew = LwM2mMultipleResource.newResource(resultIds.getResourceId(), values, resourceOld.getType());
} else {
resourceNew = LwM2mSingleResource.newResource(resultIds.getResourceId(), value, resourceOld.getType());
}
//#3
Map<Integer, LwM2mResource> resourcesNew = new HashMap<>(resourcesOld);
// #4
resourcesNew.remove(resourceOld);
// #5
resourcesNew.put(resultIds.getResourceId(), resourceNew);
// #6
LwM2mObjectInstance instanceNew = new LwM2mObjectInstance(resultIds.instanceId, resourcesNew.values());
// #7
CountDownLatch respLatch = new CountDownLatch(1);
lwM2MClient.getModelObjects().get(resultIds.getObjectId()).removeInstance(resultIds.instanceId);
instancesModelObject.put(resultIds.instanceId, instanceNew);
respLatch.countDown();
try {
respLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
}
Set<String> paths = new HashSet<String>();
paths.add(path);
this.updateAttrTelemetry(registration, false, paths);
}
/**
* @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...)
* config attr/telemetry... in profile
*/
public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) {
log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList());
}
/**
* Update - sent request in change value resources in Client (path to resources from profile by keyName)
* Only fo resources W
* Delete - nothing
*
* @param msg -
* @param sessionInfo -
*/
public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, SessionInfoProto sessionInfo) {
if (msg.getSharedUpdatedCount() > 0) {
JsonElement el = JsonConverter.toJson(msg);
el.getAsJsonObject().entrySet().forEach(de -> {
String profilePath = lwM2mInMemorySecurityStore.getProfiles().get(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()))
.getPostKeyNameProfile().getAsJsonObject().entrySet().stream()
.filter(e -> e.getValue().getAsString().equals(de.getKey())).findFirst().map(Map.Entry::getKey)
.orElse("");
String path = !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdate(sessionInfo, de.getKey());
if (path != null) {
ResultIds resultIds = new ResultIds(path);
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue();
ResourceModel.Operations operations = lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getObjectModel().resources.get(resultIds.getResourceId()).operations;
String value = ((JsonPrimitive) de.getValue()).getAsString();
if (operations.isWritable()) {
lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE,
ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout());
log.info("[{}] path onAttributeUpdate", path);
}
else {
log.error(LOG_LW2M_ERROR + ": Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value);
String logMsg = String.format(LOG_LW2M_ERROR + " attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value);
this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId());
}
}
});
} else if (msg.getSharedDeletedCount() > 0) {
log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
}
}
private String getPathAttributeUpdate(SessionInfoProto sessionInfo, String keyName) {
try {
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue();
Predicate<Map.Entry<Integer, ResourceModel>> predicateRes = res -> keyName.equals(splitCamelCaseString(res.getValue().name));
Predicate<Map.Entry<Integer, ModelObject>> predicateObj = (obj -> {
return obj.getValue().getObjectModel().resources.entrySet().stream().filter(predicateRes).findFirst().isPresent();
});
Stream<Map.Entry<Integer, ModelObject>> objectStream = lwM2MClient.getModelObjects().entrySet().stream().filter(predicateObj);
Predicate<Map.Entry<Integer, ResourceModel>> predicateResFinal = (objectStream.count() > 0) ? predicateRes : res -> keyName.equals(res.getValue().name);
Predicate<Map.Entry<Integer, ModelObject>> predicateObjFinal = (obj -> {
return obj.getValue().getObjectModel().resources.entrySet().stream().filter(predicateResFinal).findFirst().isPresent();
});
Map.Entry<Integer, ModelObject> object = lwM2MClient.getModelObjects().entrySet().stream().filter(predicateObjFinal).findFirst().get();
ModelObject modelObject = object.getValue();
LwM2mObjectInstance instance = modelObject.getInstances().entrySet().stream().findFirst().get().getValue();
ResourceModel resource = modelObject.getObjectModel().resources.entrySet().stream().filter(predicateResFinal).findFirst().get().getValue();
return new LwM2mPath(object.getKey(), instance.getId(), resource.id).toString();
} catch (NoSuchElementException e) {
log.error("[{}] keyName [{}]", keyName, e.toString());
return null;
}
}
public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request) {
ResultIds resultIds = new ResultIds(path);
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId());
LwM2mResource resource = lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getInstances().get(resultIds.getInstanceId()).getResource(resultIds.getResourceId());
if (resource.isMultiInstances()) {
this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path);
} else {
this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path);
}
}
/**
* @param sessionInfo -
* @param deviceProfile -
*/
public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet()
.stream()
.filter(e -> e.getValue().getDeviceUuid().equals(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())))
.findFirst()
.map(Map.Entry::getKey)
.orElse("");
if (!registrationId.isEmpty()) {
this.onDeviceUpdateChangeProfile(registrationId, deviceProfile);
}
}
/**
* @param sessionInfo -
* @param device -
* @param deviceProfileOpt -
*/
public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
Optional<String> registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream()
.filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid()))
.map(Map.Entry::getKey)
.findFirst();
registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt));
}
/**
* Update parameters device in LwM2MClient
* If new deviceProfile != old deviceProfile => update deviceProfile
*
* @param registrationId -
* @param device -
*/
private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional<DeviceProfile> deviceProfileOpt) {
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId);
lwM2MClient.setDeviceName(device.getName());
if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) {
deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationId, deviceProfile));
}
}
/**
* #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName)
* #2 Update in lwM2MClient: ...Profile
* #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName)
* #3.1 Attribute isChange (add&del)
* #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 ti 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
* #5.3 Observe.del
* -- different between newObserve and oldObserve: sent Request cancel observe to client
*
* @param registrationId -
* @param deviceProfile -
*/
public void onDeviceUpdateChangeProfile(String registrationId, DeviceProfile deviceProfile) {
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registrationId);
AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid());
if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) {
LeshanServer lwServer = lwM2MClient.getLwServer();
Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId);
// #1
JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile();
Set<String> attributeSetOld = new Gson().fromJson(attributeOld, Set.class);
JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile();
Set<String> telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class);
JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile();
JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile();
AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId());
JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile();
Set<String> attributeSetNew = new Gson().fromJson(attributeNew, Set.class);
JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile();
Set<String> telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class);
JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile();
JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile();
// #2
lwM2MClient.setDeviceProfileName(deviceProfile.getName());
lwM2MClient.setProfileUuid(deviceProfile.getUuidId());
// #3
ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters();
// #3.1
if (!attributeOld.equals(attributeNew)) {
ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew);
sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
}
// #3.2
if (!attributeOld.equals(attributeNew)) {
ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew);
sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
}
// #3.3
if (!keyNameOld.equals(keyNameNew)) {
ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class),
new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class));
sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
}
// #4.1 add
if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
// update value in Resources
this.updateResourceValueObserve(lwServer, registration, lwM2MClient, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ);
// sent attr/telemetry to tingsboard for new path
this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd());
}
// #4.2 del
if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) {
ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel());
sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel());
}
// #5.1
if (!observeOld.equals(observeNew)) {
Set<String> observeSetOld = new Gson().fromJson(observeOld, Set.class);
Set<String> observeSetNew = new Gson().fromJson(observeNew, Set.class);
//#5.2 add
// path Attr/Telemetry includes newObserve
attributeSetOld.addAll(telemetrySetOld);
ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe
attributeSetNew.addAll(telemetrySetNew);
ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe
// does not include oldObserve
ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd());
// sent Request observe to Client
this.updateResourceValueObserve(lwServer, registration, lwM2MClient, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE);
// 5.3 del
// sent Request cancel observe to Client
this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel());
}
}
}
/**
* Compare old list with new list after change AttrTelemetryObserve in config Profile
*
* @param parametersOld -
* @param parametersNew -
* @return ResultsAnalyzerParameters: add && new
*/
private ResultsAnalyzerParameters getAnalyzerParameters(Set<String> parametersOld, Set<String> parametersNew) {
ResultsAnalyzerParameters analyzerParameters = null;
if (!parametersOld.equals(parametersNew)) {
analyzerParameters = new ResultsAnalyzerParameters();
analyzerParameters.setPathPostParametersAdd(parametersNew
.stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet()));
analyzerParameters.setPathPostParametersDel(parametersOld
.stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet()));
}
return analyzerParameters;
}
private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap<String, String> keyNameOld, ConcurrentMap<String, String> keyNameNew) {
ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
Set<String> paths = keyNameNew.entrySet()
.stream()
.filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey())))
.collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())).keySet();
analyzerParameters.setPathPostParametersAdd(paths);
return analyzerParameters;
}
private ResultsAnalyzerParameters getAnalyzerParametersIn(Set<String> parametersObserve, Set<String> parameters) {
ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
analyzerParameters.setPathPostParametersAdd(parametersObserve
.stream().filter(p -> parameters.contains(p)).collect(Collectors.toSet()));
return analyzerParameters;
}
/**
* Update Resource value after change RezAttrTelemetry in config Profile
* sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue()
*
* @param lwServer - LeshanServer
* @param registration - Registration LwM2M Client
* @param lwM2MClient - object with All parameters off client
* @param targets - path Resources == [ "/2/0/0", "/2/0/1"]
*/
private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient, Set<String> targets, String typeOper) {
targets.stream().forEach(target -> {
ResultIds pathIds = new ResultIds(target);
if (pathIds.resourceId >= 0 && lwM2MClient.getModelObjects().get(pathIds.getObjectId())
.getInstances().get(pathIds.getInstanceId()).getResource(pathIds.getResourceId()).getValue() != null) {
if (GET_TYPE_OPER_READ.equals(typeOper)) {
lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper,
ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout());
} else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper,
null, null, null, null, this.context.getCtxServer().getTimeout());
}
}
});
}
private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set<String> paramAnallyzer) {
LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId());
paramAnallyzer.forEach(p -> {
if (this.getResourceValue(lwM2MClient, p) != null) {
this.setCancelObservationRecourse(lwServer, registration, p);
}
}
);
}
private ResourceValue getResourceValue(LwM2MClient lwM2MClient, String path) {
ResourceValue resourceValue = null;
ResultIds pathIds = new ResultIds(path);
if (pathIds.getResourceId() > -1) {
LwM2mResource resource = lwM2MClient.getModelObjects().get(pathIds.getObjectId()).getInstances().get(pathIds.getInstanceId()).getResource(pathIds.getResourceId());
if (resource.isMultiInstances()) {
Map<Integer, ?> values = resource.getValues();
if (resource.getValues().size() > 0) {
resourceValue = new ResourceValue();
resourceValue.setMultiInstances(resource.isMultiInstances());
resourceValue.setValues(resource.getValues());
}
} else {
if (resource.getValue() != null) {
resourceValue = new ResourceValue();
resourceValue.setMultiInstances(resource.isMultiInstances());
resourceValue.setValue(resource.getValue());
}
}
}
return resourceValue;
}
/**
* Trigger Server path = "/1/0/8"
*
* Trigger bootStrap path = "/1/0/9" - have to implemented on client
*/
public void doTrigger(LeshanServer lwServer, Registration registration, String path) {
lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE,
ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout());
}
/**
* Session device in thingsboard is closed
*
* @param sessionInfo - lwm2m client
*/
private void doCloseSession(SessionInfoProto sessionInfo) {
TransportProtos.SessionEvent event = SessionEvent.CLOSED;
TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder()
.setSessionType(TransportProtos.SessionType.ASYNC)
.setEvent(event).build();
transportService.process(sessionInfo, msg, null);
}
/**
* Deregister session in transport
* @param sessionInfo - lwm2m client
*/
private void doDisconnect(SessionInfoProto sessionInfo) {
transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null);
transportService.deregisterSession(sessionInfo);
}
private void checkInactivityAndReportActivity() {
lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> transportService.reportActivity(this.getValidateSessionInfo(key)));
}
public void sentLogsToThingsboard(String msg, String registrationId) {
if (msg != null) {
JsonObject telemetrys = new JsonObject();
telemetrys.addProperty(LOG_LW2M_TELEMETRY, msg);
this.updateParametersOnThingsboard(telemetrys, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registrationId);
}
}
} }

1159
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java

File diff suppressed because it is too large

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

@ -29,10 +29,11 @@ import java.util.Collection;
@Slf4j @Slf4j
public class LwM2mServerListener { public class LwM2mServerListener {
private LeshanServer lhServer;
private LwM2MTransportService service;
public LwM2mServerListener(LeshanServer lhServer, LwM2MTransportService service) { private final LeshanServer lhServer;
private final LwM2MTransportServiceImpl service;
public LwM2mServerListener(LeshanServer lhServer, LwM2MTransportServiceImpl service) {
this.lhServer = lhServer; this.lhServer = lhServer;
this.service = service; this.service = service;
} }
@ -43,9 +44,8 @@ public class LwM2mServerListener {
*/ */
@Override @Override
public void registered(Registration registration, Registration previousReg, public void registered(Registration registration, Registration previousReg,
Collection<Observation> previousObsersations) { Collection<Observation> previousObservations) {
service.onRegistered(lhServer, registration, previousObservations);
service.onRegistered(lhServer, registration, previousObsersations);
} }
/** /**
@ -63,7 +63,7 @@ public class LwM2mServerListener {
@Override @Override
public void unregistered(Registration registration, Collection<Observation> observations, boolean expired, public void unregistered(Registration registration, Collection<Observation> observations, boolean expired,
Registration newReg) { Registration newReg) {
service.unReg(registration, observations); service.unReg(lhServer, registration, observations);
} }
}; };
@ -71,11 +71,13 @@ public class LwM2mServerListener {
public final PresenceListener presenceListener = new PresenceListener() { public final PresenceListener presenceListener = new PresenceListener() {
@Override @Override
public void onSleeping(Registration registration) { public void onSleeping(Registration registration) {
log.info("onSleeping");
service.onSleepingDev(registration); service.onSleepingDev(registration);
} }
@Override @Override
public void onAwake(Registration registration) { public void onAwake(Registration registration) {
log.info("onAwake");
service.onAwakeDev(registration); service.onAwakeDev(registration);
} }
}; };
@ -92,8 +94,9 @@ public class LwM2mServerListener {
if (registration != null) { if (registration != null) {
try { try {
service.onObservationResponse(registration, observation.getPath().toString(), response); service.onObservationResponse(registration, observation.getPath().toString(), response);
} catch (java.lang.NullPointerException e) { } catch (Exception e) {
log.error(e.toString()); log.error("[{}] onResponse", e.toString());
} }
} }
} }
@ -108,4 +111,5 @@ public class LwM2mServerListener {
log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint());
} }
}; };
} }

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

@ -1,38 +0,0 @@
/**
* Copyright © 2016-2020 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;
import lombok.Builder;
import lombok.Data;
@Data
public class ResultIds {
@Builder.Default
int objectId = -1;
@Builder.Default
int instanceId = -1;
@Builder.Default
int resourceId = -1;
public ResultIds (String path) {
String[] paths = path.split("/");
if (paths != null && paths.length > 1) {
this.objectId = (paths.length > 1) ? Integer.parseInt(paths[1]) : this.objectId;
this.instanceId = (paths.length > 2) ? Integer.parseInt(paths[2]) : this.instanceId;
this.resourceId = (paths.length > 3) ? Integer.parseInt(paths[3]) : this.resourceId;
}
}
}

39
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java

@ -24,6 +24,12 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
@Slf4j @Slf4j
@Component("LwM2MJsonAdaptor") @Component("LwM2MJsonAdaptor")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
@ -46,4 +52,37 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor {
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@Override
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List<String> clientKeys, List<String> sharedKeys) throws AdaptorException {
return processGetAttributeRequestMsg(clientKeys, sharedKeys);
}
protected TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(List<String> clientKeys, List<String> sharedKeys) throws AdaptorException {
try {
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
Random random = new Random();
result.setRequestId(random.nextInt());
if (clientKeys != null) {
result.addAllClientAttributeNames(clientKeys);
}
if (sharedKeys != null) {
result.addAllSharedAttributeNames(sharedKeys);
}
return result.build();
} catch (RuntimeException e) {
log.warn("Failed to decode get attributes request", e);
throw new AdaptorException(e);
}
}
private Set<String> toStringSet(JsonElement requestBody, String name) {
JsonElement element = requestBody.getAsJsonObject().get(name);
if (element != null) {
return new HashSet<>(Arrays.asList(element.getAsString().split(",")));
} else {
return null;
}
}
} }

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

@ -19,9 +19,13 @@ import com.google.gson.JsonElement;
import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.List;
public interface LwM2MTransportAdaptor { public interface LwM2MTransportAdaptor {
TransportProtos.PostTelemetryMsg convertToPostTelemetry(JsonElement jsonElement) throws AdaptorException; TransportProtos.PostTelemetryMsg convertToPostTelemetry(JsonElement jsonElement) throws AdaptorException;
TransportProtos.PostAttributeMsg convertToPostAttributes(JsonElement jsonElement) throws AdaptorException; TransportProtos.PostAttributeMsg convertToPostAttributes(JsonElement jsonElement) throws AdaptorException;
TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List<String> clientKeys, List<String> sharedKeys) throws AdaptorException;
} }

70
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java

@ -18,20 +18,22 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportService; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportServiceImpl;
import org.thingsboard.server.transport.lwm2m.server.ResultIds; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.UUID;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Collection; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -47,36 +49,43 @@ public class LwM2MClient implements Cloneable {
private UUID sessionUuid; private UUID sessionUuid;
private UUID profileUuid; private UUID profileUuid;
private LeshanServer lwServer; private LeshanServer lwServer;
private LwM2MTransportService lwM2MTransportService; private LwM2MTransportServiceImpl lwM2MTransportServiceImpl;
private Registration registration; private Registration registration;
private ValidateDeviceCredentialsResponseMsg credentialsResponse; private ValidateDeviceCredentialsResponseMsg credentialsResponse;
private Map<String, String> attributes; private Map<String, String> attributes;
private Map<Integer, ModelObject> modelObjects; private Map<String, ResourceValue> resources;
private Set<String> pendingRequests; private Set<String> pendingRequests;
private Map<String, TransportProtos.TsKvProto> delayedRequests;
private Set<Integer> delayedRequestsId;
private Map<String, LwM2mResponse> responses; private Map<String, LwM2mResponse> responses;
private final LwM2mValueConverterImpl converter;
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
return super.clone(); return super.clone();
} }
public LwM2MClient(String endPoint, String identity, SecurityInfo info, ValidateDeviceCredentialsResponseMsg credentialsResponse, Map<String, String> attributes, Map<Integer, ModelObject> modelObjects, UUID profileUuid) { public LwM2MClient(String endPoint, String identity, SecurityInfo info, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileUuid) {
this.endPoint = endPoint; this.endPoint = endPoint;
this.identity = identity; this.identity = identity;
this.info = info; this.info = info;
this.credentialsResponse = credentialsResponse; this.credentialsResponse = credentialsResponse;
this.attributes = (attributes != null && attributes.size() > 0) ? attributes : new ConcurrentHashMap<String, String>(); this.attributes = new ConcurrentHashMap<>();
this.modelObjects = (modelObjects != null && modelObjects.size() > 0) ? modelObjects : new ConcurrentHashMap<Integer, ModelObject>();
this.pendingRequests = ConcurrentHashMap.newKeySet(); this.pendingRequests = ConcurrentHashMap.newKeySet();
this.delayedRequests = new ConcurrentHashMap<>();
this.resources = new ConcurrentHashMap<>();
this.delayedRequestsId = ConcurrentHashMap.newKeySet();
this.profileUuid = profileUuid; this.profileUuid = profileUuid;
/** /**
* Key <objectId>, response<Value -> instance -> resources: value...> * Key <objectId>, response<Value -> instance -> resources: value...>
*/ */
this.responses = new ConcurrentHashMap<>(); this.responses = new ConcurrentHashMap<>();
this.converter = LwM2mValueConverterImpl.getInstance();
} }
/** /**
* Fill with data -> Model client * Fill with data -> Model client
* @param path - *
* @param path -
* @param response - * @param response -
*/ */
public void onSuccessHandler(String path, LwM2mResponse response) { public void onSuccessHandler(String path, LwM2mResponse response) {
@ -84,24 +93,41 @@ public class LwM2MClient implements Cloneable {
this.pendingRequests.remove(path); this.pendingRequests.remove(path);
if (this.pendingRequests.size() == 0) { if (this.pendingRequests.size() == 0) {
this.initValue(); this.initValue();
this.lwM2MTransportService.updatesAndSentModelParameter(this.lwServer, this.registration); this.lwM2MTransportServiceImpl.putDelayedUpdateResourcesThingsboard(this);
} }
} }
private void initValue() { private void initValue() {
this.responses.forEach((key, resp) -> { this.responses.forEach((key, resp) -> {
ResultIds pathIds = new ResultIds(key); LwM2mPath pathIds = new LwM2mPath(key);
if (pathIds.getObjectId() > -1) { if (pathIds.isObject() || pathIds.isObjectInstance() || pathIds.isResource()) {
ObjectModel objectModel = ((Collection<ObjectModel>) this.lwServer.getModelProvider().getObjectModel(registration).getObjectModels()).stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0); ObjectModel objectModel = this.lwServer.getModelProvider().getObjectModel(registration).getObjectModels().stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0);
if (this.modelObjects.get(pathIds.getObjectId()) != null) { if (objectModel != null) {
this.modelObjects.get(pathIds.getObjectId()).getInstances().put(((ReadResponse) resp).getContent().getId(), (LwM2mObjectInstance) ((ReadResponse) resp).getContent()); ((LwM2mObjectInstance)((ReadResponse)resp).getContent()).getResources().forEach((k, v) -> {
} else { String rez = pathIds.toString() + "/" + k;
Map<Integer, LwM2mObjectInstance> instances = new ConcurrentHashMap<>(); if (((LwM2mObjectInstance) ((ReadResponse) resp).getContent()).getResource(k) instanceof LwM2mMultipleResource){
instances.put(((ReadResponse) resp).getContent().getId(), (LwM2mObjectInstance) ((ReadResponse) resp).getContent()); this.resources.put(rez, new ResourceValue(v.getValues(), null, true));
ModelObject modelObject = new ModelObject(objectModel, instances); }
this.modelObjects.put(pathIds.getObjectId(), modelObject); else {
this.resources.put(rez, new ResourceValue(null, v.getValue(), false));
}
});
} }
} }
}); });
if (this.responses.size() == 0) this.responses = new ConcurrentHashMap<>();
} }
/**
* if path != null
* @param path
*/
public void onSuccessOrErrorDelayedRequests(String path) {
if (path != null) this.delayedRequests.remove(path);
if (this.delayedRequests.size() == 0 && this.getDelayedRequestsId().size() == 0) {
this.lwM2MTransportServiceImpl.updatesAndSentModelParameter(this);
}
}
} }

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java

@ -21,7 +21,7 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import java.util.Map; import java.util.Map;
@Data @Data
public class ModelObject { public class ModelObject implements Cloneable {
/** /**
* model one on all instance * model one on all instance
* for each instance only id resource with parameters of resources (observe, attr, telemetry) * for each instance only id resource with parameters of resources (observe, attr, telemetry)
@ -38,4 +38,8 @@ public class ModelObject {
LwM2mObjectInstance instance = this.instances.get(id); LwM2mObjectInstance instance = this.instances.get(id);
return this.instances.remove(id, instance); return this.instances.remove(id, instance);
} }
public ModelObject clone() throws CloneNotSupportedException {
return (ModelObject) super.clone();
}
} }

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

@ -16,11 +16,23 @@
package org.thingsboard.server.transport.lwm2m.server.client; package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@Data @Data
public class ResourceValue { public class ResourceValue {
Map<Integer, ?> values; Map<Integer, ?> values;
Object value; Object value;
boolean multiInstances; boolean multiInstances;
public ResourceValue(Map<Integer, ?> values, Object value, boolean multiInstances) {
this.values = values;
this.value = value;
this.multiInstances = multiInstances;
}
public Object getResourceValue() {
return this.multiInstances ? this.values : this.value;
}
} }

234
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java

@ -1,234 +0,0 @@
/**
* Copyright © 2016-2020 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.secure;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.redis.RedisRegistrationStore;
import org.eclipse.leshan.server.redis.RedisSecurityStore;
import org.eclipse.leshan.server.security.DefaultAuthorizer;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.Pool;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.GeneralSecurityException;
import java.security.KeyStoreException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.*;
import java.util.Arrays;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*;
@Slf4j
@Data
public class LwM2MSetSecurityStoreServer {
private KeyStore keyStore;
private X509Certificate certificate;
private PublicKey publicKey;
private PrivateKey privateKey;
private LwM2MTransportContextServer context;
private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore;
private LeshanServerBuilder builder;
EditableSecurityStore securityStore;
public LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MTransportContextServer context, LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore, LwM2MSecurityMode dtlsMode) {
this.builder = builder;
this.context = context;
this.lwM2mInMemorySecurityStore = lwM2mInMemorySecurityStore;
/** Set securityStore with new registrationStore */
switch (dtlsMode) {
/** Use PSK only */
case PSK:
generatePSK_RPK();
if (this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPrivateKey(this.privateKey);
builder.setPublicKey(null);
getParamsPSK();
}
break;
/** Use RPK only */
case RPK:
generatePSK_RPK();
if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPublicKey(this.publicKey);
builder.setPrivateKey(this.privateKey);
getParamsRPK();
}
break;
/** Use x509 only */
case X509:
setServerWithX509Cert();
break;
/** No security */
case NO_SEC:
builder.setTrustedCertificates(new X509Certificate[0]);
break;
/** Use x509 with EST */
case X509_EST:
// TODO support sentinel pool and make pool configurable
break;
case REDIS:
/**
* Set securityStore with new registrationStore (if use redis store)
* Connect to redis
*/
Pool<Jedis> jedis = null;
try {
jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl()));
securityStore = new RedisSecurityStore(jedis);
builder.setRegistrationStore(new RedisRegistrationStore(jedis));
} catch (URISyntaxException e) {
e.printStackTrace();
}
break;
default:
}
/** Set securityStore with new registrationStore (if not redis)*/
if (dtlsMode.code < REDIS.code) {
securityStore = lwM2mInMemorySecurityStore;
if (dtlsMode == X509) {
builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
@Override
protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
String expectedX509CommonName) {
return endpoint.startsWith(expectedX509CommonName);
}
}));
}
}
/** Set securityStore with new registrationStore */
builder.setSecurityStore(securityStore);
}
private void generatePSK_RPK() {
try {
/** Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) {
/** Get point values */
byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray());
/** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
/** Get keys */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
}
if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) {
/** Get point values */
byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray());
/** Create key specs */
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
/** Get keys */
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
}
} catch (GeneralSecurityException | IllegalArgumentException e) {
log.error("[{}] Failed generate Server PSK/RPK", e.getMessage());
throw new RuntimeException(e);
}
}
private void setServerWithX509Cert() {
try {
if (this.context.getCtxServer().getKeyStoreValue() != null) {
setBuilderX509();
X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias());
if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates);
} else {
/** by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
}
else {
/** by default trust all */
this.builder.setTrustedCertificates(new X509Certificate[0]);
log.error("Unable to load X509 files for LWM2MServer");
}
} catch (KeyStoreException ex) {
log.error("[{}] Unable to load X509 files server", ex.getMessage());
}
}
private void setBuilderX509() {
/**
* For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
* For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
*/
try {
X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias());
PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray());
this.builder.setPrivateKey(privateKey);
this.builder.setCertificateChain(new X509Certificate[]{serverCertificate});
} catch (Exception ex) {
log.error("[{}] Unable to load KeyStore files server", ex.getMessage());
}
}
private void getParamsPSK() {
log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]",
Hex.encodeHexString(this.privateKey.getEncoded()),
this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerSecurePort()));
}
private void getParamsRPK() {
if (this.publicKey instanceof ECPublicKey) {
/** Get x coordinate */
byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);
/** Get Y coordinate */
byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);
/** Get Curves params */
String params = ((ECPublicKey) this.publicKey).getParams().toString();
log.info(
" \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]",
params, Hex.encodeHexString(x), Hex.encodeHexString(y),
Hex.encodeHexString(this.publicKey.getEncoded()),
Hex.encodeHexString(this.privateKey.getEncoded()));
} else {
throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported).");
}
}
}

136
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.secure;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.InMemorySecurityStore; import org.eclipse.leshan.server.security.InMemorySecurityStore;
@ -24,61 +25,75 @@ import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.server.security.SecurityStoreListener; import org.eclipse.leshan.server.security.SecurityStoreListener;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo; 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.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler;
import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient;
import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.util.Map;
import java.util.UUID;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
@Slf4j @Slf4j
@Component("LwM2mInMemorySecurityStore") @Service("LwM2mInMemorySecurityStore")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
private static final boolean INFOS_ARE_COMPROMISED = false;
// lock for the two maps // lock for the two maps
protected final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
protected final Lock readLock = readWriteLock.readLock(); private final Lock readLock = readWriteLock.readLock();
protected final Lock writeLock = readWriteLock.writeLock(); private final Lock writeLock = readWriteLock.writeLock();
private final boolean infosAreCompromised = false; private final Map<String /** registrationId */, LwM2MClient> sessions = new ConcurrentHashMap<>();
protected Map<String /** registrationId */, LwM2MClient> sessions = new ConcurrentHashMap<>(); private Map<UUID /** profileUUid */, AttrTelemetryObserveValue> profiles = new ConcurrentHashMap<>();
protected Map<UUID /** profileUUid */, AttrTelemetryObserveValue> profiles = new ConcurrentHashMap<>();
private SecurityStoreListener listener; private SecurityStoreListener listener;
@Autowired @Autowired
LwM2MGetSecurityInfo lwM2MGetSecurityInfo; LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator;
/**
* Start after DefaultAuthorizer or LwM2mPskStore
* @param endPoint -
* @return SecurityInfo
*/
@Override @Override
public SecurityInfo getByEndpoint(String endPoint) { public SecurityInfo getByEndpoint(String endPoint) {
readLock.lock(); readLock.lock();
try { try {
String registrationId = this.getByRegistrationId(endPoint, null); String registrationId = this.getByRegistrationId(endPoint, null);
return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? sessions.get(registrationId).getInfo() : this.add(endPoint); return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ?
sessions.get(registrationId).getInfo() : this.addLwM2MClientToSession(endPoint);
} finally { } finally {
readLock.unlock(); readLock.unlock();
} }
} }
/**
* Start after LwM2mPskStore
* @param identity -
* @return SecurityInfo
*/
@Override @Override
public SecurityInfo getByIdentity(String identity) { public SecurityInfo getByIdentity(String identity) {
readLock.lock(); readLock.lock();
try { try {
String integrationId = this.getByRegistrationId(null, identity); String integrationId = this.getByRegistrationId(null, identity);
return (integrationId != null) ? sessions.get(integrationId).getInfo() : add(identity); return (integrationId != null) ? sessions.get(integrationId).getInfo() : this.addLwM2MClientToSession(identity);
} finally { } finally {
readLock.unlock(); readLock.unlock();
} }
@ -88,7 +103,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
public Collection<SecurityInfo> getAll() { public Collection<SecurityInfo> getAll() {
readLock.lock(); readLock.lock();
try { try {
return Collections.unmodifiableCollection(this.sessions.entrySet().stream().map(model -> model.getValue().getInfo()).collect(Collectors.toList())); return Collections.unmodifiableCollection(this.sessions.values().stream().map(LwM2MClient::getInfo).collect(Collectors.toList()));
} finally { } finally {
readLock.unlock(); readLock.unlock();
} }
@ -104,7 +119,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
LwM2MClient lwM2MClient = (sessions.get(registrationId) != null) ? sessions.get(registrationId) : null; LwM2MClient lwM2MClient = (sessions.get(registrationId) != null) ? sessions.get(registrationId) : null;
if (lwM2MClient != null) { if (lwM2MClient != null) {
if (listener != null) { if (listener != null) {
listener.securityInfoRemoved(infosAreCompromised, lwM2MClient.getInfo()); listener.securityInfoRemoved(INFOS_ARE_COMPROMISED, lwM2MClient.getInfo());
} }
sessions.remove(registrationId); sessions.remove(registrationId);
} }
@ -118,22 +133,38 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
this.listener = listener; this.listener = listener;
} }
public LwM2MClient getlwM2MClient(String endPoint, String identity) { public LwM2MClient getLwM2MClient(String endPoint, String identity) {
Map.Entry<String, LwM2MClient> modelClients = (endPoint != null) ? 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 -> endPoint.equals(model.getValue().getEndPoint())).findAny().orElse(null) :
this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).findAny().orElse(null); this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).findAny().orElse(null);
return (modelClients != null) ? modelClients.getValue() : null; return (modelClients != null) ? modelClients.getValue() : null;
} }
public LwM2MClient getlwM2MClient(String registrationId) { public LwM2MClient getLwM2MClientWithReg(Registration registration, String registrationId) {
return this.sessions.get(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();
} }
public LwM2MClient getlwM2MClient(LeshanServer lwServer, Registration registration) { /**
* Update in sessions (LwM2MClient for key registration_Id) after starting registration LwM2MClient in LwM2MTransportServiceImpl
* Remove from sessions LwM2MClient with key registration_Endpoint
* @param lwServer -
* @param registration -
* @return LwM2MClient after adding it to session
*/
public LwM2MClient updateInSessionsLwM2MClient(LeshanServer lwServer, Registration registration) {
writeLock.lock(); writeLock.lock();
try { try {
if (this.sessions.get(registration.getEndpoint()) == null) { if (this.sessions.get(registration.getEndpoint()) == null) {
this.add(registration.getEndpoint()); this.addLwM2MClientToSession(registration.getEndpoint());
} }
LwM2MClient lwM2MClient = this.sessions.get(registration.getEndpoint()); LwM2MClient lwM2MClient = this.sessions.get(registration.getEndpoint());
lwM2MClient.setLwServer(lwServer); lwM2MClient.setLwServer(lwServer);
@ -154,35 +185,43 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null;
} }
public String getByRegistrationId(String credentialsId) {
List<String> registrationIds = (this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()).size() > 0) ?
this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()) :
this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getIdentity())).map(model -> model.getKey()).collect(Collectors.toList());
return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null;
}
public Registration getByRegistration(String registrationId) { public Registration getByRegistration(String registrationId) {
return this.sessions.get(registrationId).getRegistration(); return this.sessions.get(registrationId).getRegistration();
} }
private SecurityInfo add(String identity) { /**
ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.CLIENT); * Add new LwM2MClient to session
UUID profileUuid = (addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; * @param identity-
if (store.getSecurityInfo() != null) { * @return SecurityInfo. If error - SecurityInfoError
if (store.getSecurityMode() < DEFAULT_MODE.code) { * 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(); String endpoint = store.getSecurityInfo().getEndpoint();
sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), null, null, profileUuid)); sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid));
} else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) {
sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), profileUuid));
} 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;
}
} }
} else { return store.getSecurityInfo();
if (store.getSecurityMode() == NO_SEC.code)
sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), null, null, profileUuid));
else log.error("Registration failed: FORBIDDEN, endpointId: [{}]", identity);
}
return store.getSecurityInfo();
} }
public Map<String, LwM2MClient> getSession (UUID sessionUuId){ public Map<String, LwM2MClient> getSession(UUID sessionUuId) {
return this.sessions.entrySet().stream().filter(e -> e.getValue().getSessionUuid().equals(sessionUuId)).collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); return this.sessions.entrySet().stream().filter(e -> e.getValue().getSessionUuid().equals(sessionUuId)).collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue()));
} }
public Map<String, LwM2MClient> getSessions() { public Map<String, LwM2MClient> getSessions() {
@ -193,14 +232,14 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
return this.profiles; return this.profiles;
} }
public Map<UUID, AttrTelemetryObserveValue>setProfiles(Map<UUID, AttrTelemetryObserveValue> profiles) { public AttrTelemetryObserveValue getProfile(UUID profileUuId) {
return this.profiles.get(profileUuId);
}
public Map<UUID, AttrTelemetryObserveValue> setProfiles(Map<UUID, AttrTelemetryObserveValue> profiles) {
return this.profiles = profiles; return this.profiles = profiles;
} }
/**
*
* @param deviceProfile
*/
public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) {
JsonObject profilesConfigData = LwM2MTransportHandler.getObserveAttrTelemetryFromThingsboard(deviceProfile); JsonObject profilesConfigData = LwM2MTransportHandler.getObserveAttrTelemetryFromThingsboard(deviceProfile);
if (profilesConfigData != null) { if (profilesConfigData != null) {
@ -208,5 +247,4 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore {
} }
return (profilesConfigData != null); return (profilesConfigData != null);
} }
} }

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

@ -26,11 +26,19 @@ import org.eclipse.leshan.core.util.StringUtils;
import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.datatype.XMLGregorianCalendar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@Slf4j @Slf4j
public class LwM2mValueConverterImpl implements LwM2mValueConverter { public class LwM2mValueConverterImpl implements LwM2mValueConverter {
private static final LwM2mValueConverterImpl INSTANCE = new LwM2mValueConverterImpl();
public static LwM2mValueConverterImpl getInstance() {
return INSTANCE;
}
@Override @Override
public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath) public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath)
throws CodecException { throws CodecException {
@ -120,6 +128,11 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
case INTEGER: case INTEGER:
case FLOAT: case FLOAT:
return String.valueOf(value); return String.valueOf(value);
case TIME:
String DATE_FORMAT = "MMM d, yyyy HH:mm a";
Long timeValue = ((Date) value).getTime();
DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
return formatter.format(new Date(timeValue));
default: default:
break; break;
} }

BIN
common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks

Binary file not shown.

309
common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
# #
# Copyright © 2016-2020 The Thingsboard Authors # Copyright © 2016-2020 The Thingsboard Authors
# #
@ -15,9 +15,147 @@
# limitations under the License. # limitations under the License.
# #
#p) CLIENT_CN=LwX50900000000
#s) client_start=0
#f) client_finish=1
#a) CLIENT_ALIAS=client_alias_00000000
#b) BOOTSTRAP_ALIAS=bootstrap
#d) SERVER_ALIAS=server
#j) SERVER_STORE=serverKeyStore.jks
#k) CLIENT_STORE=clientKeyStore.jks
#c) CLIENT_STORE_PWD=client_ks_password
#w) SERVER_STORE_PWD=server_ks_password
#while test $# -gt 0; do
# case "$1" in
# -h|--help)
# echo "$package - attempt to capture frames"
# echo " "
# echo "$package [options] application [arguments]"
# echo " "
# echo "options:"
# echo "-h, --help show brief help"
# echo "-a, --action=ACTION specify an action to use"
# echo "-o, --output-dir=DIR specify a directory to store output in"
# exit 0
# ;;
# -a)
# shift
# if test $# -gt 0; then
# export PROCESS=$1
# else
# echo "no process specified"
# exit 1
# fi
# shift
# ;;
# --action*)
# export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
# shift
# ;;
# -o)
# shift
# if test $# -gt 0; then
# export OUTPUT=$1
# else
# echo "no output dir specified"
# exit 1
# fi
# shift
# ;;
# --output-dir*)
# export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
# shift
# ;;
# *)
# break
# ;;
# esac
#done
while getopts p:s:f:a:b:d:j:k:c:w: flag; do
case "${flag}" in
p) client_prefix=${OPTARG} ;;
s) client_start=${OPTARG} ;;
f) client_finish=${OPTARG} ;;
a) client_alias=${OPTARG} ;;
b) bootstrap_alias=${OPTARG} ;;
d) server_alias=${OPTARG} ;;
j) key_store_server_file=${OPTARG} ;;
k) key_store_client_file=${OPTARG} ;;
c) client_key_store_pwd=${OPTARG} ;;
w) server_key_store_pwd=${OPTARG} ;;
esac
done
# cd to dir of script
script_dir=$(dirname $0)
echo "script_dir: $script_dir"
cd $script_dir
# source the properties: # source the properties:
. ./lwM2M_keygen.properties . ./lwM2M_keygen.properties
if [ -n "$client_prefix" ]; then
CLIENT_PREFIX=$client_prefix
fi
if [ -z "$client_start" ]; then
client_start=0
fi
if [ -z "$client_finish" ]; then
client_finish=1
fi
if [ -n "$client_alias" ]; then
CLIENT_ALIAS=$client_alias
fi
if [ -n "$bootstrap_alias" ]; then
BOOTSTRAP_ALIAS=$bootstrap_alias
fi
if [ -n "$server_alias" ]; then
SERVER_ALIAS=$server_alias
fi
if [ -n "$key_store_server_file" ]; then
SERVER_STORE=$key_store_server_file
fi
if [ -n "$key_store_client_file" ]; then
CLIENT_STORE=$key_store_client_file
fi
if [ -n "$client_key_store_pwd" ]; then
CLIENT_STORE_PWD=$client_key_store_pwd
fi
if [ -n "$server_key_store_pwd" ]; then
SERVER_STORE_PWD=$server_key_store_pwd
fi
echo "==Start=="
echo "CLIENT_PREFIX: $CLIENT_PREFIX"
echo "client_start: $client_start"
echo "client_finish: $client_finish"
echo "CLIENT_ALIAS: $CLIENT_ALIAS"
echo "BOOTSTRAP_ALIAS: $BOOTSTRAP_ALIAS"
echo "SERVER_ALIAS: $SERVER_ALIAS"
echo "SERVER_STORE: $SERVER_STORE"
echo "CLIENT_STORE: $CLIENT_STORE"
echo "CLIENT_STORE_PWD: $CLIENT_STORE_PWD"
echo "SERVER_STORE_PWD: $SERVER_STORE_PWD"
end_point() {
echo "$CLIENT_PREFIX$(printf "%08d" $CLIENT_NUMBER)"
}
client_alias_point() {
echo "$CLIENT_ALIAS$(printf "%08d" $CLIENT_NUMBER)"
}
# Generation of the keystore. # Generation of the keystore.
echo "${H0}====START========${RESET}" echo "${H0}====START========${RESET}"
echo "${H1}Server Keystore : ${RESET}" echo "${H1}Server Keystore : ${RESET}"
@ -52,7 +190,7 @@ keytool \
-exportcert \ -exportcert \
-alias $SERVER_ALIAS \ -alias $SERVER_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD | \ -storepass $SERVER_STORE_PWD |
keytool \ keytool \
-importcert \ -importcert \
-alias $SERVER_SELF_ALIAS \ -alias $SERVER_SELF_ALIAS \
@ -67,22 +205,22 @@ keytool \
-alias $SERVER_ALIAS \ -alias $SERVER_ALIAS \
-dname "CN=$SERVER_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -dname "CN=$SERVER_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD | \ -storepass $SERVER_STORE_PWD |
keytool \ keytool \
-gencert \ -gencert \
-alias $ROOT_KEY_ALIAS \ -alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \ -storepass $SERVER_STORE_PWD \
-storetype $STORETYPE \ -storetype $STORETYPE \
-validity $VALIDITY | \ -validity $VALIDITY |
keytool \ keytool \
-importcert \ -importcert \
-alias $SERVER_ALIAS \ -alias $SERVER_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD -storepass $SERVER_STORE_PWD
echo echo
echo "${H2}Creating server key and self-signed certificate ...${RESET}" echo "${H2}Creating bootstrap key and self-signed certificate ...${RESET}"
keytool \ keytool \
-genkeypair \ -genkeypair \
-alias $BOOTSTRAP_ALIAS \ -alias $BOOTSTRAP_ALIAS \
@ -97,7 +235,7 @@ keytool \
-exportcert \ -exportcert \
-alias $BOOTSTRAP_ALIAS \ -alias $BOOTSTRAP_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD | \ -storepass $SERVER_STORE_PWD |
keytool \ keytool \
-importcert \ -importcert \
-alias $BOOTSTRAP_SELF_ALIAS \ -alias $BOOTSTRAP_SELF_ALIAS \
@ -112,54 +250,53 @@ keytool \
-alias $BOOTSTRAP_ALIAS \ -alias $BOOTSTRAP_ALIAS \
-dname "CN=$BOOTSTRAP_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -dname "CN=$BOOTSTRAP_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD | \ -storepass $SERVER_STORE_PWD |
keytool \ keytool \
-gencert \ -gencert \
-alias $ROOT_KEY_ALIAS \ -alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \ -storepass $SERVER_STORE_PWD \
-storetype $STORETYPE \ -storetype $STORETYPE \
-validity $VALIDITY | \ -validity $VALIDITY |
keytool \ keytool \
-importcert \ -importcert \
-alias $BOOTSTRAP_ALIAS \ -alias $BOOTSTRAP_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD -storepass $SERVER_STORE_PWD
echo echo
echo "${H1}Client Keystore : ${RESET}" echo "${H1}Client Keystore : ${RESET}"
echo "${H1}==================${RESET}" echo "${H1}==================${RESET}"
echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" #echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}"
keytool \ #keytool \
-genkeypair \ # -genkeypair \
-alias $CLIENT_ALIAS \ # -alias $CLIENT_ALIAS \
-keyalg EC \ # -keyalg EC \
-dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ # -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-validity $VALIDITY \ # -validity $VALIDITY \
-storetype $STORETYPE \ # -storetype $STORETYPE \
-keypass $CLIENT_STORE_PWD \ # -keypass $CLIENT_STORE_PWD \
-keystore $CLIENT_STORE \ # -keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD # -storepass $CLIENT_STORE_PWD
keytool \ #keytool \
-exportcert \ # -exportcert \
-alias $CLIENT_ALIAS \ # -alias $CLIENT_ALIAS \
-keystore $CLIENT_STORE \ # -keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD | \ # -storepass $CLIENT_STORE_PWD | \
keytool \ # keytool \
-importcert \ # -importcert \
-alias $CLIENT_SELF_ALIAS \ # -alias $CLIENT_SELF_ALIAS \
-keystore $CLIENT_STORE \ # -keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD \ # -storepass $CLIENT_STORE_PWD \
-noprompt # -noprompt
echo echo
echo "${H2}Import root certificate just to be able to import ned by root CA with expected CN...${RESET}" echo "${H2}Import root certificate just to be able to import need by root CA with expected CN...${RESET}"
keytool \ keytool \
-exportcert \ -exportcert \
-alias $ROOT_KEY_ALIAS \ -alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \ -keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD | \ -storepass $SERVER_STORE_PWD |
keytool \ keytool \
-importcert \ -importcert \
-alias $ROOT_KEY_ALIAS \ -alias $ROOT_KEY_ALIAS \
@ -167,27 +304,85 @@ keytool \
-storepass $CLIENT_STORE_PWD \ -storepass $CLIENT_STORE_PWD \
-noprompt -noprompt
echo #echo
echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" #echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}"
keytool \ #keytool \
-certreq \ # -certreq \
-alias $CLIENT_ALIAS \ # -alias $CLIENT_ALIAS \
-dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ # -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $CLIENT_STORE \ # -keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD | \ # -storepass $CLIENT_STORE_PWD | \
# keytool \
# -gencert \
# -alias $ROOT_KEY_ALIAS \
# -keystore $SERVER_STORE \
# -storepass $SERVER_STORE_PWD \
# -storetype $STORETYPE \
# -validity $VALIDITY | \
# keytool \
# -importcert \
# -alias $CLIENT_ALIAS \
# -keystore $CLIENT_STORE \
# -storepass $CLIENT_STORE_PWD \
# -noprompt
cert_end_point() {
echo "${H2}Creating client key and self-signed certificate with expected CN $CLIENT_SELF_CN ${RESET}"
keytool \ keytool \
-gencert \ -genkeypair \
-alias $ROOT_KEY_ALIAS \ -alias $CLIENT_CN_ALIAS \
-keystore $SERVER_STORE \ -keyalg EC \
-storepass $SERVER_STORE_PWD \ -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-validity $VALIDITY \
-storetype $STORETYPE \ -storetype $STORETYPE \
-validity $VALIDITY | \ -keypass $CLIENT_STORE_PWD \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD
keytool \
-exportcert \
-alias $CLIENT_CN_ALIAS \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD |
keytool \
-importcert \
-alias $CLIENT_SELF_ALIAS \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD \
-noprompt
echo
echo "${H2}Creating client certificate signed by root CA with expected $CLIENT_CN_NAME ${RESET}"
keytool \
-certreq \
-alias $CLIENT_CN_ALIAS \
-dname "CN=$CLIENT_CN_NAME, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD |
keytool \
-gencert \
-alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \
-storetype $STORETYPE \
-validity $VALIDITY |
keytool \ keytool \
-importcert \ -importcert \
-alias $CLIENT_ALIAS \ -alias $CLIENT_CN_ALIAS \
-keystore $CLIENT_STORE \ -keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD \ -storepass $CLIENT_STORE_PWD \
-noprompt -noprompt
}
while [ "$CLIENT_NUMBER" != "$client_finish" ]; do
CLIENT_CN_NAME=$(end_point)
CLIENT_CN_ALIAS=$(client_alias_point)
echo "$CLIENT_CN_NAME"
echo "$CLIENT_CN_ALIAS"
cert_end_point
CLIENT_NUMBER=$(($CLIENT_NUMBER + 1))
echo "number $CLIENT_NUMBER"
echo "finish $client_finish"
done
echo echo
echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}"

19
common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties

@ -17,7 +17,7 @@
# Keystore common parameters # Keystore common parameters
ROOT_KEY_ALIAS=rootCA ROOT_KEY_ALIAS=rootCA
DOMAIN_SUFFIX="$(hostname)" DOMAIN_SUFFIX="$(hostname)"
ROOT_CN="$DOMAIN_SUFFIX rootCA" ROOT_CN="$DOMAIN_SUFFIX $ROOT_KEY_ALIAS"
ORGANIZATIONAL_UNIT=Thingsboard ORGANIZATIONAL_UNIT=Thingsboard
ORGANIZATION=Thingsboard ORGANIZATION=Thingsboard
CITY=SF CITY=SF
@ -27,23 +27,22 @@ VALIDITY=36500 #days
STORETYPE="JKS" STORETYPE="JKS"
#Server #Server
SERVER_STORE=serverKeyStore.jks SERVER_STORE=serverKeyStore1.jks
SERVER_STORE_PWD=server_ks_password SERVER_STORE_PWD=server_ks_password1
SERVER_ALIAS=server SERVER_ALIAS=server1
SERVER_CN="$DOMAIN_SUFFIX server LwM2M signed by root CA" SERVER_CN="$DOMAIN_SUFFIX server LwM2M signed by root CA"
SERVER_SELF_ALIAS=server_self_signed SERVER_SELF_ALIAS=server_self_signed
SERVER_SELF_CN="$DOMAIN_SUFFIX server LwM2M self-signed" SERVER_SELF_CN="$DOMAIN_SUFFIX server LwM2M self-signed"
BOOTSTRAP_ALIAS=bootstrap BOOTSTRAP_ALIAS=bootstrap1
BOOTSTRAP_CN="$DOMAIN_SUFFIX bootstrap server LwM2M signed by root CA" BOOTSTRAP_CN="$DOMAIN_SUFFIX bootstrap server LwM2M signed by root CA"
BOOTSTRAP_SELF_ALIAS=bootstrap_self_signed BOOTSTRAP_SELF_ALIAS=bootstrap_self_signed
BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed" BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed"
# Client # Client
CLIENT_STORE=clientKeyStore.jks CLIENT_STORE=clientKeyStore1.jks
CLIENT_STORE_PWD=client_ks_password CLIENT_STORE_PWD=client_ks_password1
CLIENT_ALIAS=client CLIENT_ALIAS=client_alias_1
#CLIENT_CN=client_lwm2m_x509 CLIENT_PREFIX=LwX509_
CLIENT_CN=mobile_lwm2m_x509
CLIENT_SELF_ALIAS=client_self_signed CLIENT_SELF_ALIAS=client_self_signed
CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed" CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed"

208
common/transport/lwm2m/src/main/resources/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml

@ -1,208 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-XML_LWM2M_Connectivity_Monitoring-V1_0_2-20180612-A
Type: xml
Date: 2018-June-12
Public Reachable Information
Path: http://www.openmobilealliance.org/tech/profiles
Name: LWM2M_Connectivity_Monitoring-v1_0_2.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LightweightM2M-V1_0_2
OMA-TS-LightweightM2M_Core-V1_1
This is available at http://www.openmobilealliance.org/
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
LEGAL DISCLAIMER
Copyright 2018 Open Mobile Alliance All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The above license is used as a license under copyright only. Please
reference the OMA IPR Policy for patent licensing terms:
http://www.openmobilealliance.org/ipr.html
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Connectivity Monitoring</Name>
<Description1>
This LwM2M Object enables monitoring of parameters related to network connectivity. In this general connectivity Object, the Resources are limited to the most general cases common to most network bearers. It is recommended to read the description, which refers to relevant standard development organizations (e.g. 3GPP, IEEE). The goal of the Connectivity Monitoring Object is to carry information reflecting the more up to date values of the current connection for monitoring purposes. Resources such as Link Quality, Radio Signal Strength, Cell ID are retrieved during connected mode at least for cellular networks.
</Description1>
<ObjectID>4</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:4</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Network Bearer</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-50</RangeEnumeration>
<Units/>
<Description>
Indicates the network bearer used for the current LwM2M communication session from the network bearer list below. The number range is split into three categories: 0 - 20 are Cellular Bearers 21 - 40 are Wireless Bearers 41 - 50 are Wireline Bearers More specifically: 0: GSM cellular network 1: TD-SCDMA cellular network 2: WCDMA cellular network 3: CDMA2000 cellular network 4: WiMAX cellular network 5: LTE-TDD cellular network 6: LTE-FDD cellular network 7: NB-IoT 8 - 20: Reserved for other types of cellular network 21: WLAN network 22: Bluetooth network 23: IEEE 802.15.4 network 24 - 40: Reserved for other types of local wireless network 41: Ethernet 42: DSL 43: PLC 44 - 50: reserved for other types of wireline networks.
</Description>
</Item>
<Item ID="1">
<Name>Available Network Bearer</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-50</RangeEnumeration>
<Units/>
<Description>
Indicates a list of current available network bearer. Each Resource Instance has a value from the network bearer list.
</Description>
</Item>
<Item ID="2">
<Name>Radio Signal Strength</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration/>
<Units>dBm</Units>
<Description>
Indicates the average value of the received signal strength indication used in the current network bearer (as indicated by Resource 0 of this Object). For the following network bearers the signal strength parameters indicated below are represented by this resource: GSM: RSSI UMTS: RSCP LTE: RSRP NB-IoT: NRSRP For more details on Network Measurement Report, refer to the appropriate Cellular or Wireless Network standards, (e.g. for LTE Cellular Network refer to 3GPP TS 36.133 specification).
</Description>
</Item>
<Item ID="3">
<Name>Link Quality</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration/>
<Units/>
<Description>
This contains received link quality e.g. LQI for IEEE 802.15.4 (range 0...255), RxQual Downlink for GSM (range 0...7, refer to [3GPP 44.018] for more details on Network Measurement Report encoding), RSRQ for LTE, (refer to [3GPP 36.214]), NRSRQ for NB-IoT (refer to [3GPP 36.214]).
</Description>
</Item>
<Item ID="4">
<Name>IP Addresses</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>String</Type>
<RangeEnumeration/>
<Units/>
<Description>
The IP addresses assigned to the connectivity interface. (e.g. IPv4, IPv6, etc.)
</Description>
</Item>
<Item ID="5">
<Name>Router IP Addresses</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration/>
<Units/>
<Description>
The IP address of the next-hop IP router, on each of the interfaces specified in resource 4 (IP Addresses). Note: This IP Address doesn’t indicate the Server IP address.
</Description>
</Item>
<Item ID="6">
<Name>Link Utilization</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-100</RangeEnumeration>
<Units>%</Units>
<Description>
The percentage indicating the average utilization of the link to the next-hop IP router.
</Description>
</Item>
<Item ID="7">
<Name>APN</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration/>
<Units/>
<Description>
Access Point Name in case Network Bearer Resource is a Cellular Network.
</Description>
</Item>
<Item ID="8">
<Name>Cell ID</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration/>
<Units/>
<Description>
Serving Cell ID in case Network Bearer Resource is a Cellular Network. As specified in TS [3GPP 23.003] and in [3GPP. 24.008]. Range (0...65535) in GSM/EDGE UTRAN Cell ID has a length of 28 bits. Cell Identity in WCDMA/TD-SCDMA. Range: (0...268435455). LTE Cell ID has a length of 28 bits. Parameter definitions in [3GPP 25.331].
</Description>
</Item>
<Item ID="9">
<Name>SMNC</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-999</RangeEnumeration>
<Units>%</Units>
<Description>
Serving Mobile Network Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003].
</Description>
</Item>
<Item ID="10">
<Name>SMCC</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-999</RangeEnumeration>
<Units/>
<Description>
Serving Mobile Country Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003].
</Description>
</Item>
</Resources>
<Description2/>
</Object>
</LWM2M>

147
common/transport/lwm2m/src/main/resources/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml

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

5
common/transport/transport-api/pom.xml

@ -114,9 +114,12 @@
<dependency> <dependency>
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId> <artifactId>leshan-core</artifactId>
<version>1.0.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-server-cf</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java

@ -43,8 +43,10 @@ public abstract class TransportContext {
@Autowired @Autowired
private TransportService transportService; private TransportService transportService;
@Autowired @Autowired
private TbServiceInfoProvider serviceInfoProvider; private TbServiceInfoProvider serviceInfoProvider;
@Autowired @Autowired
private SchedulerComponent scheduler; private SchedulerComponent scheduler;

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

@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.common.transport.service.SessionMetaData;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
@ -98,7 +98,7 @@ public interface TransportService {
void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
void reportActivity(SessionInfoProto sessionInfo); SessionMetaData reportActivity(SessionInfoProto sessionInfo);
void deregisterSession(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo);
} }

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

@ -59,7 +59,6 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -426,6 +425,8 @@ public class JsonConverter {
case MQTT_BASIC: case MQTT_BASIC:
result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject());
break; break;
case LWM2M_CREDENTIALS:
break;
} }
result.addProperty("credentialsType", payload.getCredentialsType().name()); result.addProperty("credentialsType", payload.getCredentialsType().name());
result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); result.addProperty("status", ProvisionResponseStatus.SUCCESS.name());

53
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java

@ -24,7 +24,6 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Map; import java.util.Map;
@Slf4j @Slf4j
@ -32,39 +31,57 @@ import java.util.Map;
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportConfigBootstrap { public class LwM2MTransportConfigBootstrap {
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.bind_address:}") @Value("${transport.lwm2m.bootstrap.enable:}")
private String bootstrapHost; private Boolean bootstrapEnable;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.bind_port:}") @Value("${transport.lwm2m.bootstrap.id:}")
private Integer bootstrapPort; private Integer bootstrapServerId;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.bind_port_cert:}") @Value("${transport.lwm2m.bootstrap.secure.start_psk:}")
private Integer bootstrapPortCert; private Boolean bootstrapStartPsk;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.start_rpk:}")
private Boolean bootstrapStartRpk;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.start_all:}") @Value("${transport.lwm2m.bootstrap.secure.start_x509:}")
private boolean bootstrapStartAll; private Boolean bootstrapStartX509;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.dtls_mode:}") @Value("${transport.lwm2m.bootstrap.bind_address:}")
private Integer bootStrapDtlsMode; private String bootstrapHost;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_address:}") @Value("${transport.lwm2m.bootstrap.secure.bind_address:}")
private String bootstrapSecureHost; private String bootstrapSecureHost;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port:}") @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_psk:}")
private Integer bootstrapSecurePort; private Integer bootstrapPortNoSecPsk;
@Getter
@Value("${transport.lwm2m.bootstrap.bind_port_no_sec_rpk:}")
private Integer bootstrapPortNoSecRpk;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port_cert:}") @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_x509:}")
private Integer bootstrapSecurePortCert; private Integer bootstrapPortNoSecX509;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port_psk:}")
private Integer bootstrapSecurePortPsk;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port_rpk:}")
private Integer bootstrapSecurePortRpk;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port_x509:}")
private Integer bootstrapSecurePortX509;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.public_x:}") @Value("${transport.lwm2m.bootstrap.secure.public_x:}")
@ -86,10 +103,6 @@ public class LwM2MTransportConfigBootstrap {
@Value("${transport.lwm2m.bootstrap.secure.alias:}") @Value("${transport.lwm2m.bootstrap.secure.alias:}")
private String bootstrapAlias; private String bootstrapAlias;
@Getter
@Setter
private X509Certificate bootstrapCertificate;
@Getter @Getter
@Setter @Setter
private Map<String /** clientEndPoint */, TransportProtos.ValidateDeviceCredentialsResponseMsg> sessions; private Map<String /** clientEndPoint */, TransportProtos.ValidateDeviceCredentialsResponseMsg> sessions;

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

@ -20,32 +20,33 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.*; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportConfigServer { public class LwM2MTransportConfigServer {
@Getter
@Value("${transport.lwm2m.timeout:}")
private Long timeout;
@Getter
@Value("${transport.sessions.report_timeout}")
private long sessionReportTimeout;
@Getter @Getter
private String MODEL_PATH_DEFAULT = "models"; private String MODEL_PATH_DEFAULT = "models";
@ -77,10 +78,6 @@ public class LwM2MTransportConfigServer {
@Getter @Getter
private String BASE_DIR_PATH = System.getProperty("user.dir"); private String BASE_DIR_PATH = System.getProperty("user.dir");
@Getter
@Value("${transport.lwm2m.model_path_file:}")
private String modelPathFile;
@Getter @Getter
// private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$"; // private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$";
private String PATH_DATA = "data"; private String PATH_DATA = "data";
@ -90,8 +87,44 @@ public class LwM2MTransportConfigServer {
private List<ObjectModel> modelsValue; private List<ObjectModel> modelsValue;
@Getter @Getter
@Value("${transport.lwm2m.support_deprecated_ciphers_enable:}") @Value("${transport.lwm2m.timeout:}")
private boolean supportDeprecatedCiphersEnable; private Long timeout;
@Getter
@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;
@Getter
@Value("${transport.lwm2m.recommended_supported_groups:}")
private boolean recommendedSupportedGroups;
@Getter
@Value("${transport.lwm2m.request_pool_size:}")
private int requestPoolSize;
@Getter
@Value("${transport.lwm2m.request_error_pool_size:}")
private int requestErrorPoolSize;
@Getter
@Value("${transport.lwm2m.registered_pool_size:}")
private int registeredPoolSize;
@Getter
@Value("${transport.lwm2m.update_registered_pool_size:}")
private int updateRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.un_registered_pool_size:}")
private int unRegisteredPoolSize;
@Getter @Getter
@Value("${transport.lwm2m.secure.key_store_type:}") @Value("${transport.lwm2m.secure.key_store_type:}")
@ -113,6 +146,18 @@ public class LwM2MTransportConfigServer {
@Value("${transport.lwm2m.secure.root_alias:}") @Value("${transport.lwm2m.secure.root_alias:}")
private String rootAlias; private String rootAlias;
@Getter
@Value("${transport.lwm2m.server.secure.start_psk:}")
private boolean serverStartPsk;
@Getter
@Value("${transport.lwm2m.server.secure.start_rpk:}")
private boolean serverStartRpk;
@Getter
@Value("${transport.lwm2m.server.secure.start_x509:}")
private boolean serverStartX509;
@Getter @Getter
@Value("${transport.lwm2m.secure.enable_gen_psk_rpk:}") @Value("${transport.lwm2m.secure.enable_gen_psk_rpk:}")
private Boolean enableGenPskRpk; private Boolean enableGenPskRpk;
@ -122,32 +167,37 @@ public class LwM2MTransportConfigServer {
private String serverHost; private String serverHost;
@Getter @Getter
@Value("${transport.lwm2m.server.bind_port:}") @Value("${transport.lwm2m.server.id:}")
private Integer serverPort; private Integer serverId;
@Getter
@Value("${transport.lwm2m.server.secure.bind_address:}")
private String serverSecureHost;
@Getter @Getter
@Value("${transport.lwm2m.server.bind_port_cert:}") @Value("${transport.lwm2m.server.bind_port_no_sec_psk:}")
private Integer serverPortCert; private Integer serverPortNoSecPsk;
@Getter @Getter
@Value("${transport.lwm2m.server.secure.start_all:}") @Value("${transport.lwm2m.server.bind_port_no_sec_rpk:}")
private boolean serverStartAll; private Integer serverPortNoSecRpk;
@Getter @Getter
@Value("${transport.lwm2m.server.secure.dtls_mode:}") @Value("${transport.lwm2m.server.bind_port_no_sec_x509:}")
private Integer serverDtlsMode; private Integer serverPortNoSecX509;
@Getter @Getter
@Value("${transport.lwm2m.server.secure.bind_address:}") @Value("${transport.lwm2m.server.secure.bind_port_psk:}")
private String serverSecureHost; private Integer serverPortPsk;
@Getter @Getter
@Value("${transport.lwm2m.server.secure.bind_port:}") @Value("${transport.lwm2m.server.secure.bind_port_rpk:}")
private Integer serverSecurePort; private Integer serverPortRpk;
@Getter @Getter
@Value("${transport.lwm2m.server.secure.bind_port_cert:}") @Value("${transport.lwm2m.server.secure.bind_port_x509:}")
private Integer serverSecurePortCert; private Integer serverPortX509;
@Getter @Getter
@Value("${transport.lwm2m.server.secure.public_x:}") @Value("${transport.lwm2m.server.secure.public_x:}")
@ -165,10 +215,6 @@ public class LwM2MTransportConfigServer {
@Value("${transport.lwm2m.server.secure.alias:}") @Value("${transport.lwm2m.server.secure.alias:}")
private String serverAlias; private String serverAlias;
@Getter
@Value("${transport.lwm2m.bootstrap.enable:}")
private Boolean bootstrapEnable;
@Getter @Getter
@Value("${transport.lwm2m.secure.redis_url:}") @Value("${transport.lwm2m.secure.redis_url:}")
private String redisUrl; private String redisUrl;
@ -187,7 +233,7 @@ public class LwM2MTransportConfigServer {
} else { } else {
log.error(" [{}] Read Models", path.getAbsoluteFile()); log.error(" [{}] Read Models", path.getAbsoluteFile());
} }
getInKeyStore(); this.getInKeyStore();
} }
private File getPathModels() { private File getPathModels() {
@ -238,4 +284,26 @@ public class LwM2MTransportConfigServer {
} }
return FULL_FILE_PATH.toUri().getPath(); return FULL_FILE_PATH.toUri().getPath();
} }
public ResourceModel getResourceModel(Registration registration, LwM2mPath pathIds) {
String pathLink = "/" + pathIds.getObjectId() + "/" + pathIds.getObjectInstanceId();
return (Arrays.stream(registration.getObjectLinks()).filter(p-> p.getUrl().equals(pathLink)).findFirst().isPresent() &&
this.modelsValue.stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).size() > 0) &&
this.modelsValue.stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0).resources.containsKey(pathIds.getResourceId()) ?
this.modelsValue.stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0).resources.get(pathIds.getResourceId()) :
null;
}
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;
}
} }

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

@ -509,8 +509,8 @@ public class DefaultTransportService implements TransportService {
} }
@Override @Override
public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
reportActivityInternal(sessionInfo); return reportActivityInternal(sessionInfo);
} }
private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) {

1
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java

@ -25,6 +25,7 @@ import java.util.concurrent.ScheduledFuture;
* Created by ashvayka on 15.10.18. * Created by ashvayka on 15.10.18.
*/ */
@Data @Data
public
class SessionMetaData { class SessionMetaData {
private volatile TransportProtos.SessionInfoProto sessionInfo; private volatile TransportProtos.SessionInfoProto sessionInfo;

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

@ -21,7 +21,6 @@ import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -87,6 +86,9 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
case MQTT_BASIC: case MQTT_BASIC:
formatSimpleMqttCredentials(deviceCredentials); formatSimpleMqttCredentials(deviceCredentials);
break; break;
case LWM2M_CREDENTIALS:
formatSimpleLwm2mCredentials(deviceCredentials);
break;
} }
log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials);
credentialsValidator.validate(deviceCredentials, id -> tenantId); credentialsValidator.validate(deviceCredentials, id -> tenantId);
@ -129,6 +131,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials));
} }
private void formatCertData(DeviceCredentials deviceCredentials) { private void formatCertData(DeviceCredentials deviceCredentials) {
String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert); String sha3Hash = EncryptionUtil.getSha3Hash(cert);
@ -136,6 +139,10 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
deviceCredentials.setCredentialsValue(cert); deviceCredentials.setCredentialsValue(cert);
} }
private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) {
}
@Override @Override
@CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId") @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId")
public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) {

47
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -177,10 +177,40 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
return doSaveDevice(device, null); return doSaveDevice(device, null);
} }
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials) {
if (device.getId() == null) {
Device deviceWithName = this.findDeviceByTenantIdAndName(device.getTenantId(), device.getName());
device = deviceWithName == null ? device : deviceWithName.updateDevice(device);
}
Device savedDevice = this.saveDeviceWithoutCredentials(device);
deviceCredentials.setDeviceId(savedDevice.getId());
if (device.getId() == null) {
deviceCredentials = deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
}
else {
deviceCredentials.setId(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), savedDevice.getId()).getId());
deviceCredentials = deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials);
}
return savedDevice;
}
private Device doSaveDevice(Device device, String accessToken) { private Device doSaveDevice(Device device, String accessToken) {
Device savedDevice = this.saveDeviceWithoutCredentials(device);
if (device.getId() == null) {
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId()));
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20));
deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials);
}
return savedDevice;
}
private Device saveDeviceWithoutCredentials(Device device) {
log.trace("Executing saveDevice [{}]", device); log.trace("Executing saveDevice [{}]", device);
deviceValidator.validate(device, Device::getTenantId); deviceValidator.validate(device, Device::getTenantId);
Device savedDevice;
try { try {
DeviceProfile deviceProfile; DeviceProfile deviceProfile;
if (device.getDeviceProfileId() == null) { if (device.getDeviceProfileId() == null) {
@ -198,8 +228,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
} }
device.setType(deviceProfile.getName()); device.setType(deviceProfile.getName());
device.setDeviceData(syncDeviceData(deviceProfile, device.getDeviceData())); device.setDeviceData(syncDeviceData(deviceProfile, device.getDeviceData()));
return deviceDao.save(device.getTenantId(), device);
savedDevice = deviceDao.save(device.getTenantId(), device);
} catch (Exception t) { } catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
@ -208,14 +237,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
throw t; throw t;
} }
} }
if (device.getId() == null) {
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId()));
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20));
deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials);
}
return savedDevice;
} }
private DeviceData syncDeviceData(DeviceProfile deviceProfile, DeviceData deviceData) { private DeviceData syncDeviceData(DeviceProfile deviceProfile, DeviceData deviceData) {
@ -512,6 +533,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
case X509_CERTIFICATE: case X509_CERTIFICATE:
deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash()); deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash());
break; break;
case LWM2M_CREDENTIALS:
break;
} }
try { try {
deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
@ -528,7 +551,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Override @Override
protected void validateCreate(TenantId tenantId, Device device) { protected void validateCreate(TenantId tenantId, Device device) {
DefaultTenantProfileConfiguration profileConfiguration = DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDevices = profileConfiguration.getMaxDevices(); long maxDevices = profileConfiguration.getMaxDevices();
validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE); validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
} }

7
netty-mqtt/pom.xml

@ -67,11 +67,6 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -87,4 +82,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

28
pom.xml

@ -62,9 +62,10 @@
<jackson-annotations.version>2.11.3</jackson-annotations.version> <jackson-annotations.version>2.11.3</jackson-annotations.version>
<jackson-core.version>2.11.3</jackson-core.version> <jackson-core.version>2.11.3</jackson-core.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version> <json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>2.2.3</californium.version> <californium.version>2.6.0</californium.version>
<leshan-server.version>1.0.1</leshan-server.version> <leshan-server.version>1.3.0</leshan-server.version>
<leshan-client.version>1.0.0</leshan-client.version> <leshan-core.version>1.3.0</leshan-core.version>
<leshan-client.version>1.3.0</leshan-client.version>
<gson.version>2.6.2</gson.version> <gson.version>2.6.2</gson.version>
<freemarker.version>2.3.30</freemarker.version> <freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>
@ -578,7 +579,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version> <version>3.8.1</version>
<configuration> <configuration>
<source>1.8</source> <source>1.8</source>
<target>1.8</target> <target>1.8</target>
@ -1160,11 +1161,30 @@
<artifactId>leshan-server-redis</artifactId> <artifactId>leshan-server-redis</artifactId>
<version>${leshan-server.version}</version> <version>${leshan-server.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
<version>${leshan-core.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
<version>${californium.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.californium</groupId> <groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId> <artifactId>californium-core</artifactId>
<version>${californium.version}</version> <version>${californium.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>element-connector</artifactId>
<version>${californium.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

19
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -121,6 +121,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -1091,6 +1092,24 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody();
} }
public Optional<Device> saveDeviceWithCredentials(Device device, DeviceCredentials credentials) {
try {
Map<Class<?>, Object> deviceCredentials = new ConcurrentHashMap<>();
deviceCredentials.put(Device.class, device);
deviceCredentials.put(DeviceCredentials.class, credentials);
// return restTemplate.postForEntity(baseURL + "/api/lwm2m/device-credentials", deviceCredentials, Device.class).getBody();
ResponseEntity<Device> deviceOpt = restTemplate.postForEntity(baseURL + "/api/lwm2m/device-credentials", deviceCredentials, Device.class);
return Optional.ofNullable(deviceOpt.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public PageData<Device> getTenantDevices(String type, PageLink pageLink) { public PageData<Device> getTenantDevices(String type, PageLink pageLink) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("type", type); params.put("type", type);

60
transport/lwm2m/pom.xml

@ -45,11 +45,38 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>transport-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-server-cf</artifactId> <artifactId>leshan-server-cf</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-client-cf</artifactId> <artifactId>leshan-client-cf</artifactId>
@ -59,7 +86,6 @@
<groupId>org.eclipse.leshan</groupId> <groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-server-redis</artifactId> <artifactId>leshan-server-redis</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
@ -78,37 +104,23 @@
<dependency> <dependency>
<groupId>org.eclipse.californium</groupId> <groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId> <artifactId>californium-core</artifactId>
<version>${californium.version}</version>
<type>test-jar</type> <type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.eclipse.californium</groupId>-->
<!-- <artifactId>scandium</artifactId>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>org.eclipse.californium</groupId> <groupId>org.eclipse.californium</groupId>
<artifactId>element-connector</artifactId> <artifactId>element-connector</artifactId>
<version>${californium.version}</version>
<type>test-jar</type> <type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>lwm2m</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.sun.winsw</groupId>
<artifactId>winsw</artifactId>
<classifier>bin</classifier>
<type>exe</type>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

BIN
transport/lwm2m/src/main/data/credentials/serverKeyStore.jks

Binary file not shown.

403
transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh

@ -0,0 +1,403 @@
#!/bin/bash
#
# Copyright © 2016-2020 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.
#
#p) CLIENT_CN=LwX50900000000
#s) client_start=0
#f) client_finish=1
#a) CLIENT_ALIAS=client_alias_00000000
#b) BOOTSTRAP_ALIAS=bootstrap
#d) SERVER_ALIAS=server
#j) SERVER_STORE=serverKeyStore.jks
#k) CLIENT_STORE=clientKeyStore.jks
#c) CLIENT_STORE_PWD=client_ks_password
#w) SERVER_STORE_PWD=server_ks_password
#while test $# -gt 0; do
# case "$1" in
# -h|--help)
# echo "$package - attempt to capture frames"
# echo " "
# echo "$package [options] application [arguments]"
# echo " "
# echo "options:"
# echo "-h, --help show brief help"
# echo "-a, --action=ACTION specify an action to use"
# echo "-o, --output-dir=DIR specify a directory to store output in"
# exit 0
# ;;
# -a)
# shift
# if test $# -gt 0; then
# export PROCESS=$1
# else
# echo "no process specified"
# exit 1
# fi
# shift
# ;;
# --action*)
# export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
# shift
# ;;
# -o)
# shift
# if test $# -gt 0; then
# export OUTPUT=$1
# else
# echo "no output dir specified"
# exit 1
# fi
# shift
# ;;
# --output-dir*)
# export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
# shift
# ;;
# *)
# break
# ;;
# esac
#done
while getopts p:s:f:a:b:d:j:k:c:w: flag; do
case "${flag}" in
p) client_prefix=${OPTARG} ;;
s) client_start=${OPTARG} ;;
f) client_finish=${OPTARG} ;;
a) client_alias=${OPTARG} ;;
b) bootstrap_alias=${OPTARG} ;;
d) server_alias=${OPTARG} ;;
j) key_store_server_file=${OPTARG} ;;
k) key_store_client_file=${OPTARG} ;;
c) client_key_store_pwd=${OPTARG} ;;
w) server_key_store_pwd=${OPTARG} ;;
esac
done
# cd to dir of script
script_dir=$(dirname $0)
echo "script_dir: $script_dir"
cd $script_dir
# source the properties:
. ./lwM2M_keygen.properties
if [ -n "$client_prefix" ]; then
CLIENT_PREFIX=$client_prefix
fi
if [ -z "$client_start" ]; then
client_start=0
fi
if [ -z "$client_finish" ]; then
client_finish=1
fi
if [ -n "$client_alias" ]; then
CLIENT_ALIAS=$client_alias
fi
if [ -n "$bootstrap_alias" ]; then
BOOTSTRAP_ALIAS=$bootstrap_alias
fi
if [ -n "$server_alias" ]; then
SERVER_ALIAS=$server_alias
fi
if [ -n "$key_store_server_file" ]; then
SERVER_STORE=$key_store_server_file
fi
if [ -n "$key_store_client_file" ]; then
CLIENT_STORE=$key_store_client_file
fi
if [ -n "$client_key_store_pwd" ]; then
CLIENT_STORE_PWD=$client_key_store_pwd
fi
if [ -n "$server_key_store_pwd" ]; then
SERVER_STORE_PWD=$server_key_store_pwd
fi
echo "==Start=="
echo "CLIENT_PREFIX: $CLIENT_PREFIX"
echo "client_start: $client_start"
echo "client_finish: $client_finish"
echo "CLIENT_ALIAS: $CLIENT_ALIAS"
echo "BOOTSTRAP_ALIAS: $BOOTSTRAP_ALIAS"
echo "SERVER_ALIAS: $SERVER_ALIAS"
echo "SERVER_STORE: $SERVER_STORE"
echo "CLIENT_STORE: $CLIENT_STORE"
echo "CLIENT_STORE_PWD: $CLIENT_STORE_PWD"
echo "SERVER_STORE_PWD: $SERVER_STORE_PWD"
end_point() {
echo "$CLIENT_PREFIX$(printf "%08d" $CLIENT_NUMBER)"
}
client_alias_point() {
echo "$CLIENT_ALIAS$(printf "%08d" $CLIENT_NUMBER)"
}
# Generation of the keystore.
echo "${H0}====START========${RESET}"
echo "${H1}Server Keystore : ${RESET}"
echo "${H1}==================${RESET}"
echo "${H2}Creating the trusted root CA key and certificate...${RESET}"
# -keysize
# 1024 (when using -genkeypair)
keytool \
-genkeypair \
-alias $ROOT_KEY_ALIAS \
-keyalg EC \
-dname "CN=$ROOT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-validity $VALIDITY \
-storetype $STORETYPE \
-keypass $SERVER_STORE_PWD \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD
echo
echo "${H2}Creating server key and self-signed certificate ...${RESET}"
keytool \
-genkeypair \
-alias $SERVER_ALIAS \
-keyalg EC \
-dname "CN=$SERVER_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-validity $VALIDITY \
-storetype $STORETYPE \
-keypass $SERVER_STORE_PWD \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD
keytool \
-exportcert \
-alias $SERVER_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD |
keytool \
-importcert \
-alias $SERVER_SELF_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \
-noprompt
echo
echo "${H2}Creating server certificate signed by root CA...${RESET}"
keytool \
-certreq \
-alias $SERVER_ALIAS \
-dname "CN=$SERVER_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD |
keytool \
-gencert \
-alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \
-storetype $STORETYPE \
-validity $VALIDITY |
keytool \
-importcert \
-alias $SERVER_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD
echo
echo "${H2}Creating bootstrap key and self-signed certificate ...${RESET}"
keytool \
-genkeypair \
-alias $BOOTSTRAP_ALIAS \
-keyalg EC \
-dname "CN=$BOOTSTRAP_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-validity $VALIDITY \
-storetype $STORETYPE \
-keypass $SERVER_STORE_PWD \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD
keytool \
-exportcert \
-alias $BOOTSTRAP_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD |
keytool \
-importcert \
-alias $BOOTSTRAP_SELF_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \
-noprompt
echo
echo "${H2}Creating bootstrap certificate signed by root CA...${RESET}"
keytool \
-certreq \
-alias $BOOTSTRAP_ALIAS \
-dname "CN=$BOOTSTRAP_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD |
keytool \
-gencert \
-alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \
-storetype $STORETYPE \
-validity $VALIDITY |
keytool \
-importcert \
-alias $BOOTSTRAP_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD
echo
echo "${H1}Client Keystore : ${RESET}"
echo "${H1}==================${RESET}"
#echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}"
#keytool \
# -genkeypair \
# -alias $CLIENT_ALIAS \
# -keyalg EC \
# -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
# -validity $VALIDITY \
# -storetype $STORETYPE \
# -keypass $CLIENT_STORE_PWD \
# -keystore $CLIENT_STORE \
# -storepass $CLIENT_STORE_PWD
#keytool \
# -exportcert \
# -alias $CLIENT_ALIAS \
# -keystore $CLIENT_STORE \
# -storepass $CLIENT_STORE_PWD | \
# keytool \
# -importcert \
# -alias $CLIENT_SELF_ALIAS \
# -keystore $CLIENT_STORE \
# -storepass $CLIENT_STORE_PWD \
# -noprompt
echo
echo "${H2}Import root certificate just to be able to import need by root CA with expected CN...${RESET}"
keytool \
-exportcert \
-alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD |
keytool \
-importcert \
-alias $ROOT_KEY_ALIAS \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD \
-noprompt
#echo
#echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}"
#keytool \
# -certreq \
# -alias $CLIENT_ALIAS \
# -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
# -keystore $CLIENT_STORE \
# -storepass $CLIENT_STORE_PWD | \
# keytool \
# -gencert \
# -alias $ROOT_KEY_ALIAS \
# -keystore $SERVER_STORE \
# -storepass $SERVER_STORE_PWD \
# -storetype $STORETYPE \
# -validity $VALIDITY | \
# keytool \
# -importcert \
# -alias $CLIENT_ALIAS \
# -keystore $CLIENT_STORE \
# -storepass $CLIENT_STORE_PWD \
# -noprompt
cert_end_point() {
echo "${H2}Creating client key and self-signed certificate with expected CN $CLIENT_SELF_CN ${RESET}"
keytool \
-genkeypair \
-alias $CLIENT_CN_ALIAS \
-keyalg EC \
-dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-validity $VALIDITY \
-storetype $STORETYPE \
-keypass $CLIENT_STORE_PWD \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD
keytool \
-exportcert \
-alias $CLIENT_CN_ALIAS \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD |
keytool \
-importcert \
-alias $CLIENT_SELF_ALIAS \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD \
-noprompt
echo
echo "${H2}Creating client certificate signed by root CA with expected $CLIENT_CN_NAME ${RESET}"
keytool \
-certreq \
-alias $CLIENT_CN_ALIAS \
-dname "CN=$CLIENT_CN_NAME, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD |
keytool \
-gencert \
-alias $ROOT_KEY_ALIAS \
-keystore $SERVER_STORE \
-storepass $SERVER_STORE_PWD \
-storetype $STORETYPE \
-validity $VALIDITY |
keytool \
-importcert \
-alias $CLIENT_CN_ALIAS \
-keystore $CLIENT_STORE \
-storepass $CLIENT_STORE_PWD \
-noprompt
}
while [ "$CLIENT_NUMBER" != "$client_finish" ]; do
CLIENT_CN_NAME=$(end_point)
CLIENT_CN_ALIAS=$(client_alias_point)
echo "$CLIENT_CN_NAME"
echo "$CLIENT_CN_ALIAS"
cert_end_point
CLIENT_NUMBER=$(($CLIENT_NUMBER + 1))
echo "number $CLIENT_NUMBER"
echo "finish $client_finish"
done
echo
echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}"
keytool \
-importkeystore \
-srckeystore $SERVER_STORE \
-destkeystore $SERVER_STORE \
-deststoretype pkcs12 \
-srcstorepass $SERVER_STORE_PWD
echo
echo "${H0}!!! Warning ${H2}Migrate ${H1}${CLIENT_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}"
keytool \
-importkeystore \
-srckeystore $CLIENT_STORE \
-destkeystore $CLIENT_STORE \
-deststoretype pkcs12 \
-srcstorepass $CLIENT_STORE_PWD

57
transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties

@ -0,0 +1,57 @@
#
# Copyright © 2016-2017 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.
#
# Keystore common parameters
ROOT_KEY_ALIAS=rootCA
DOMAIN_SUFFIX="$(hostname)"
ROOT_CN="$DOMAIN_SUFFIX $ROOT_KEY_ALIAS"
ORGANIZATIONAL_UNIT=Thingsboard
ORGANIZATION=Thingsboard
CITY=SF
STATE_OR_PROVINCE=CA
TWO_LETTER_COUNTRY_CODE=US
VALIDITY=36500 #days
STORETYPE="JKS"
#Server
SERVER_STORE=serverKeyStore1.jks
SERVER_STORE_PWD=server_ks_password1
SERVER_ALIAS=server1
SERVER_CN="$DOMAIN_SUFFIX server LwM2M signed by root CA"
SERVER_SELF_ALIAS=server_self_signed
SERVER_SELF_CN="$DOMAIN_SUFFIX server LwM2M self-signed"
BOOTSTRAP_ALIAS=bootstrap1
BOOTSTRAP_CN="$DOMAIN_SUFFIX bootstrap server LwM2M signed by root CA"
BOOTSTRAP_SELF_ALIAS=bootstrap_self_signed
BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed"
# Client
CLIENT_STORE=clientKeyStore1.jks
CLIENT_STORE_PWD=client_ks_password1
CLIENT_ALIAS=client_alias_1
CLIENT_PREFIX=LwX509_
CLIENT_SELF_ALIAS=client_self_signed
CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed"
# Color output stuff
red=`tput setaf 1`
green=`tput setaf 2`
blue=`tput setaf 4`
bold=`tput bold`
H0=${red}${bold}
H1=${green}${bold}
H2=${blue}
RESET=`tput sgr0`

167
transport/lwm2m/src/main/data/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml

@ -1,167 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Connectivity Monitoring</Name>
<Description1>
This LwM2M Object enables monitoring of parameters related to network connectivity. In this general connectivity Object, the Resources are limited to the most general cases common to most network bearers. It is recommended to read the description, which refers to relevant standard development organizations (e.g. 3GPP, IEEE). The goal of the Connectivity Monitoring Object is to carry information reflecting the more up to date values of the current connection for monitoring purposes. Resources such as Link Quality, Radio Signal Strength, Cell ID are retrieved during connected mode at least for cellular networks.
</Description1>
<ObjectID>4</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:4</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Network Bearer</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-50</RangeEnumeration>
<Units/>
<Description>
Indicates the network bearer used for the current LwM2M communication session from the network bearer list below. The number range is split into three categories: 0 - 20 are Cellular Bearers 21 - 40 are Wireless Bearers 41 - 50 are Wireline Bearers More specifically: 0: GSM cellular network 1: TD-SCDMA cellular network 2: WCDMA cellular network 3: CDMA2000 cellular network 4: WiMAX cellular network 5: LTE-TDD cellular network 6: LTE-FDD cellular network 7: NB-IoT 8 - 20: Reserved for other types of cellular network 21: WLAN network 22: Bluetooth network 23: IEEE 802.15.4 network 24 - 40: Reserved for other types of local wireless network 41: Ethernet 42: DSL 43: PLC 44 - 50: reserved for other types of wireline networks.
</Description>
</Item>
<Item ID="1">
<Name>Available Network Bearer</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-50</RangeEnumeration>
<Units/>
<Description>
Indicates a list of current available network bearer. Each Resource Instance has a value from the network bearer list.
</Description>
</Item>
<Item ID="2">
<Name>Radio Signal Strength</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration/>
<Units>dBm</Units>
<Description>
Indicates the average value of the received signal strength indication used in the current network bearer (as indicated by Resource 0 of this Object). For the following network bearers the signal strength parameters indicated below are represented by this resource: GSM: RSSI UMTS: RSCP LTE: RSRP NB-IoT: NRSRP For more details on Network Measurement Report, refer to the appropriate Cellular or Wireless Network standards, (e.g. for LTE Cellular Network refer to 3GPP TS 36.133 specification).
</Description>
</Item>
<Item ID="3">
<Name>Link Quality</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration/>
<Units/>
<Description>
This contains received link quality e.g. LQI for IEEE 802.15.4 (range 0...255), RxQual Downlink for GSM (range 0...7, refer to [3GPP 44.018] for more details on Network Measurement Report encoding), RSRQ for LTE, (refer to [3GPP 36.214]), NRSRQ for NB-IoT (refer to [3GPP 36.214]).
</Description>
</Item>
<Item ID="4">
<Name>IP Addresses</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>String</Type>
<RangeEnumeration/>
<Units/>
<Description>
The IP addresses assigned to the connectivity interface. (e.g. IPv4, IPv6, etc.)
</Description>
</Item>
<Item ID="5">
<Name>Router IP Addresses</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration/>
<Units/>
<Description>
The IP address of the next-hop IP router, on each of the interfaces specified in resource 4 (IP Addresses). Note: This IP Address doesn’t indicate the Server IP address.
</Description>
</Item>
<Item ID="6">
<Name>Link Utilization</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-100</RangeEnumeration>
<Units>%</Units>
<Description>
The percentage indicating the average utilization of the link to the next-hop IP router.
</Description>
</Item>
<Item ID="7">
<Name>APN</Name>
<Operations>R</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration/>
<Units/>
<Description>
Access Point Name in case Network Bearer Resource is a Cellular Network.
</Description>
</Item>
<Item ID="8">
<Name>Cell ID</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration/>
<Units/>
<Description>
Serving Cell ID in case Network Bearer Resource is a Cellular Network. As specified in TS [3GPP 23.003] and in [3GPP. 24.008]. Range (0...65535) in GSM/EDGE UTRAN Cell ID has a length of 28 bits. Cell Identity in WCDMA/TD-SCDMA. Range: (0...268435455). LTE Cell ID has a length of 28 bits. Parameter definitions in [3GPP 25.331].
</Description>
</Item>
<Item ID="9">
<Name>SMNC</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-999</RangeEnumeration>
<Units>%</Units>
<Description>
Serving Mobile Network Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003].
</Description>
</Item>
<Item ID="10">
<Name>SMCC</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>0-999</RangeEnumeration>
<Units/>
<Description>
Serving Mobile Country Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003].
</Description>
</Item>
</Resources>
<Description2/>
</Object>
</LWM2M>

164
transport/lwm2m/src/main/data/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml

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

51
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -42,6 +42,7 @@ zk:
# LWM2M server parameters # LWM2M server parameters
transport: transport:
# Local LwM2M transport parameters
lwm2m: lwm2m:
# Enable/disable lvm2m transport protocol. # Enable/disable lvm2m transport protocol.
enabled: "${LWM2M_ENABLED:true}" enabled: "${LWM2M_ENABLED:true}"
@ -51,7 +52,13 @@ transport:
timeout: "${LWM2M_TIMEOUT:120000}" 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:./common/transport/lwm2m/src/main/resources/models/}"
model_path_file: "${LWM2M_MODEL_PATH_FILE:}" model_path_file: "${LWM2M_MODEL_PATH_FILE:}"
support_deprecated_ciphers_enable: "${LWM2M_SUPPORT_DEPRECATED_CIPHERS_ENABLED:true}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:false}"
request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}"
request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}"
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}"
secure: secure:
# Only Certificate_x509: # Only Certificate_x509:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
@ -64,24 +71,19 @@ transport:
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}" enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}"
server: server:
id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT:5685}" bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_PSK:5685}"
bind_port_cert: "${LWM2M_BIND_PORT_CERT:5687}" bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_RPK:5687}"
bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_X509:5689}"
secure: secure:
start_all: "${START_SERVER_ALL:true}"
#leshan.core (V1_1)
#DTLS security modes:
#0: Pre-Shared Key mode
#1: Raw Public Key mode
#2: Certificate mode X509
#3: NoSec mode *
#OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A (add)
#4: Certificate mode X509 with EST
# If only startAll == false
dtls_mode: "${LWM2M_SECURITY_MODE:1}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_SEC:5686}" start_psk: "${START_SERVER_PSK:true}"
bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT:5688}" start_rpk: "${START_SERVER_RPK:true}"
start_x509: "${START_SERVER_X509:true}"
bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK:5686}"
bind_port_rpk: "${LWM2M_BIND_PORT_SEC_RPK:5688}"
bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509:5690}"
# Only RPK: Public & Private Key # Only RPK: Public & Private Key
# create_rpk: "${CREATE_RPK:}" # create_rpk: "${CREATE_RPK:}"
public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}"
@ -91,16 +93,19 @@ transport:
alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
bootstrap: bootstrap:
enable: "${BOOTSTRAP:true}" enable: "${BOOTSTRAP:true}"
id: "${LWM2M_SERVER_ID:111}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_BS:5689}" bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_BS:5691}"
bind_port_cert: "${LWM2M_BIND_PORT_SER_BS:5691}" bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_BS:5693}"
bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_BS:5695}"
secure: secure:
start_all: "${START_BOOTSTRAP_ALL:true}"
# If only startAll == false
dtls_mode: "${LWM2M_SECURITY_MODE_BS:1}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_SEC_BS:5690}" start_psk: "${START_SERVER_PSK_BS:true}"
bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT_BS:5692}" start_rpk: "${START_SERVER_RPK_BS:true}"
start_x509: "${START_SERVER_X509_BS:true}"
bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK_BS:5692}"
bind_port_rpk: "${LWM2M_BIND_PORT_SER_RPK_BS:5694}"
bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509_BS:5696}"
# Only RPK: Public & Private Key # Only RPK: Public & Private Key
public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}"
public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}"

5
ui-ngx/angular.json

@ -247,6 +247,7 @@
}, },
"defaultProject": "thingsboard", "defaultProject": "thingsboard",
"cli": { "cli": {
"packageManager": "yarn" "packageManager": "yarn",
"analytics": false
} }
} }

35
ui-ngx/src/app/core/http/device-profile.service.ts

@ -21,10 +21,9 @@ import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull, isEmptyStr } from '@core/utils';
import { import { ObjectLwM2M, ServerSecurityConfig } from '@home/components/profile/device/lwm2m/profile-config.models';
ObjectLwM2M, ServerSecurityConfig import { SortOrder } from '@shared/models/page/sort-order';
} from "../../modules/home/components/profile/device/lwm2m/profile-config.models";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -33,7 +32,8 @@ export class DeviceProfileService {
constructor( constructor(
private http: HttpClient private http: HttpClient
) { } ) {
}
public getDeviceProfiles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DeviceProfile>> { public getDeviceProfiles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DeviceProfile>> {
return this.http.get<PageData<DeviceProfile>>(`/api/deviceProfiles${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); return this.http.get<PageData<DeviceProfile>>(`/api/deviceProfiles${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
@ -43,16 +43,31 @@ export class DeviceProfileService {
return this.http.get<DeviceProfile>(`/api/deviceProfile/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); return this.http.get<DeviceProfile>(`/api/deviceProfile/${deviceProfileId}`, defaultHttpOptionsFromConfig(config));
} }
public getLwm2mObjects(objectIds: number [], config?: RequestConfig): Observable<ObjectLwM2M[]> { public getLwm2mObjects(sortOrder: SortOrder, objectIds?: number[], searchText?: string, config?: RequestConfig):
return this.http.get<ObjectLwM2M[]>(`/api/lwm2m/deviceProfile/${objectIds}`, defaultHttpOptionsFromConfig(config)); Observable<Array<ObjectLwM2M>> {
let url = `/api/lwm2m/deviceProfile/?sortProperty=${sortOrder.property}&sortOrder=${sortOrder.direction}`;
if (isDefinedAndNotNull(objectIds) && objectIds.length > 0) {
url += `&objectIds=${objectIds}`;
}
if (isDefinedAndNotNull(searchText) && !isEmptyStr(searchText)) {
url += `&searchText=${searchText}`;
}
return this.http.get<Array<ObjectLwM2M>>(url, defaultHttpOptionsFromConfig(config));
} }
public getLwm2mBootstrapSecurityInfo(securityMode: string, bootstrapServerIs: boolean, config?: RequestConfig): Observable<ServerSecurityConfig> { public getLwm2mBootstrapSecurityInfo(securityMode: string, bootstrapServerIs: boolean,
return this.http.get<ServerSecurityConfig>(`/api/lwm2m/deviceProfile/bootstrap/${securityMode}/${bootstrapServerIs}`, defaultHttpOptionsFromConfig(config)); config?: RequestConfig): Observable<ServerSecurityConfig> {
return this.http.get<ServerSecurityConfig>(
`/api/lwm2m/deviceProfile/bootstrap/${securityMode}/${bootstrapServerIs}`,
defaultHttpOptionsFromConfig(config)
);
} }
public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ObjectLwM2M>> { public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ObjectLwM2M>> {
return this.http.get<PageData<ObjectLwM2M>>(`/api/lwm2m/deviceProfile/objects${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); return this.http.get<PageData<ObjectLwM2M>>(
`/api/lwm2m/deviceProfile/objects${pageLink.toQuery()}`,
defaultHttpOptionsFromConfig(config)
);
} }
public saveDeviceProfile(deviceProfile: DeviceProfile, config?: RequestConfig): Observable<DeviceProfile> { public saveDeviceProfile(deviceProfile: DeviceProfile, config?: RequestConfig): Observable<DeviceProfile> {

3
ui-ngx/src/app/modules/home/components/device/device-credentials.component.html

@ -90,9 +90,8 @@
</mat-error> </mat-error>
<div mat-dialog-actions fxLayoutAlign="center center"> <div mat-dialog-actions fxLayoutAlign="center center">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="false"
matTooltip="{{'device.lwm2m-value-edit-tip' | translate }}" matTooltip="{{'device.lwm2m-value-edit-tip' | translate }}"
(click)="openSecurityInfoLwM2mDialog($event, deviceCredentialsFormGroup.get('credentialsValue').value, deviceCredentialsFormGroup.get('credentialsId').value )" (click)="openSecurityInfoLwM2mDialog($event)"
> >
{{'device.lwm2m-value-edit' | translate }} {{'device.lwm2m-value-edit' | translate }}
</button> </button>

37
ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
@ -41,8 +41,7 @@ import {
DeviceCredentialsDialogLwm2mData, DeviceCredentialsDialogLwm2mData,
END_POINT, END_POINT,
getDefaultSecurityConfig, getDefaultSecurityConfig,
JSON_ALL_CONFIG, JSON_ALL_CONFIG
SecurityConfigModels
} from '@home/pages/device/lwm2m/security-config.models'; } from '@home/pages/device/lwm2m/security-config.models';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -197,6 +196,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false});
break; break;
case DeviceCredentialsType.X509_CERTIFICATE: case DeviceCredentialsType.X509_CERTIFICATE:
case DeviceCredentialsType.LWM2M_CREDENTIALS:
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
@ -211,13 +211,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
break; break;
case DeviceCredentialsType.LWM2M_CREDENTIALS:
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false});
break;
} }
} }
@ -245,27 +238,39 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
}); });
} }
openSecurityInfoLwM2mDialog($event: Event, value: string, id: string): void { openSecurityInfoLwM2mDialog($event: Event): void {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
$event.preventDefault(); $event.preventDefault();
} }
let credentialsValue = this.deviceCredentialsFormGroup.get('credentialsValue').value;
if (credentialsValue === null || credentialsValue.length === 0) {
credentialsValue = getDefaultSecurityConfig();
} else {
credentialsValue = JSON.parse(credentialsValue);
}
const credentialsId = this.deviceCredentialsFormGroup.get('credentialsId').value || DEFAULT_END_POINT;
this.dialog.open<SecurityConfigComponent, DeviceCredentialsDialogLwm2mData, object>(SecurityConfigComponent, { this.dialog.open<SecurityConfigComponent, DeviceCredentialsDialogLwm2mData, object>(SecurityConfigComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
jsonAllConfig: (value === null || value.length === 0) ? getDefaultSecurityConfig() as SecurityConfigModels : JSON.parse(value) as SecurityConfigModels, jsonAllConfig: credentialsValue,
endPoint: (id === null) ? DEFAULT_END_POINT : id, endPoint: credentialsId
isNew: (id === null || value === null || value.length === 0)
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {
if (res) { if (res) {
this.deviceCredentialsFormGroup.get('credentialsValue').patchValue((Object.keys(res[JSON_ALL_CONFIG]).length === 0 || JSON.stringify(res[JSON_ALL_CONFIG]) === "[{}]") ? null : JSON.stringify(res[JSON_ALL_CONFIG])); this.deviceCredentialsFormGroup.patchValue({
this.deviceCredentialsFormGroup.get('credentialsId').patchValue((Object.keys(res[END_POINT]).length === 0 || JSON.stringify(res[END_POINT]) === "[{}]") ? null : JSON.stringify(res[END_POINT]).split('\"').join('')); credentialsValue: this.isDefautLw2mResponse(res[JSON_ALL_CONFIG]) ? null : JSON.stringify(res[JSON_ALL_CONFIG]),
credentialsId: this.isDefautLw2mResponse(res[END_POINT]) ? null : JSON.stringify(res[END_POINT]).split('\"').join('')
});
this.deviceCredentialsFormGroup.get('credentialsValue').markAsDirty(); this.deviceCredentialsFormGroup.get('credentialsValue').markAsDirty();
} }
} }
); );
} }
private isDefautLw2mResponse(response: object): boolean {
return Object.keys(response).length === 0 || JSON.stringify(response) === '[{}]';
}
} }

1
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -307,7 +307,6 @@ import { Lwm2mProfileComponentsModule } from '@home/components/profile/device/lw
EditAlarmDetailsDialogComponent, EditAlarmDetailsDialogComponent,
DeviceProfileProvisionConfigurationComponent, DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent, AlarmScheduleComponent,
Lwm2mProfileComponentsModule,
SmsProviderConfigurationComponent, SmsProviderConfigurationComponent,
AwsSnsProviderConfigurationComponent, AwsSnsProviderConfigurationComponent,
TwilioSmsProviderConfigurationComponent TwilioSmsProviderConfigurationComponent

66
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html

@ -21,10 +21,9 @@
<div fxLayout="row" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutGap="8px">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'device-profile.lwm2m.mode' | translate }}</mat-label> <mat-label>{{ 'device-profile.lwm2m.mode' | translate }}</mat-label>
<mat-select formControlName="securityMode" <mat-select formControlName="securityMode">
[ngSwitch]="securityConfigLwM2MTypes">
<mat-option *ngFor="let securityMode of securityConfigLwM2MTypes" <mat-option *ngFor="let securityMode of securityConfigLwM2MTypes"
[value]="securityMode" > [value]="securityMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }}
</mat-option> </mat-option>
</mat-select> </mat-select>
@ -87,33 +86,40 @@
{{ 'device-profile.lwm2m.bootstrap-server' | translate }} {{ 'device-profile.lwm2m.bootstrap-server' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div [fxShow]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC"> <div *ngIf="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
<div serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509">
[fxShow]="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK || <mat-form-field class="mat-block">
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509"> <mat-label>{{ 'device-profile.lwm2m.server-public-key' | translate }}</mat-label>
<mat-form-field class="mat-block"> <textarea matInput
<mat-label>{{ 'device-profile.lwm2m.server-public-key' | translate }}</mat-label> #serverPublicKey
<textarea matInput type="text" rows="3" cols="1" formControlName="serverPublicKey" #serverPublicKey maxlength={{lenMaxServerPublicKey}} maxlength="{{lenMaxServerPublicKey}}"
matTooltip="{{'device-profile.lwm2m.server-public-key-tip' | translate}}" cdkTextareaAutosize
[required]="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK || cdkAutosizeMinRows="1"
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509"></textarea> cols="1" required
<mat-hint align="end">{{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}}</mat-hint> style="overflow:hidden"
<mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('required')"> formControlName="serverPublicKey"
{{ 'device-profile.lwm2m.server-public-key' | translate }} matTooltip="{{'device-profile.lwm2m.server-public-key-tip' | translate}}"
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> ></textarea>
</mat-error> <mat-hint align="end">{{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}}</mat-hint>
<mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('pattern') && <mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('required')">
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK"> {{ 'device-profile.lwm2m.server-public-key' | translate }}
{{ 'device-profile.lwm2m.client-key' | translate }} <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
<strong>{{ 'device-profile.lwm2m.pattern_hex_dec_182' | translate }}</strong> </mat-error>
</mat-error> <mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('pattern') &&
<mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('pattern') && (serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509"> serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509)">
{{ 'device-profile.lwm2m.client-key' | translate }} {{ 'device-profile.lwm2m.server-public-key' | translate }}
<strong>{{ 'device-profile.lwm2m.pattern_hex_dec' | translate }}</strong> <strong>{{ translate.get('device-profile.lwm2m.pattern_hex_dec', {
</mat-error> count: 0}) | async }}</strong>
</mat-form-field> </mat-error>
</div> <mat-error *ngIf="(serverFormGroup.get('serverPublicKey').hasError('maxlength') ||
serverFormGroup.get('serverPublicKey').hasError('minlength')) &&
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK">
{{ 'device-profile.lwm2m.server-public-key' | translate }}
<strong>{{ translate.get('device-profile.lwm2m.pattern_hex_dec', {
count: lenMaxServerPublicKey }) | async }}</strong>
</mat-error>
</mat-form-field>
</div> </div>
</div> </div>
</div> </div>

129
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts

@ -14,36 +14,31 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, Inject, Input, OnInit } from "@angular/core"; import { Component, forwardRef, Inject, Input } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgModel, Validators
} from "@angular/forms";
import { import {
SECURITY_CONFIG_MODE, DEFAULT_CLIENT_HOLD_OFF_TIME,
SECURITY_CONFIG_MODE_NAMES, DEFAULT_ID_SERVER,
KEY_PUBLIC_REGEXP_PSK,
ServerSecurityConfig,
LEN_MAX_PUBLIC_KEY_PSK,
LEN_MAX_PUBLIC_KEY_RPK_X509,
KEY_PUBLIC_REGEXP_X509,
DEFAULT_PORT_BOOTSTRAP_NO_SEC, DEFAULT_PORT_BOOTSTRAP_NO_SEC,
DEFAULT_PORT_SERVER_NO_SEC, DEFAULT_PORT_SERVER_NO_SEC,
DEFAULT_CLIENT_HOLD_OFF_TIME, KEY_REGEXP_HEX_DEC,
DEFAULT_ID_SERVER LEN_MAX_PUBLIC_KEY_RPK,
} from "./profile-config.models"; LEN_MAX_PUBLIC_KEY_X509,
import { Store } from "@ngrx/store"; SECURITY_CONFIG_MODE,
import { AppState } from "@core/core.state"; SECURITY_CONFIG_MODE_NAMES,
import { coerceBooleanProperty } from "@angular/cdk/coercion"; ServerSecurityConfig
import { WINDOW } from "../../../../../../core/services/window.service"; } from './profile-config.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { WINDOW } from '@core/services/window.service';
import { pairwise, startWith } from 'rxjs/operators'; import { pairwise, startWith } from 'rxjs/operators';
import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceProfileService } from '@core/http/device-profile.service';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'tb-profile-lwm2m-device-config-server', selector: 'tb-profile-lwm2m-device-config-server',
templateUrl: './lwm2m-device-config-server.component.html', templateUrl: './lwm2m-device-config-server.component.html',
styleUrls: [],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -53,15 +48,17 @@ import { DeviceProfileService } from '@core/http/device-profile.service';
] ]
}) })
export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAccessor, Validators { export class Lwm2mDeviceConfigServerComponent implements ControlValueAccessor {
valuePrev = null as any;
private requiredValue: boolean; private requiredValue: boolean;
valuePrev = null;
serverFormGroup: FormGroup; serverFormGroup: FormGroup;
securityConfigLwM2MType = SECURITY_CONFIG_MODE; securityConfigLwM2MType = SECURITY_CONFIG_MODE;
securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE); securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE);
credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES; credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES;
lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_PSK; lenMinServerPublicKey = 0;
lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_RPK;
currentSecurityMode = null; currentSecurityMode = null;
@ -69,7 +66,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc
disabled: boolean; disabled: boolean;
@Input() @Input()
bootstrapServerIs: boolean bootstrapServerIs: boolean;
get required(): boolean { get required(): boolean {
return this.requiredValue; return this.requiredValue;
@ -81,10 +78,11 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc
} }
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
public translate: TranslateService,
public fb: FormBuilder, public fb: FormBuilder,
private deviceProfileService: DeviceProfileService, private deviceProfileService: DeviceProfileService,
@Inject(WINDOW) private window: Window) { @Inject(WINDOW) private window: Window) {
this.serverFormGroup = this.getServerGroup(); this.serverFormGroup = this.initServerGroup();
this.serverFormGroup.get('securityMode').valueChanges.pipe( this.serverFormGroup.get('securityMode').valueChanges.pipe(
startWith(null), startWith(null),
pairwise() pairwise()
@ -103,49 +101,45 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc
}); });
} }
ngOnInit(): void { private updateValueFields = (serverData: ServerSecurityConfig): void => {
} serverData.bootstrapServerIs = this.bootstrapServerIs;
updateValueFields(serverData: ServerSecurityConfig): void {
serverData['bootstrapServerIs'] = this.bootstrapServerIs;
this.serverFormGroup.patchValue(serverData, {emitEvent: false}); this.serverFormGroup.patchValue(serverData, {emitEvent: false});
this.serverFormGroup.get('bootstrapServerIs').disable(); this.serverFormGroup.get('bootstrapServerIs').disable();
const securityMode = this.serverFormGroup.get('securityMode').value as SECURITY_CONFIG_MODE; const securityMode = this.serverFormGroup.get('securityMode').value as SECURITY_CONFIG_MODE;
this.updateValidate(securityMode); this.updateValidate(securityMode);
} }
updateValidate(securityMode: SECURITY_CONFIG_MODE): void { private updateValidate = (securityMode: SECURITY_CONFIG_MODE): void => {
switch (securityMode) { switch (securityMode) {
case SECURITY_CONFIG_MODE.NO_SEC: case SECURITY_CONFIG_MODE.NO_SEC:
this.serverFormGroup.get('serverPublicKey').setValidators([]); this.setValidatorsNoSecPsk();
break; break;
case SECURITY_CONFIG_MODE.PSK: case SECURITY_CONFIG_MODE.PSK:
this.lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_PSK; this.setValidatorsNoSecPsk();
this.serverFormGroup.get('serverPublicKey').setValidators([]);
break; break;
case SECURITY_CONFIG_MODE.RPK: case SECURITY_CONFIG_MODE.RPK:
this.lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_RPK_X509; this.lenMinServerPublicKey = LEN_MAX_PUBLIC_KEY_RPK;
this.serverFormGroup.get('serverPublicKey').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_PSK)]); this.lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_RPK;
this.setValidatorsRpkX509();
break; break;
case SECURITY_CONFIG_MODE.X509: case SECURITY_CONFIG_MODE.X509:
this.lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_RPK_X509; this.lenMinServerPublicKey = 0;
this.serverFormGroup.get('serverPublicKey').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_X509)]); this.lenMaxServerPublicKey = LEN_MAX_PUBLIC_KEY_X509;
this.setValidatorsRpkX509();
break; break;
} }
this.checkValueWithNewValidate(); this.serverFormGroup.updateValueAndValidity();
} }
checkValueWithNewValidate(): void { private setValidatorsNoSecPsk = (): void => {
this.serverFormGroup.patchValue({ this.serverFormGroup.get('serverPublicKey').setValidators([]);
host: this.serverFormGroup.get('host').value, }
port: this.serverFormGroup.get('port').value,
bootstrapServerIs: this.serverFormGroup.get('bootstrapServerIs').value, private setValidatorsRpkX509 = (): void => {
serverPublicKey: this.serverFormGroup.get('serverPublicKey').value, this.serverFormGroup.get('serverPublicKey').setValidators([Validators.required,
clientHoldOffTime: this.serverFormGroup.get('clientHoldOffTime').value, Validators.pattern(KEY_REGEXP_HEX_DEC),
serverId: this.serverFormGroup.get('serverId').value, Validators.minLength(this.lenMinServerPublicKey),
bootstrapServerAccountTimeout: this.serverFormGroup.get('bootstrapServerAccountTimeout').value, Validators.maxLength(this.lenMaxServerPublicKey)]);
},
{emitEvent: true});
} }
writeValue(value: any): void { writeValue(value: any): void {
@ -154,18 +148,17 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc
} }
} }
private propagateChange = (v: any) => { private propagateChange = (v: any) => {};
};
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
private propagateChangeState(value: any): void { private propagateChangeState = (value: any): void => {
if (value !== undefined) { if (value !== undefined) {
if (this.valuePrev === null) { if (this.valuePrev === null) {
this.valuePrev = "init"; this.valuePrev = 'init';
} else if (this.valuePrev === "init") { } else if (this.valuePrev === 'init') {
this.valuePrev = value; this.valuePrev = value;
} else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) { } else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) {
this.valuePrev = value; this.valuePrev = value;
@ -191,21 +184,21 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
getServerGroup(): FormGroup { private initServerGroup = (): FormGroup => {
const port = (this.bootstrapServerIs) ? DEFAULT_PORT_BOOTSTRAP_NO_SEC : DEFAULT_PORT_SERVER_NO_SEC; const port = this.bootstrapServerIs ? DEFAULT_PORT_BOOTSTRAP_NO_SEC : DEFAULT_PORT_SERVER_NO_SEC;
return this.fb.group({ return this.fb.group({
host: [this.window.location.hostname, this.required ? [Validators.required] : []], host: [this.window.location.hostname, this.required ? Validators.required : ''],
port: [port, this.required ? [Validators.required] : []], port: [port, this.required ? Validators.required : ''],
bootstrapServerIs: [this.bootstrapServerIs, []], bootstrapServerIs: [this.bootstrapServerIs, ''],
securityMode: [this.fb.control(SECURITY_CONFIG_MODE.NO_SEC), []], securityMode: [this.fb.control(SECURITY_CONFIG_MODE.NO_SEC)],
serverPublicKey: ['', this.required ? [Validators.required] : []], serverPublicKey: ['', this.required ? Validators.required : ''],
clientHoldOffTime: [DEFAULT_CLIENT_HOLD_OFF_TIME, this.required ? [Validators.required] : []], clientHoldOffTime: [DEFAULT_CLIENT_HOLD_OFF_TIME, this.required ? Validators.required : ''],
serverId: [DEFAULT_ID_SERVER, this.required ? [Validators.required] : []], serverId: [DEFAULT_ID_SERVER, this.required ? Validators.required : ''],
bootstrapServerAccountTimeout: ['', this.required ? [Validators.required] : []], bootstrapServerAccountTimeout: ['', this.required ? Validators.required : ''],
}) });
} }
getLwm2mBootstrapSecurityInfo(mode: string) { private getLwm2mBootstrapSecurityInfo = (mode: string): void => {
this.deviceProfileService.getLwm2mBootstrapSecurityInfo(mode, this.serverFormGroup.get('bootstrapServerIs').value).subscribe( this.deviceProfileService.getLwm2mBootstrapSecurityInfo(mode, this.serverFormGroup.get('bootstrapServerIs').value).subscribe(
(serverSecurityConfig) => { (serverSecurityConfig) => {
this.serverFormGroup.patchValue({ this.serverFormGroup.patchValue({

250
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html

@ -15,126 +15,140 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="lwm2mDeviceProfileTransportConfFormGroup" style="padding-bottom: 16px;"> <section style="padding-bottom: 16px;" mat-dialog-content>
<div mat-dialog-content> <mat-tab-group dynamicHeight>
<mat-tab-group dynamicHeight> <mat-tab label="{{ 'device-profile.lwm2m.model-tab' | translate }}">
<mat-tab label="{{ 'device-profile.lwm2m.model-tab' | translate }}"> <ng-template matTabContent>
<div class="mat-padding"> <section [formGroup]="lwm2mDeviceProfileFormGroup">
<tb-profile-lwm2m-object-list <div class="mat-padding">
(addList)="addObjectsList($event)" <tb-profile-lwm2m-object-list
(removeList)="removeObjectsList($event)" (addList)="addObjectsList($event)"
[required]="required" (removeList)="removeObjectsList($event)"
formControlName="objectIds"> [required]="required"
</tb-profile-lwm2m-object-list> formControlName="objectIds">
</div> </tb-profile-lwm2m-object-list>
<div class="mat-padding"> </div>
<tb-profile-lwm2m-observe-attr-telemetry <div class="mat-padding">
[required]="required" <tb-profile-lwm2m-observe-attr-telemetry
formControlName="observeAttrTelemetry"> [required]="required"
</tb-profile-lwm2m-observe-attr-telemetry> formControlName="observeAttrTelemetry">
</div> </tb-profile-lwm2m-observe-attr-telemetry>
</mat-tab> </div>
<mat-tab label="{{ 'device-profile.lwm2m.bootstrap-tab' | translate }}"> </section>
<div class="mat-padding"> </ng-template>
<mat-accordion multi="true" class="mat-body-1"> </mat-tab>
<mat-expansion-panel> <mat-tab label="{{ 'device-profile.lwm2m.bootstrap-tab' | translate }}">
<mat-expansion-panel-header> <ng-template matTabContent>
<mat-panel-title> <section [formGroup]="lwm2mDeviceProfileFormGroup">
<div <div class="mat-padding">
class="tb-panel-title">{{ 'device-profile.lwm2m.servers' | translate | uppercase }}</div> <mat-accordion multi="true" class="mat-body-1">
</mat-panel-title> <mat-expansion-panel>
</mat-expansion-panel-header> <mat-expansion-panel-header>
<div fxLayout="column"> <mat-panel-title>
<div fxLayout="row" fxLayoutGap="8px"> <div class="tb-panel-title">{{ 'device-profile.lwm2m.servers' | translate | uppercase }}</div>
<mat-form-field fxFlex> </mat-panel-title>
<mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label> </mat-expansion-panel-header>
<input matInput type="number" formControlName="shortId" required> <ng-template matExpansionPanelContent>
<mat-error *ngIf="lwm2mDeviceProfileTransportConfFormGroup.get('shortId').hasError('required')"> <div fxLayout="column">
{{ 'device-profile.lwm2m.short-id' | translate }} <div fxLayout="row" fxLayoutGap="8px">
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> <mat-form-field fxFlex>
</mat-error> <mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label>
</mat-form-field> <input matInput type="number" formControlName="shortId" required>
<mat-form-field fxFlex> <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('shortId').hasError('required')">
<mat-label>{{ 'device-profile.lwm2m.lifetime' | translate }}</mat-label> {{ 'device-profile.lwm2m.short-id' | translate }}
<input matInput type="number" formControlName="lifetime" required> <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
<mat-error *ngIf="lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').hasError('required')"> </mat-error>
{{ 'device-profile.lwm2m.lifetime' | translate }} </mat-form-field>
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> <mat-form-field fxFlex>
</mat-error> <mat-label>{{ 'device-profile.lwm2m.lifetime' | translate }}</mat-label>
</mat-form-field> <input matInput type="number" formControlName="lifetime" required>
</div> <mat-error
<div fxLayout="row" fxLayoutGap="8px"> *ngIf="lwm2mDeviceProfileFormGroup.get('lifetime').hasError('required')">
<mat-form-field fxFlex> {{ 'device-profile.lwm2m.lifetime' | translate }}
<mat-label>{{ 'device-profile.lwm2m.default-min-period' | translate }}</mat-label> <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
<input matInput type="number" formControlName="defaultMinPeriod" required> </mat-error>
<mat-error *ngIf="lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').hasError('required')"> </mat-form-field>
{{ 'device-profile.lwm2m.default-min-period' | translate }} </div>
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> <div fxLayout="row" fxLayoutGap="8px">
</mat-error> <mat-form-field fxFlex>
</mat-form-field> <mat-label>{{ 'device-profile.lwm2m.default-min-period' | translate }}</mat-label>
<mat-form-field fxFlex> <input matInput type="number" formControlName="defaultMinPeriod" required>
<mat-label>{{ 'device-profile.lwm2m.binding' | translate }}</mat-label> <mat-error
<input matInput type="text" formControlName="binding" required> *ngIf="lwm2mDeviceProfileFormGroup.get('defaultMinPeriod').hasError('required')">
<mat-error *ngIf="lwm2mDeviceProfileTransportConfFormGroup.get('binding').hasError('required')"> {{ 'device-profile.lwm2m.default-min-period' | translate }}
{{ 'device-profile.lwm2m.binding' | translate }} <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> </mat-error>
</mat-error> </mat-form-field>
</mat-form-field> <mat-form-field fxFlex>
</div> <mat-label>{{ 'device-profile.lwm2m.binding' | translate }}</mat-label>
<div> <input matInput type="text" formControlName="binding" required>
<mat-checkbox formControlName="notifIfDisabled" color="primary"> <mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('binding').hasError('required')">
{{ 'device-profile.lwm2m.notif-if-disabled' | translate }} {{ 'device-profile.lwm2m.binding' | translate }}
</mat-checkbox> <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
</div> </mat-error>
</div> </mat-form-field>
</mat-expansion-panel> </div>
</mat-accordion> <div>
<mat-accordion multi="true" class="mat-body-1"> <mat-checkbox formControlName="notifIfDisabled" color="primary">
<mat-expansion-panel> {{ 'device-profile.lwm2m.notif-if-disabled' | translate }}
<mat-expansion-panel-header> </mat-checkbox>
<mat-panel-title> </div>
<div </div>
class="tb-panel-title">{{ 'device-profile.lwm2m.bootstrap-server' | translate | uppercase }}</div> </ng-template>
</mat-panel-title> </mat-expansion-panel>
</mat-expansion-panel-header> </mat-accordion>
<div class="mat-padding"> <mat-accordion multi="true" class="mat-body-1">
<tb-profile-lwm2m-device-config-server <mat-expansion-panel>
[required]="required" <mat-expansion-panel-header>
formControlName = "bootstrapServer" <mat-panel-title>
[bootstrapServerIs]=true> <div
</tb-profile-lwm2m-device-config-server> class="tb-panel-title">{{ 'device-profile.lwm2m.bootstrap-server' | translate | uppercase }}</div>
</div> </mat-panel-title>
</mat-expansion-panel> </mat-expansion-panel-header>
</mat-accordion> <ng-template matExpansionPanelContent>
<mat-accordion multi="true" class="mat-body-1"> <div class="mat-padding">
<mat-expansion-panel> <tb-profile-lwm2m-device-config-server
<mat-expansion-panel-header> [required]="required"
<mat-panel-title> formControlName="bootstrapServer"
<div [bootstrapServerIs]=true>
class="tb-panel-title">{{ 'device-profile.lwm2m.lwm2m-server' | translate | uppercase }}</div> </tb-profile-lwm2m-device-config-server>
</mat-panel-title> </div>
</mat-expansion-panel-header> </ng-template>
<div class="mat-padding"> </mat-expansion-panel>
<tb-profile-lwm2m-device-config-server </mat-accordion>
[required]="required" <mat-accordion multi="true" class="mat-body-1">
formControlName="lwm2mServer" <mat-expansion-panel>
[bootstrapServerIs]=false> <mat-expansion-panel-header>
</tb-profile-lwm2m-device-config-server> <mat-panel-title>
</div> <div class="tb-panel-title">{{ 'device-profile.lwm2m.lwm2m-server' | translate | uppercase }}</div>
</mat-expansion-panel> </mat-panel-title>
</mat-accordion> </mat-expansion-panel-header>
</div> <ng-template matExpansionPanelContent>
</mat-tab> <div class="mat-padding">
<mat-tab label="{{ 'device-profile.lwm2m.config-json-tab' | translate }}"> <tb-profile-lwm2m-device-config-server
<div class="mat-padding"> [required]="required"
formControlName="lwm2mServer"
[bootstrapServerIs]=false>
</tb-profile-lwm2m-device-config-server>
</div>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>
</section>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'device-profile.lwm2m.config-json-tab' | translate }}">
<ng-template matTabContent>
<section [formGroup]="lwm2mDeviceConfigFormGroup" class="mat-padding">
<tb-json-object-edit <tb-json-object-edit
[required]="required" [required]="required"
[sort] = "sortFunction" [sort]="sortFunction"
label="{{ 'device-profile.transport-type-lwm2m' | translate }}" label="{{ 'device-profile.transport-type-lwm2m' | translate }}"
formControlName="configurationJson"> formControlName="configurationJson">
</tb-json-object-edit> </tb-json-object-edit>
</div> </section>
</mat-tab> </ng-template>
</mat-tab-group> </mat-tab>
</div> </mat-tab-group>
</form> </section>

518
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts

@ -14,58 +14,56 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { DeviceProfileTransportConfiguration, DeviceTransportType } from '@shared/models/device.models';
DeviceProfileTransportConfiguration, import { Component, forwardRef, Inject, Input } from '@angular/core';
DeviceTransportType
} from '@shared/models/device.models';
import {
Component,
forwardRef, Inject,
Input,
OnInit
} from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { import {
ATTR, ATTR,
getDefaultProfileConfig,
Instance,
KEY_NAME,
ObjectLwM2M,
OBSERVE, OBSERVE,
OBSERVE_ATTR, OBSERVE_ATTR,
TELEMETRY, ProfileConfigModels,
ObjectLwM2M, getDefaultProfileConfig, KEY_NAME, Instance ResourceLwM2M,
} from "./profile-config.models"; TELEMETRY
import { DeviceProfileService } from "../../../../../../core/http/device-profile.service"; } from './profile-config.models';
import { deepClone, isUndefined } from "../../../../../../core/utils"; import { DeviceProfileService } from '@core/http/device-profile.service';
import { WINDOW } from "../../../../../../core/services/window.service"; import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { WINDOW } from '@core/services/window.service';
import { JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point';
import { isNotNullOrUndefined } from 'codelyzer/util/isNotNullOrUndefined'; import { Direction } from '@shared/models/page/sort-order';
@Component({ @Component({
selector: 'tb-profile-lwm2m-device-transport-configuration', selector: 'tb-profile-lwm2m-device-transport-configuration',
templateUrl: './lwm2m-device-profile-transport-configuration.component.html', templateUrl: './lwm2m-device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{ providers: [{
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Lwm2mDeviceProfileTransportConfigurationComponent), useExisting: forwardRef(() => Lwm2mDeviceProfileTransportConfigurationComponent),
multi: true multi: true
}] }]
}) })
export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, Validators { export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators {
lwm2mDeviceProfileTransportConfFormGroup: FormGroup; private configurationValue: ProfileConfigModels;
observeAttr = OBSERVE_ATTR as string; private requiredValue: boolean;
observe = OBSERVE as string; private disabled = false;
attribute = ATTR as string;
telemetry = TELEMETRY as string; lwm2mDeviceProfileFormGroup: FormGroup;
keyName = KEY_NAME as string; lwm2mDeviceConfigFormGroup: FormGroup;
observeAttr = OBSERVE_ATTR;
observe = OBSERVE;
attribute = ATTR;
telemetry = TELEMETRY;
keyName = KEY_NAME;
bootstrapServers: string; bootstrapServers: string;
bootstrapServer: string; bootstrapServer: string;
lwm2mServer: string; lwm2mServer: string;
private configurationValue: {}; sortFunction: (key: string, value: object) => object;
private requiredValue: boolean;
private disabled = false as boolean;
sortFunction = this.sortObjectKeyPathJson;
get required(): boolean { get required(): boolean {
return this.requiredValue; return this.requiredValue;
@ -77,29 +75,33 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
} }
private propagateChange = (v: any) => { private propagateChange = (v: any) => {
}; }
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private fb: FormBuilder, private fb: FormBuilder,
private deviceProfileService: DeviceProfileService, private deviceProfileService: DeviceProfileService,
@Inject(WINDOW) private window: Window) { @Inject(WINDOW) private window: Window) {
this.lwm2mDeviceProfileTransportConfFormGroup = this.fb.group({ this.lwm2mDeviceProfileFormGroup = this.fb.group({
objectIds: [{}, Validators.required], objectIds: [null, Validators.required],
observeAttrTelemetry: [{'clientLwM2M': [] as ObjectLwM2M []}, Validators.required], observeAttrTelemetry: [null, Validators.required],
shortId: [null, Validators.required], shortId: [null, Validators.required],
lifetime: [null, Validators.required], lifetime: [null, Validators.required],
defaultMinPeriod: [null, Validators.required], defaultMinPeriod: [null, Validators.required],
notifIfDisabled: [true, []], notifIfDisabled: [true, []],
binding: ["U", Validators.required], binding: ['U', Validators.required],
bootstrapServer: [null, Validators.required], bootstrapServer: [null, Validators.required],
lwm2mServer: [null, Validators.required], lwm2mServer: [null, Validators.required],
configurationJson: [null, Validators.required],
}); });
this.lwm2mDeviceProfileTransportConfFormGroup.valueChanges.subscribe(value => { this.lwm2mDeviceConfigFormGroup = this.fb.group({
if (!this.disabled) { configurationJson: [null, Validators.required]
this.updateModel(); });
} this.lwm2mDeviceProfileFormGroup.valueChanges.subscribe((value) => {
this.updateDeviceProfileValue(value);
});
this.lwm2mDeviceConfigFormGroup.valueChanges.subscribe(() => {
this.updateModel();
}); });
this.sortFunction = this.sortObjectKeyPathJson;
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
@ -109,33 +111,34 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
ngOnInit() {
}
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
this.lwm2mDeviceProfileTransportConfFormGroup.disable({emitEvent: false}); this.lwm2mDeviceProfileFormGroup.disable({emitEvent: false});
this.lwm2mDeviceConfigFormGroup.disable({emitEvent: false});
} else { } else {
this.lwm2mDeviceProfileTransportConfFormGroup.enable({emitEvent: false}); this.lwm2mDeviceProfileFormGroup.enable({emitEvent: false});
this.lwm2mDeviceConfigFormGroup.enable({emitEvent: false});
} }
} }
writeValue(value: any | null): void { writeValue(value: any | null): void {
value = (Object.keys(value).length == 0) ? getDefaultProfileConfig() : value; this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value;
this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ this.lwm2mDeviceConfigFormGroup.patchValue({
configurationJson: value configurationJson: this.configurationValue
}, }, {emitEvent: false});
{emitEvent: false});
this.configurationValue = this.lwm2mDeviceProfileTransportConfFormGroup.getRawValue().configurationJson;
this.initWriteValue(); this.initWriteValue();
} }
private initWriteValue(): void { private initWriteValue = (): void => {
let modelValue = {"objectIds": null, "objectsList": []}; const modelValue = {objectIds: null, objectsList: []};
modelValue.objectIds = this.getObjectsFromJsonAllConfig(); modelValue.objectIds = this.getObjectsFromJsonAllConfig();
if (modelValue.objectIds !== null) { if (modelValue.objectIds !== null) {
this.deviceProfileService.getLwm2mObjects(modelValue.objectIds).subscribe( const sortOrder = {
property: 'id',
direction: Direction.ASC
};
this.deviceProfileService.getLwm2mObjects(sortOrder, modelValue.objectIds, null).subscribe(
(objectsList) => { (objectsList) => {
modelValue.objectsList = objectsList; modelValue.objectsList = objectsList;
this.updateWriteValue(modelValue); this.updateWriteValue(modelValue);
@ -146,240 +149,176 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
} }
} }
private updateWriteValue(value: any): void { private updateWriteValue = (value: any): void => {
let objectsList = deepClone(value.objectsList); const objectsList = value.objectsList;
this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ this.lwm2mDeviceProfileFormGroup.patchValue({
objectIds: value, objectIds: value,
observeAttrTelemetry: {clientLwM2M: this.getObserveAttrTelemetryObjects(objectsList)}, observeAttrTelemetry: this.getObserveAttrTelemetryObjects(objectsList),
shortId: this.configurationValue['bootstrap'].servers.shortId, shortId: this.configurationValue.bootstrap.servers.shortId,
lifetime: this.configurationValue['bootstrap'].servers.lifetime, lifetime: this.configurationValue.bootstrap.servers.lifetime,
defaultMinPeriod: this.configurationValue['bootstrap'].servers.defaultMinPeriod, defaultMinPeriod: this.configurationValue.bootstrap.servers.defaultMinPeriod,
notifIfDisabled: this.configurationValue['bootstrap'].servers.notifIfDisabled, notifIfDisabled: this.configurationValue.bootstrap.servers.notifIfDisabled,
binding: this.configurationValue['bootstrap'].servers.binding, binding: this.configurationValue.bootstrap.servers.binding,
bootstrapServer: this.configurationValue['bootstrap'].bootstrapServer, bootstrapServer: this.configurationValue.bootstrap.bootstrapServer,
lwm2mServer: this.configurationValue['bootstrap'].lwm2mServer lwm2mServer: this.configurationValue.bootstrap.lwm2mServer
}, },
{emitEvent: false}); {emitEvent: false});
} }
private updateModel() { private updateModel = (): void => {
let configuration: DeviceProfileTransportConfiguration = null; let configuration: DeviceProfileTransportConfiguration = null;
if (this.lwm2mDeviceProfileTransportConfFormGroup.valid) { if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) {
this.upDateValueToJson(); configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson;
configuration = this.lwm2mDeviceProfileTransportConfFormGroup.getRawValue().configurationJson;
configuration.type = DeviceTransportType.LWM2M; configuration.type = DeviceTransportType.LWM2M;
} }
this.propagateChange(configuration); this.propagateChange(configuration);
} }
private updateObserveAttrTelemetryObjectFormGroup(objectsList: ObjectLwM2M[]) { private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => {
this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ this.lwm2mDeviceProfileFormGroup.patchValue({
observeAttrTelemetry: {clientLwM2M: this.getObserveAttrTelemetryObjects(objectsList)} observeAttrTelemetry: this.getObserveAttrTelemetryObjects(objectsList)
}, },
{emitEvent: false}); {emitEvent: false});
this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").markAsPristine({
onlySelf: true
});
} }
upDateValueToJson(): void { private updateDeviceProfileValue(config): void {
this.upDateValueToJsonTab_0(); if (this.lwm2mDeviceProfileFormGroup.valid) {
this.upDateValueToJsonTab_1(); this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry.clientLwM2M);
} this.configurationValue.bootstrap.bootstrapServer = config.bootstrapServer;
this.configurationValue.bootstrap.lwm2mServer = config.lwm2mServer;
upDateValueToJsonTab_0(): void { const bootstrapServers = this.configurationValue.bootstrap.servers;
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").pristine) { bootstrapServers.shortId = config.shortId;
this.upDateObserveAttrTelemetryFromGroupToJson(this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value['clientLwM2M']); bootstrapServers.lifetime = config.lifetime;
this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").markAsPristine({ bootstrapServers.defaultMinPeriod = config.defaultMinPeriod;
onlySelf: true bootstrapServers.notifIfDisabled = config.notifIfDisabled;
}); bootstrapServers.binding = config.binding;
this.upDateJsonAllConfig();
}
}
upDateValueToJsonTab_1(): void {
this.upDateValueServersToJson();
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('bootstrapServer').pristine) {
this.configurationValue['bootstrap'].bootstrapServer = this.lwm2mDeviceProfileTransportConfFormGroup.get('bootstrapServer').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('bootstrapServer').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig();
}
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('lwm2mServer').pristine) {
this.configurationValue['bootstrap'].lwm2mServer = this.lwm2mDeviceProfileTransportConfFormGroup.get('lwm2mServer').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('lwm2mServer').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig();
}
}
upDateValueServersToJson(): void {
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('shortId').pristine) {
this.configurationValue['bootstrap'].servers.shortId = this.lwm2mDeviceProfileTransportConfFormGroup.get('shortId').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('shortId').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig();
}
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').pristine) {
this.configurationValue['bootstrap'].servers.lifetime = this.lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig();
}
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').pristine) {
this.configurationValue['bootstrap'].servers.defaultMinPeriod = this.lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig();
}
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('notifIfDisabled').pristine) {
this.configurationValue['bootstrap'].servers.notifIfDisabled = this.lwm2mDeviceProfileTransportConfFormGroup.get('notifIfDisabled').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('notifIfDisabled').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig();
}
if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('binding').pristine) {
this.configurationValue['bootstrap'].servers.binding = this.lwm2mDeviceProfileTransportConfFormGroup.get('binding').value;
this.lwm2mDeviceProfileTransportConfFormGroup.get('binding').markAsPristine({
onlySelf: true
});
this.upDateJsonAllConfig(); this.upDateJsonAllConfig();
this.updateModel();
} }
} }
getObserveAttrTelemetryObjects(listObject: ObjectLwM2M[]): ObjectLwM2M [] { private getObserveAttrTelemetryObjects = (listObject: ObjectLwM2M[]): object => {
let clientObserveAttr = deepClone(listObject) as ObjectLwM2M[]; const clientObserveAttrTelemetry = listObject;
if (this.configurationValue[this.observeAttr]) { if (this.configurationValue[this.observeAttr]) {
let observeArray = this.configurationValue[this.observeAttr][this.observe] as Array<string>; const observeArray = this.configurationValue[this.observeAttr][this.observe];
let attributeArray = this.configurationValue[this.observeAttr][this.attribute] as Array<string>; const attributeArray = this.configurationValue[this.observeAttr][this.attribute];
let telemetryArray = this.configurationValue[this.observeAttr][this.telemetry] as Array<string>; const telemetryArray = this.configurationValue[this.observeAttr][this.telemetry];
let keyNameJson = this.configurationValue[this.observeAttr][this.keyName] as JsonObject; const keyNameJson = this.configurationValue[this.observeAttr][this.keyName];
if (this.includesInstancesNo(attributeArray, telemetryArray, clientObserveAttr)) { if (this.includesNotZeroInstance(attributeArray, telemetryArray)) {
this.addInstances(attributeArray, telemetryArray, clientObserveAttr); this.addInstances(attributeArray, telemetryArray, clientObserveAttrTelemetry);
}
if (isDefinedAndNotNull(observeArray)) {
this.updateObserveAttrTelemetryObjects(observeArray, clientObserveAttrTelemetry, 'observe');
}
if (isDefinedAndNotNull(attributeArray)) {
this.updateObserveAttrTelemetryObjects(attributeArray, clientObserveAttrTelemetry, 'attribute');
}
if (isDefinedAndNotNull(telemetryArray)) {
this.updateObserveAttrTelemetryObjects(telemetryArray, clientObserveAttrTelemetry, 'telemetry');
}
if (isDefinedAndNotNull(keyNameJson)) {
this.updateKeyNameObjects(keyNameJson, clientObserveAttrTelemetry);
} }
if (observeArray) this.updateObserveAttrTelemetryObjects(observeArray, clientObserveAttr, "observe");
if (attributeArray) this.updateObserveAttrTelemetryObjects(attributeArray, clientObserveAttr, "attribute");
if (telemetryArray) this.updateObserveAttrTelemetryObjects(telemetryArray, clientObserveAttr, "telemetry");
if (keyNameJson) this.updateKeyNameObjects(deepClone(keyNameJson), clientObserveAttr);
} }
clientObserveAttr.forEach(obj => { return {clientLwM2M: clientObserveAttrTelemetry};
obj.instances.sort((a,b) => a.id - b.id);;
})
return clientObserveAttr;
} }
includesInstancesNo(attributeArray: Array<string>, telemetryArray: Array<string>, clientObserveAttr: ObjectLwM2M[]): boolean { private includesNotZeroInstance = (attribute: string[], telemetry: string[]): boolean => {
let isIdIndex = (element) => !element.includes("/0/"); const isNotZeroInstanceId = (instance) => !instance.includes('/0/');
return attributeArray.findIndex(isIdIndex) >= 0 || telemetryArray.findIndex(isIdIndex) >= 0 return attribute.some(isNotZeroInstanceId) || telemetry.some(isNotZeroInstanceId);
} }
addInstances(attributeArray: Array<string>, telemetryArray: Array<string>, clientObserveAttr: ObjectLwM2M[]): void { private addInstances = (attribute: string[], telemetry: string[], clientObserveAttrTelemetry: ObjectLwM2M[]): void => {
let attr = [] as Array<string>; const instancesPath = attribute.concat(telemetry)
[...attributeArray].filter(x => (!x.includes("/0/"))).forEach(x => { .filter(instance => !instance.includes('/0/'))
attr.push(this.convertPathToInstance(x)); .map(instance => instance.slice(1, instance.lastIndexOf('/')))
}); .sort(this.sortPath);
let telemetry = [] as Array<string>;
[...telemetryArray].filter(x => (!x.includes("/0/"))).forEach(x => { new Set(instancesPath).forEach(path => {
telemetry.push(this.convertPathToInstance(x)); const pathParameter = Array.from(path.split('/'), Number);
}); const objectLwM2M = clientObserveAttrTelemetry.find(x => x.id === pathParameter[0]);
let instancesNoZero = new Set(attr.concat(telemetry).sort());
instancesNoZero.forEach(path => {
let pathParameter = Array.from(path.split('/'), Number);
let objectLwM2M = clientObserveAttr.find(x => (x.id === pathParameter[0]));
if (objectLwM2M) { if (objectLwM2M) {
let instance = deepClone(objectLwM2M.instances[0]) as Instance; const instance = deepClone(objectLwM2M.instances[0]);
instance.id = pathParameter[1]; instance.id = pathParameter[1];
objectLwM2M.instances.push(instance); objectLwM2M.instances.push(instance);
} }
}) });
}
convertPathToInstance(path: string): string {
let newX = Array.from(path.substring(1).split('/'), Number);
return [newX[0], newX[1]].join("/");
} }
updateObserveAttrTelemetryObjects(isParameter: Array<string>, clientObserveAttr: ObjectLwM2M[], nameParameter: string): void { private updateObserveAttrTelemetryObjects = (parameters: string[], clientObserveAttrTelemetry: ObjectLwM2M[],
isParameter.forEach(attr => { nameParameter: string): void => {
let idKeys = Array.from(attr.substring(1).split('/'), Number); parameters.forEach(parameter => {
clientObserveAttr const [objectId, instanceId, resourceId] = Array.from(parameter.substring(1).split('/'), Number);
.forEach(e => { clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.id === objectId)
if (e.id == idKeys[0]) { .instances.find(itrInstance => itrInstance.id === instanceId)
let instance = e.instances.find(e => e.id == idKeys[1]); .resources.find(resource => resource.id === resourceId)
if (isNotNullOrUndefined(instance)) { [nameParameter] = true;
instance.resources.find(e => e.id == idKeys[2])[nameParameter] = true;
}
}
});
}); });
} }
updateKeyNameObjects(nameJson: JsonObject, clientObserveAttr: ObjectLwM2M[]): void { private updateKeyNameObjects = (nameJson: JsonObject, clientObserveAttrTelemetry: ObjectLwM2M[]): void => {
let keyName = JSON.parse(JSON.stringify(nameJson)); const keyName = JSON.parse(JSON.stringify(nameJson));
Object.keys(keyName).forEach(function (key) { Object.keys(keyName).forEach(key => {
let idKeys = Array.from(key.substring(1).split('/'), Number); const [objectId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), Number);
clientObserveAttr clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.id === objectId)
.forEach(e => { .instances.find(instance => instance.id === instanceId)
if (e.id == idKeys[0]) { .resources.find(resource => resource.id === resourceId)
e.instances.find(e => e.id == idKeys[1]).resources .keyName = keyName[key];
.find(e => e.id == idKeys[2]).keyName = keyName[key];
}
});
}); });
} }
upDateObserveAttrTelemetryFromGroupToJson(val: ObjectLwM2M []): void { private updateObserveAttrTelemetryFromGroupToJson = (val: ObjectLwM2M[]): void => {
let observeArray = [] as Array<string>; const observeArray: Array<string> = [];
let attributeArray = [] as Array<string>; const attributeArray: Array<string> = [];
let telemetryArray = [] as Array<string>; const telemetryArray: Array<string> = [];
let observeJson = JSON.parse(JSON.stringify(val)); const keyNameNew = {};
const observeJson: ObjectLwM2M[] = JSON.parse(JSON.stringify(val));
const paths = new Set<string>();
let pathObj; let pathObj;
let pathInst; let pathInst;
let pathRes let pathRes;
observeJson.forEach(obj => { observeJson.forEach(obj => {
Object.entries(obj).forEach(([key, value]) => { for (const [key, value] of Object.entries(obj)) {
if (key === 'id') { if (key === 'id') {
pathObj = value; pathObj = value;
} }
if (key === 'instances') { if (key === 'instances') {
let instancesJson = JSON.parse(JSON.stringify(value)) as []; const instancesJson = value as Instance[];
if (instancesJson.length > 0) { if (instancesJson.length > 0) {
instancesJson.forEach(instance => { instancesJson.forEach(instance => {
Object.entries(instance).forEach(([key, value]) => { for (const [instanceKey, instanceValue] of Object.entries(instance)) {
if (key === 'id') { if (instanceKey === 'id') {
pathInst = value; pathInst = instanceValue;
} }
if (key === 'resources') { if (instanceKey === 'resources') {
let resourcesJson = JSON.parse(JSON.stringify(value)) as []; const resourcesJson = instanceValue as ResourceLwM2M[];
if (resourcesJson.length > 0) { if (resourcesJson.length > 0) {
resourcesJson.forEach(res => { resourcesJson.forEach(res => {
Object.entries(res).forEach(([key, value]) => { for (const [resourceKey, value] of Object.entries(res)) {
if (key === 'id') { if (resourceKey === 'id') {
// pathRes = value pathRes = `/${pathObj}/${pathInst}/${value}`;
pathRes = '/' + pathObj + '/' + pathInst + '/' + value; } else if (resourceKey === 'observe' && value) {
} else if (key === 'observe' && value) { observeArray.push(pathRes);
observeArray.push(pathRes) } else if (resourceKey === 'attribute' && value) {
} else if (key === 'attribute' && value) { attributeArray.push(pathRes);
attributeArray.push(pathRes) paths.add(pathRes);
} else if (key === 'telemetry' && value) { } else if (resourceKey === 'telemetry' && value) {
telemetryArray.push(pathRes) telemetryArray.push(pathRes);
paths.add(pathRes);
}
else if (resourceKey === this.keyName && paths.has(pathRes)) {
console.warn(pathRes, value);
keyNameNew[pathRes] = value;
} }
}); }
}); });
} }
} }
}); }
}); });
} }
} }
}); }
}); });
if (isUndefined(this.configurationValue[this.observeAttr])) { if (isUndefined(this.configurationValue[this.observeAttr])) {
this.configurationValue[this.observeAttr] = { this.configurationValue[this.observeAttr] = {
@ -392,68 +331,34 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.configurationValue[this.observeAttr][this.attribute] = attributeArray; this.configurationValue[this.observeAttr][this.attribute] = attributeArray;
this.configurationValue[this.observeAttr][this.telemetry] = telemetryArray; this.configurationValue[this.observeAttr][this.telemetry] = telemetryArray;
} }
this.updateKeyName(); this.configurationValue[this.observeAttr][this.keyName] = this.sortObjectKeyPathJson('keyName', keyNameNew);
} }
sortObjectKeyPathJson(key, value) { sortObjectKeyPathJson = (key: string, value: object): object => {
if (key == "keyName") { if (key === 'keyName') {
return Object.keys(value).sort((a, b) => { return Object.keys(value).sort(this.sortPath).reduce((obj, keySort) => {
let aLC = Array.from(a.substring(1).split('/'), Number); obj[keySort] = value[keySort];
let bLC = Array.from(b.substring(1).split('/'), Number); return obj;
return aLC[0] == bLC[0] ? aLC[1] - bLC[1] : aLC[0] - bLC[0]; }, {});
}).reduce((r, k) => (r[k] = value[k], r), {}); } else if (key === 'observe' || key === 'attribute' || key === 'telemetry') {
return Object.values(value).sort(this.sortPath).reduce((arr, arrValue) => {
arr.push(arrValue);
return arr;
}, []);
} else { } else {
return value return value;
} }
} }
updateKeyName(): void { private sortPath = (a, b): number => {
let paths = new Set<string>(); return a.localeCompare(b, undefined, {
if (this.configurationValue[this.observeAttr][this.attribute]) { numeric: true,
this.configurationValue[this.observeAttr][this.attribute].forEach(path => { sensitivity: 'base'
paths.add(path);
});
}
if (this.configurationValue[this.observeAttr][this.telemetry]) {
this.configurationValue[this.observeAttr][this.telemetry].forEach(path => {
paths.add(path);
});
}
let keyNameNew = {};
paths.forEach(path => {
let pathParameter = this.findIndexsForIds(path);
if (pathParameter.length === 3) {
let value = this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value['clientLwM2M'][pathParameter[0]].instances[pathParameter[1]].resources[pathParameter[2]][this.keyName];
keyNameNew[path] = value;
}
}); });
this.configurationValue[this.observeAttr][this.keyName] = this.sortObjectKeyPathJson("keyName", keyNameNew);
} }
findIndexsForIds(path: string): number[] { private getObjectsFromJsonAllConfig = (): number[] => {
let pathParameter = Array.from(path.substring(1).split('/'), Number); const objectsIds = new Set<number>();
let pathParameterIndexes = [] as number[];
let objectsOld = deepClone(this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value.clientLwM2M) as ObjectLwM2M[];
let isIdIndex = (element) => element.id === pathParameter[0];
let objIndex = objectsOld.findIndex(isIdIndex);
if (objIndex >= 0) {
pathParameterIndexes.push(objIndex);
isIdIndex = (element) => element.id === pathParameter[1];
let instIndex = objectsOld[objIndex].instances.findIndex(isIdIndex);
if (instIndex >= 0) {
pathParameterIndexes.push(instIndex);
isIdIndex = (element) => element.id === pathParameter[2];
let resIndex = objectsOld[objIndex].instances[instIndex].resources.findIndex(isIdIndex);
if (resIndex >= 0) {
pathParameterIndexes.push(resIndex);
}
}
}
return pathParameterIndexes;
}
getObjectsFromJsonAllConfig(): number [] {
let objectsIds = new Set<number>();
if (this.configurationValue[this.observeAttr]) { if (this.configurationValue[this.observeAttr]) {
if (this.configurationValue[this.observeAttr][this.observe]) { if (this.configurationValue[this.observeAttr][this.observe]) {
this.configurationValue[this.observeAttr][this.observe].forEach(obj => { this.configurationValue[this.observeAttr][this.observe].forEach(obj => {
@ -474,50 +379,45 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
return (objectsIds.size > 0) ? Array.from(objectsIds) : null; return (objectsIds.size > 0) ? Array.from(objectsIds) : null;
} }
upDateJsonAllConfig(): void { private upDateJsonAllConfig = (): void => {
this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ this.lwm2mDeviceConfigFormGroup.patchValue({
configurationJson: this.configurationValue configurationJson: this.configurationValue
}, {emitEvent: false}); }, {emitEvent: false});
this.lwm2mDeviceProfileTransportConfFormGroup.markAsPristine({
onlySelf: true
});
} }
addObjectsList(value: ObjectLwM2M[]): void { addObjectsList = (value: ObjectLwM2M[]): void => {
this.updateObserveAttrTelemetryObjectFormGroup(deepClone(value)); this.updateObserveAttrTelemetryObjectFormGroup(value);
} }
removeObjectsList(value: ObjectLwM2M): void { removeObjectsList = (value: ObjectLwM2M): void => {
let objectsOld = deepClone(this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value.clientLwM2M); const objectsOld = this.lwm2mDeviceProfileFormGroup.get('observeAttrTelemetry').value.clientLwM2M;
const isIdIndex = (element) => element.id === value.id; const isIdIndex = (element) => element.id === value.id;
let index = objectsOld.findIndex(isIdIndex); const index = objectsOld.findIndex(isIdIndex);
if (index >= 0) { if (index >= 0) {
objectsOld.splice(index, 1); objectsOld.splice(index, 1);
} }
this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
this.removeObserveAttrTelemetryFromJson(this.observe, value.id); this.removeObserveAttrTelemetryFromJson(this.observe, value.id);
this.removeObserveAttrTelemetryFromJson(this.telemetry, value.id); this.removeObserveAttrTelemetryFromJson(this.telemetry, value.id);
this.removeObserveAttrTelemetryFromJson(this.attribute, value.id); this.removeObserveAttrTelemetryFromJson(this.attribute, value.id);
this.removeObserveAttrTelemetryFromJson(this.attribute, value.id);
this.removeKeyNameFromJson(value.id); this.removeKeyNameFromJson(value.id);
this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
this.upDateJsonAllConfig(); this.upDateJsonAllConfig();
} }
removeObserveAttrTelemetryFromJson(observeAttrTel: string, id: number): void { private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, id: number): void => {
let isIdIndex = (element) => Array.from(element.substring(1).split('/'), Number)[0] === id; const isIdIndex = (element) => element.startsWith(`/${id}`);
let index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex); let index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex);
while (index >= 0) { while (index >= 0) {
this.configurationValue[this.observeAttr][observeAttrTel].splice(index, 1); this.configurationValue[this.observeAttr][observeAttrTel].splice(index, 1);
index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex); index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex, index);
} }
} }
removeKeyNameFromJson(id: number): void { private removeKeyNameFromJson = (id: number): void => {
let keyNmaeJson = this.configurationValue[this.observeAttr][this.keyName]; const keyNameJson = this.configurationValue[this.observeAttr][this.keyName];
Object.keys(keyNmaeJson).forEach(function (key) { Object.keys(keyNameJson).forEach(key => {
let idKey = Array.from(key.substring(1).split('/'), Number)[0]; if (key.startsWith(`/${id}`)) {
if (idKey === id) { delete keyNameJson[key];
delete keyNmaeJson[key];
} }
}); });
} }
@ -527,10 +427,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
if (!isPath) { if (!isPath) {
isPath = this.findPathInJson(path, this.telemetry); isPath = this.findPathInJson(path, this.telemetry);
} }
return (isPath) ? true : false; return !!isPath;
} }
findPathInJson(path: string, side: string): string { private findPathInJson = (path: string, side: string): string => {
if (this.configurationValue[this.observeAttr]) { if (this.configurationValue[this.observeAttr]) {
if (this.configurationValue[this.observeAttr][side]) { if (this.configurationValue[this.observeAttr][side]) {
return this.configurationValue[this.observeAttr][side].find( return this.configurationValue[this.observeAttr][side].find(

79
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.html

@ -15,43 +15,44 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field appearance="standard" [formGroup]="lwm2mObjectListFormGroup" class="lwm2m-instances-list mat-block"> <section [formGroup]="instancesListFormGroup" class="lwm2m-instances-list">
<mat-label>{{ 'device-profile.lwm2m.instances-list' | translate }}</mat-label> <mat-form-field class="mat-block">
<mat-chip-list #chipList formControlName="instancesIdsList"> <mat-label>{{ 'device-profile.lwm2m.instances-list' | translate }}</mat-label>
<mat-chip <mat-chip-list>
*ngFor="let instanceIdInputFor of instancesIdsList" <mat-chip
[selectable]="true" *ngFor="let instanceId of instancesId"
[removable]="true" [selectable]="true"
(removed)="remove(instanceIdInputFor)"> [removable]="true"
{{instanceIdInputFor}} (removed)="remove(instanceId)">
<mat-icon matChipRemove>close</mat-icon> {{instanceId}}
</mat-chip> <mat-icon matChipRemove>close</mat-icon>
<mat-form-field class="mat-block" fxFlexFill style="width: auto"> </mat-chip>
<mat-label>{{ 'device-profile.lwm2m.instances-input' | translate }}</mat-label> </mat-chip-list>
<input fxFlexFill matInput type="number" </mat-form-field>
placeholder="{{'device-profile.lwm2m.instances-input-holder' | translate}}" <mat-form-field class="mat-block">
formControlName="instanceIdInput" <mat-label>{{ 'device-profile.lwm2m.instances-input' | translate }}</mat-label>
#instanceIdInput <input fxFlexFill matInput type='text'
(focusin)="onFocus()" placeholder="{{'device-profile.lwm2m.instances-input-holder' | translate}}"
(click)="add(instanceIdInput.valueAsNumber)" formControlName="instanceIdInput">
(keydown)="onkeydown($event)" <button mat-icon-button matSuffix
[matChipInputFor]="chipList"> (click)="add()" type="button">
<mat-error *ngIf="lwm2mObjectListFormGroup.get('instanceIdInput').hasError('min')"> <mat-icon>add</mat-icon>
{{ translate.get('device-profile.lwm2m.valid-id-instance-no-min', { </button>
instance: instanceIdInput.value.toString(), min: instanceIdValueMin.toString() <mat-error *ngIf="instancesListFormGroup.get('instanceIdInput').hasError('min')">
}) | async }} {{ translate.get('device-profile.lwm2m.valid-id-instance', {
</mat-error> count: 2, instance: instanceId, min: instanceIdValueMin
<mat-error *ngIf="lwm2mObjectListFormGroup.get('instanceIdInput').hasError('max')"> }) | async }}
{{ translate.get('device-profile.lwm2m.valid-id-instance-no-max', { </mat-error>
instance: instanceIdInput.value.toString(), max: instanceIdValueMax.toString() <mat-error *ngIf="instancesListFormGroup.get('instanceIdInput').hasError('max')">
}) | async }} {{ translate.get('device-profile.lwm2m.valid-id-instance', {
</mat-error> count: 1, instance: instanceId, max: instanceIdValueMax
<mat-error *ngIf="lwm2mObjectListFormGroup.get('instanceIdInput').errors?.notIsNaNValid"> }) | async }}
{{ translate.get('device-profile.lwm2m.instances-input-holder', { </mat-error>
instance: instanceIdInput.value.toString(), max: instanceIdValueMax.toString() <mat-error *ngIf="instancesListFormGroup.get('instanceIdInput').hasError('pattern')">
}) | async }} {{ translate.get('device-profile.lwm2m.valid-id-instance', {
</mat-error> count: 0, instance: instanceId, max: instanceIdValueMax
</mat-form-field> }) | async }}
</mat-chip-list> </mat-error>
</mat-form-field> </mat-form-field>
</section>

22
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.scss

@ -1,22 +0,0 @@
/**
* Copyright © 2016-2020 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.
*/
:host ::ng-deep {
mat-form-field.lwm2m-instances-list {
& > .mat-form-field-wrapper > .mat-form-field-underline {
display: none;
}
}
}

159
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts

@ -14,88 +14,48 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { Component, forwardRef, } from '@angular/core';
Component, import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
forwardRef, import { Store } from '@ngrx/store';
Input, import { AppState } from '@core/core.state';
OnInit, import { INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER } from './profile-config.models';
ViewChild, import { TranslateService } from '@ngx-translate/core';
ElementRef, import { DeviceProfileService } from '@core/http/device-profile.service';
} from "@angular/core";
import {
ControlValueAccessor,
FormBuilder,
FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR, Validators
} from "@angular/forms";
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { Store } from "@ngrx/store";
import { AppState } from "../../../../../../core/core.state";
import { MatChipList } from '@angular/material/chips';
import {
INSTANCES_ID_VALUE_MAX,
INSTANCES_ID_VALUE_MIN
} from "./profile-config.models";
import { TranslateService } from "@ngx-translate/core";
import { DeviceProfileService } from "../../../../../../core/http/device-profile.service";
@Component({ @Component({
selector: 'tb-profile-lwm2m-object-add-instances-list', selector: 'tb-profile-lwm2m-object-add-instances-list',
templateUrl: './lwm2m-object-add-instances-list.component.html', templateUrl: './lwm2m-object-add-instances-list.component.html',
styleUrls: ['./lwm2m-object-add-instances-list.component.scss'], styleUrls: [],
providers: [{ providers: [{
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Lwm2mObjectAddInstancesListComponent), useExisting: forwardRef(() => Lwm2mObjectAddInstancesListComponent),
multi: true multi: true
}] }]
}) })
export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccessor, OnInit, Validators { export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccessor {
lwm2mObjectListFormGroup: FormGroup; private disabled = false;
private requiredValue: boolean; private dirty = false;
private instancesIdsList: Set<number> | null;
filteredObjectsList: Array<number>;
private disabled = false as boolean;
private dirty = false as boolean;
instanceIdValueMin = INSTANCES_ID_VALUE_MIN as number
instanceIdValueMax = INSTANCES_ID_VALUE_MAX as number
get required(): boolean { instancesListFormGroup: FormGroup;
return this.requiredValue; instancesId = new Set<number>();
} instanceIdValueMin = INSTANCES_ID_VALUE_MIN;
instanceIdValueMax = INSTANCES_ID_VALUE_MAX;
@Input()
set required(value: boolean) {
const newVal = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) {
this.requiredValue = newVal;
this.updateValidators();
}
}
@ViewChild('instanceIdInput') instanceIdInput: ElementRef<HTMLInputElement>;
@ViewChild('chipList', {static: true}) chipList: MatChipList;
private propagateChange = (v: any) => { private propagateChange = (v: any) => { };
};
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
public translate: TranslateService, public translate: TranslateService,
private deviceProfileService: DeviceProfileService, private deviceProfileService: DeviceProfileService,
private fb: FormBuilder) { private fb: FormBuilder) {
this.lwm2mObjectListFormGroup = this.fb.group({ this.instancesListFormGroup = this.fb.group({
instancesIdsList: [null], instanceIdInput: [null, [
instanceIdInput: [null] Validators.min(this.instanceIdValueMin),
Validators.max(this.instanceIdValueMax),
Validators.pattern(KEY_REGEXP_NUMBER)]]
}); });
} }
updateValidators() {
this.lwm2mObjectListFormGroup.get('instanceIdInput').setValidators([
Validators.min(this.instanceIdValueMin),
Validators.max(this.instanceIdValueMax)]);
this.lwm2mObjectListFormGroup.get('instanceIdInput').updateValueAndValidity();
}
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
@ -103,77 +63,38 @@ export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccesso
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
ngOnInit() {
}
ngAfterViewInit(): void {
}
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
this.lwm2mObjectListFormGroup.disable({emitEvent: false}); this.instancesListFormGroup.disable({emitEvent: false});
} else { } else {
this.lwm2mObjectListFormGroup.enable({emitEvent: false}); this.instancesListFormGroup.enable({emitEvent: false});
} }
} }
writeValue(value: Set<number>): void { writeValue(value: Set<number>): void {
this.instancesIdsList = new Set<number>();
if (value && value.size) { if (value && value.size) {
value.forEach(item => this.instancesIdsList.add(item)); this.instancesId = value;
this.lwm2mObjectListFormGroup.get('instancesIdsList').setValue(this.instancesIdsList);
} }
this.updateValidators();
this.dirty = false; this.dirty = false;
} }
add(value: number): void { add = (): void => {
if (!isNaN(value) && this.lwm2mObjectListFormGroup.get('instanceIdInput').valid) { if (this.instancesListFormGroup.get('instanceIdInput').valid && Number.isFinite(Number(this.instanceId))) {
this.instancesIdsList.add(value); this.instancesId.add(Number(this.instanceId));
this.lwm2mObjectListFormGroup.get('instancesIdsList').setValue(this.instancesIdsList); this.instancesListFormGroup.get('instanceIdInput').setValue(null);
this.propagateChange(this.instancesIdsList); this.propagateChange(this.instancesId);
this.dirty = true this.dirty = true;
} }
this.clear();
}
remove(object: number) {
this.instancesIdsList.delete(object);
this.lwm2mObjectListFormGroup.get('instancesIdsList').setValue(this.instancesIdsList);
this.propagateChange(this.instancesIdsList);
this.dirty = true
this.clear();
}
displayFn(object?: number): number | undefined {
return object ? object : undefined;
} }
clear() { remove = (object: number): void => {
this.lwm2mObjectListFormGroup.get('instanceIdInput').patchValue(null, {emitEvent: true}); this.instancesId.delete(object);
this.instanceIdInput.nativeElement.value = ""; this.propagateChange(this.instancesId);
setTimeout(() => { this.dirty = true;
this.instanceIdInput.nativeElement.blur();
this.instanceIdInput.nativeElement.focus();
}, 0);
} }
onkeydown(e: KeyboardEvent) { get instanceId(): number {
if (e.keyCode == 189 || e.keyCode == 187 || e.keyCode == 109 || e.keyCode == 107) { return this.instancesListFormGroup.get('instanceIdInput').value;
return false;
} else if (e.keyCode == 8) {
if (this.lwm2mObjectListFormGroup.get('instanceIdInput').value == null) {
this.clear();
}
this.instanceIdInput.nativeElement.focus();
}
}
onFocus() {
if (this.dirty) {
this.lwm2mObjectListFormGroup.get('instanceIdInput').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
} }
} }

17
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="jsonFormGroup" (ngSubmit)="add()" style="min-width: 400px;"> <form [formGroup]="instancesFormGroup" (ngSubmit)="add()" style="min-width: 400px;">
<mat-toolbar fxLayout="row" color="primary"> <mat-toolbar fxLayout="row" color="primary">
<b><i>{{data.objectName}}</i></b> &nbsp;&nbsp; (object&nbsp;[<b>{{data.objectId}}</b>]) <b><i>{{data.objectName}}</i></b> &nbsp;&nbsp; (object&nbsp;[<b>{{data.objectId}}</b>])
<span fxFlex></span> <span fxFlex></span>
@ -30,24 +30,21 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<div class="mat-padding"> <tb-profile-lwm2m-object-add-instances-list
<tb-profile-lwm2m-object-add-instances-list formControlName="instancesIds">
formControlName="instancesIds"> </tb-profile-lwm2m-object-add-instances-list>
</tb-profile-lwm2m-object-add-instances-list>
</div>
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<span fxFlex></span>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()">
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || jsonFormGroup.invalid || !jsonFormGroup.dirty"> [disabled]="(isLoading$ | async) || instancesFormGroup.invalid || !instancesFormGroup.dirty">
{{ 'action.save' | translate }} {{ 'action.save' | translate }}
</button> </button>
</div> </div>

14
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts

@ -16,12 +16,11 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { ControlValueAccessor, FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
export interface Lwm2mObjectAddInstancesData { export interface Lwm2mObjectAddInstancesData {
instancesIds: Set<number>; instancesIds: Set<number>;
@ -36,9 +35,7 @@ export interface Lwm2mObjectAddInstancesData {
}) })
export class Lwm2mObjectAddInstancesComponent extends DialogComponent<Lwm2mObjectAddInstancesComponent, object> implements OnInit { export class Lwm2mObjectAddInstancesComponent extends DialogComponent<Lwm2mObjectAddInstancesComponent, object> implements OnInit {
jsonFormGroup: FormGroup; instancesFormGroup: FormGroup;
// title: string;
submitted = false; submitted = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -49,11 +46,10 @@ export class Lwm2mObjectAddInstancesComponent extends DialogComponent<Lwm2mObjec
super(store, router, dialogRef); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {
this.jsonFormGroup = this.fb.group({ this.instancesFormGroup = this.fb.group({
instancesIds: this.data.instancesIds instancesIds: this.data.instancesIds
}) });
} }
cancel(): void { cancel(): void {
@ -61,7 +57,7 @@ export class Lwm2mObjectAddInstancesComponent extends DialogComponent<Lwm2mObjec
} }
add(): void { add(): void {
this.data.instancesIds = this.jsonFormGroup.get('instancesIds').value this.data.instancesIds = this.instancesFormGroup.get('instancesIds').value;
this.dialogRef.close(this.data); this.dialogRef.close(this.data);
} }
} }

6
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html

@ -16,7 +16,7 @@
--> -->
<mat-form-field appearance="standard" [formGroup]="lwm2mObjectListFormGroup" class="mat-block"> <mat-form-field appearance="standard" [formGroup]="lwm2mListFormGroup" class="mat-block">
<mat-chip-list #chipList formControlName="objectsList"> <mat-chip-list #chipList formControlName="objectsList">
<mat-chip <mat-chip
*ngFor="let objectLwm2m of objectsList" *ngFor="let objectLwm2m of objectsList"
@ -41,7 +41,7 @@
class="tb-autocomplete" class="tb-autocomplete"
[displayWith]="displayObjectLwm2mFn"> [displayWith]="displayObjectLwm2mFn">
<mat-option *ngFor="let objectLwm2m of filteredObjectsList | async" [value]="objectLwm2m"> <mat-option *ngFor="let objectLwm2m of filteredObjectsList | async" [value]="objectLwm2m">
<span [innerHTML]="objectLwm2m.name | highlight:searchText"></span> <span [innerHTML]="objectLwm2m.id + ': ' + objectLwm2m.name | highlight:searchText"></span>
</mat-option> </mat-option>
<mat-option *ngIf="!(filteredObjectsList | async)?.length" [value]="null"> <mat-option *ngIf="!(filteredObjectsList | async)?.length" [value]="null">
<span> <span>
@ -49,7 +49,7 @@
</span> </span>
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
<mat-error *ngIf="lwm2mObjectListFormGroup.get('objectsList').hasError('required')"> <mat-error *ngIf="lwm2mListFormGroup.get('objectsList').hasError('required')">
{{ 'device-profile.lwm2m.object-list-empty' | translate }} {{ 'device-profile.lwm2m.object-list-empty' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>

191
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts

@ -14,39 +14,22 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core';
Component, import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
forwardRef, import { coerceBooleanProperty } from '@angular/cdk/coercion';
Input, import { Store } from '@ngrx/store';
OnInit, import { AppState } from '@core/core.state';
ViewChild, import { Observable } from 'rxjs';
ElementRef, import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
Output, import { ObjectLwM2M } from './profile-config.models';
EventEmitter import { TranslateService } from '@ngx-translate/core';
} from "@angular/core"; import { DeviceProfileService } from '@core/http/device-profile.service';
import { import { Direction } from '@shared/models/page/sort-order';
ControlValueAccessor, import { isDefined, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils';
FormBuilder,
FormGroup,
NG_VALUE_ACCESSOR, Validators
} from "@angular/forms";
import {coerceBooleanProperty} from "@angular/cdk/coercion";
import {Store} from "@ngrx/store";
import {AppState} from "../../../../../../core/core.state";
import {MatChipList} from '@angular/material/chips';
import {MatAutocomplete} from "@angular/material/autocomplete";
import {Observable} from "rxjs";
import {filter, map, mergeMap, share, tap} from 'rxjs/operators';
import {ObjectLwM2M} from "./profile-config.models";
import {TranslateService} from "@ngx-translate/core";
import {DeviceProfileService} from "../../../../../../core/http/device-profile.service";
import {PageLink} from "../../../../../../shared/models/page/page-link";
import {Direction} from "../../../../../../shared/models/page/sort-order";
@Component({ @Component({
selector: 'tb-profile-lwm2m-object-list', selector: 'tb-profile-lwm2m-object-list',
templateUrl: './lwm2m-object-list.component.html', templateUrl: './lwm2m-object-list.component.html',
styleUrls: [],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -56,14 +39,16 @@ import {Direction} from "../../../../../../shared/models/page/sort-order";
}) })
export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validators { export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validators {
lwm2mObjectListFormGroup: FormGroup;
private requiredValue: boolean; private requiredValue: boolean;
modelValue: Array<number> | null; private dirty = false;
private lw2mModels: Observable<Array<ObjectLwM2M>>;
private modelValue: Array<number> = [];
lwm2mListFormGroup: FormGroup;
objectsList: Array<ObjectLwM2M> = []; objectsList: Array<ObjectLwM2M> = [];
filteredObjectsList: Observable<Array<ObjectLwM2M>>; filteredObjectsList: Observable<Array<ObjectLwM2M>>;
private disabled = false as boolean; disabled = false;
searchText = '' as string; searchText = '';
private dirty = false as boolean;
get required(): boolean { get required(): boolean {
return this.requiredValue; return this.requiredValue;
@ -71,11 +56,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
@Input() @Input()
set required(value: boolean) { set required(value: boolean) {
const newVal = coerceBooleanProperty(value); this.requiredValue = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) { this.updateValidators();
this.requiredValue = newVal;
this.updateValidators();
}
} }
@Output() @Output()
@ -85,25 +67,23 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
removeList = new EventEmitter<any>(); removeList = new EventEmitter<any>();
@ViewChild('objectInput') objectInput: ElementRef<HTMLInputElement>; @ViewChild('objectInput') objectInput: ElementRef<HTMLInputElement>;
@ViewChild('objectAutocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('chipList', {static: true}) chipList: MatChipList;
private propagateChange = (v: any) => { private propagateChange = (v: any) => {
}; }
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
public translate: TranslateService, public translate: TranslateService,
private deviceProfileService: DeviceProfileService, private deviceProfileService: DeviceProfileService,
private fb: FormBuilder) { private fb: FormBuilder) {
this.lwm2mObjectListFormGroup = this.fb.group({ this.lwm2mListFormGroup = this.fb.group({
objectsList: [this.objectsList], objectsList: [this.objectsList],
objectLwm2m: [null, this.required ? [Validators.required] : []] objectLwm2m: ['']
}); });
} }
updateValidators() { private updateValidators = (): void => {
this.lwm2mObjectListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []); this.lwm2mListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []);
this.lwm2mObjectListFormGroup.get('objectLwm2m').updateValueAndValidity(); this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity();
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
@ -114,118 +94,117 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
} }
ngOnInit() { ngOnInit() {
this.filteredObjectsList = this.lwm2mObjectListFormGroup.get('objectLwm2m').valueChanges this.filteredObjectsList = this.lwm2mListFormGroup.get('objectLwm2m').valueChanges
.pipe( .pipe(
tap((value) => { tap((value) => {
if (value && typeof value !== 'string') { if (value && typeof value !== 'string') {
this.add(value); this.add(value);
} else if (value === null) { } else if (value === null) {
this.clear(this.objectInput.nativeElement.value); this.clear();
} }
}), }),
filter((value) => typeof value === 'string'), filter(searchText => isString(searchText)),
map((value) => value ? (typeof value === 'string' ? value : value.name) : ''), mergeMap(searchText => this.fetchListObjects(searchText))
mergeMap(name => this.fetchListObjects(name)),
share()
); );
} }
ngAfterViewInit(): void {
}
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
this.lwm2mObjectListFormGroup.disable({emitEvent: false}); this.lwm2mListFormGroup.disable({emitEvent: false});
if (isDefined(this.objectInput)) {
this.clear();
}
} else { } else {
this.lwm2mObjectListFormGroup.enable({emitEvent: false}); this.lwm2mListFormGroup.enable({emitEvent: false});
} }
} }
writeValue(value: any): void { writeValue(value: any): void {
this.searchText = ''; this.searchText = '';
if (value.hasOwnProperty("objectIds") && value["objectIds"] != null && value["objectIds"].length > 0) { if (isDefinedAndNotNull(value)) {
this.modelValue = [...value["objectIds"]]; if (Array.isArray(value.objectIds)) {
this.objectsList = value["objectsList"]; this.modelValue = value.objectIds;
this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); this.objectsList = value.objectsList;
} else { } else {
this.objectsList = []; this.objectsList = [];
this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); this.modelValue = [];
this.modelValue = null; }
} this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList, {emitEvents: false});
this.dirty = true; this.dirty = false;
}
reset() {
this.objectsList = [];
this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList);
this.modelValue = null;
if (this.objectInput) {
this.objectInput.nativeElement.value = '';
} }
this.lwm2mObjectListFormGroup.get('objectLwm2m').patchValue('', {emitEvent: false});
this.propagateChange(this.modelValue);
this.dirty = true;
} }
add(object: ObjectLwM2M): void { private add(object: ObjectLwM2M): void {
if (!this.modelValue || this.modelValue.indexOf(object.id) === -1) { if (this.modelValue.indexOf(object.id) === -1) {
if (!this.modelValue) {
this.modelValue = [];
}
this.modelValue.push(object.id); this.modelValue.push(object.id);
this.objectsList.push(object); this.objectsList.push(object);
this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList);
this.addList.next(this.objectsList); this.addList.next(this.objectsList);
} }
this.propagateChange(this.modelValue);
this.clear(); this.clear();
} }
remove(object: ObjectLwM2M) { remove = (object: ObjectLwM2M): void => {
let index = this.objectsList.indexOf(object); let index = this.objectsList.indexOf(object);
if (index >= 0) { if (index >= 0) {
this.objectsList.splice(index, 1); this.objectsList.splice(index, 1);
this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList);
index = this.modelValue.indexOf(object.id); index = this.modelValue.indexOf(object.id);
this.modelValue.splice(index, 1); this.modelValue.splice(index, 1);
this.removeList.next(object); this.removeList.next(object);
if (!this.modelValue.length) { if (!this.modelValue.length) {
this.modelValue = null; this.modelValue = null;
} }
this.propagateChange(this.modelValue);
this.clear(); this.clear();
} }
} }
displayObjectLwm2mFn(object?: ObjectLwM2M): string | undefined { displayObjectLwm2mFn = (object?: ObjectLwM2M): string | undefined => {
return object ? object.name : undefined; return object ? object.name : undefined;
} }
fetchListObjects(searchText?: string): Observable<Array<ObjectLwM2M>> { private fetchListObjects = (searchText?: string): Observable<Array<ObjectLwM2M>> => {
this.searchText = searchText; this.searchText = searchText;
const pageLink = new PageLink(10, 0, searchText, { const filters = {names: [], ids: []};
property: 'name', if (isDefinedAndNotNull(searchText) && !isEmptyStr(searchText)) {
direction: Direction.ASC const ids = searchText.match(/\d+/g);
}); filters.ids = ids !== null ? ids.map(Number) : filters.ids;
return this.deviceProfileService.getLwm2mObjectsPage(pageLink, {ignoreLoading: true}).pipe( filters.names = searchText.trim().toUpperCase().split(' ');
map(pageData => { }
let data = pageData.data; const predicate = objectLwM2M => filters.names.find(word => objectLwM2M.name.toUpperCase().includes(word))
return data; || filters.ids.includes(objectLwM2M.id);
}) return this.getLwM2mModels().pipe(
map(objectLwM2Ms => searchText ? objectLwM2Ms.filter(predicate) : objectLwM2Ms)
); );
} }
onFocus() { private getLwM2mModels(): Observable<Array<ObjectLwM2M>> {
if (this.dirty) { if (!this.lw2mModels) {
this.lwm2mObjectListFormGroup.get('objectLwm2m').updateValueAndValidity({onlySelf: true, emitEvent: true}); const sortOrder = {
this.dirty = false; property: 'name',
direction: Direction.ASC
};
this.lw2mModels = this.deviceProfileService.getLwm2mObjects(sortOrder).pipe(
publishReplay(1),
refCount()
);
}
return this.lw2mModels;
}
onFocus = (): void => {
if (!this.dirty) {
this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = true;
} }
} }
clear(value: string = '') { private clear = (value: string = ''): void => {
this.objectInput.nativeElement.value = value; this.objectInput.nativeElement.value = value;
this.lwm2mObjectListFormGroup.get('objectLwm2m').patchValue(value, {emitEvent: true}); this.searchText = '';
this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value);
setTimeout(() => { setTimeout(() => {
this.objectInput.nativeElement.blur(); this.objectInput.nativeElement.blur();
this.objectInput.nativeElement.focus(); this.objectInput.nativeElement.focus();

13
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html

@ -17,12 +17,10 @@
--> -->
<section [formGroup]="resourceFormGroup" class="mat-padding"> <section [formGroup]="resourceFormGroup" class="mat-padding">
<div fxLayout="row" fxFill formArrayName="resources" <div fxLayout="row" fxFill formArrayName="resources"
*ngFor="let resourceLwM2M of resourceLwm2mFormArray(resourceFormGroup).controls; let z = index; trackBy: trackByParams"> *ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams">
<div class="vertical-padding" fxLayout="column" fxFill [formGroupName]="z"> <div class="vertical-padding" fxLayout="column" fxFill [formGroupName]="i">
<div fxLayout="row" fxFill [fxShow]="!z"> <div fxLayout="row" fxFill [fxShow]="!i">
<div fxFlex="55"> <div fxFlex="55"></div>
<!-- <mat-label translate>device-profile.lwm2m.resource-label</mat-label>-->
</div>
<div fxFlex="10" fxLayoutAlign="start center"> <div fxFlex="10" fxLayoutAlign="start center">
<b> <b>
<mat-label translate>device-profile.lwm2m.observe-label</mat-label> <mat-label translate>device-profile.lwm2m.observe-label</mat-label>
@ -72,10 +70,9 @@
{{ 'device-profile.lwm2m.key-name_label' | translate }}</mat-label> {{ 'device-profile.lwm2m.key-name_label' | translate }}</mat-label>
<input matInput type="text" formControlName="keyName" required <input matInput type="text" formControlName="keyName" required
matTooltip="{{'device-profile.lwm2m.key-name-tip' | translate}}" matTooltip="{{'device-profile.lwm2m.key-name-tip' | translate}}"
(input)="updateValueKeyName($event, z)" (input)="updateValueKeyName($event, i)"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required')"> <mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required')">
{{ 'device-profile.lwm2m.key-name' | translate }} {{ 'device-profile.lwm2m.key-name' | translate }}
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
</mat-error> </mat-error>

72
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts

@ -14,20 +14,12 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from "@angular/core"; import { Component, forwardRef, Input } from '@angular/core';
import { import { ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
ControlValueAccessor, import { ResourceLwM2M } from '@home/components/profile/device/lwm2m/profile-config.models';
FormArray, FormBuilder,
FormGroup,
NG_VALUE_ACCESSOR, Validators
} from "@angular/forms";
import {
CAMEL_CASE_REGEXP,
ResourceLwM2M
} from '@home/components/profile/device/lwm2m/profile-config.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { deepClone, isUndefined } from '@core/utils'; import _ from 'lodash';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({ @Component({
@ -43,13 +35,13 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
] ]
}) })
export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueAccessor, OnInit, Validators { export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueAccessor {
resourceFormGroup : FormGroup;
disabled = false as boolean;
private requiredValue: boolean; private requiredValue: boolean;
resourceFormGroup: FormGroup;
disabled = false;
get required(): boolean { get required(): boolean {
return this.requiredValue; return this.requiredValue;
} }
@ -61,9 +53,12 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
this.requiredValue = newVal; this.requiredValue = newVal;
} }
} }
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private fb: FormBuilder) { private fb: FormBuilder) {
this.resourceFormGroup = this.fb.group({'resources': this.fb.array([])}); this.resourceFormGroup = this.fb.group({
resources: this.fb.array([])
});
this.resourceFormGroup.valueChanges.subscribe(value => { this.resourceFormGroup.valueChanges.subscribe(value => {
if (!this.disabled) { if (!this.disabled) {
this.propagateChangeState(value.resources); this.propagateChangeState(value.resources);
@ -71,9 +66,6 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
}); });
} }
ngOnInit(): void {
}
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
@ -85,10 +77,6 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
return this.resourceFormGroup.get('resources') as FormArray; return this.resourceFormGroup.get('resources') as FormArray;
} }
resourceLwm2mFormArray(instance: FormGroup): FormArray {
return instance.get('resources') as FormArray;
}
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
@ -98,31 +86,16 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
} }
} }
getDisabledState(): boolean { updateValueKeyName = (event: Event, index: number): void => {
return this.disabled; this.resourceFormArray.at(index).patchValue({keyName: _.camelCase((event.target as HTMLInputElement).value)});
}
updateValueKeyName (event: any, z: number): void {
this.resourceFormArray.at(z).patchValue( {keyName: this.keysToCamel(deepClone(event.target.value))} );
}
keysToCamel(o: any): string {
let val = o.split(" ");
let playStore = [];
val.forEach(function (item, k){
item = item.replace(CAMEL_CASE_REGEXP, '');
item = (k===0)? item.charAt(0).toLowerCase() + item.substr(1) : item.charAt(0).toUpperCase() + item.substr(1)
playStore.push(item);
});
return playStore.join('');
} }
createResourceLwM2M(resourcesLwM2MJson: ResourceLwM2M []): void { createResourceLwM2M(resourcesLwM2M: ResourceLwM2M[]): void {
if(resourcesLwM2MJson.length === this.resourceFormArray.length) { if (resourcesLwM2M.length === this.resourceFormArray.length) {
this.resourceFormArray.patchValue(resourcesLwM2MJson, {emitEvent: false}) this.resourceFormArray.patchValue(resourcesLwM2M, {emitEvent: false});
} else { } else {
this.resourceFormArray.clear(); this.resourceFormArray.clear();
resourcesLwM2MJson.forEach(resourceLwM2M => { resourcesLwM2M.forEach(resourceLwM2M => {
this.resourceFormArray.push(this.fb.group({ this.resourceFormArray.push(this.fb.group({
id: resourceLwM2M.id, id: resourceLwM2M.id,
name: resourceLwM2M.name, name: resourceLwM2M.name,
@ -131,18 +104,17 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
telemetry: resourceLwM2M.telemetry, telemetry: resourceLwM2M.telemetry,
keyName: [resourceLwM2M.keyName, Validators.required] keyName: [resourceLwM2M.keyName, Validators.required]
})); }));
}) });
} }
} }
private propagateChange = (v: any) => { private propagateChange = (v: any) => { };
};
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
private propagateChangeState(value: any): void { private propagateChangeState = (value: any): void => {
if (value && this.resourceFormGroup.valid) { if (value && this.resourceFormGroup.valid) {
this.propagateChange(value); this.propagateChange(value);
} else { } else {
@ -150,7 +122,7 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA
} }
} }
trackByParams(index: number): number { trackByParams = (index: number): number => {
return index; return index;
} }
} }

41
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html

@ -16,26 +16,27 @@
--> -->
<section [formGroup]="observeAttrTelemetryFormGroup" class="mat-padding"> <section [formGroup]="observeAttrTelemetryFormGroup">
<mat-accordion multi="true" class="mat-body-1" formArrayName="clientLwM2M"> <mat-accordion multi="true" class="mat-body-1" formArrayName="clientLwM2M">
<mat-expansion-panel <mat-expansion-panel
*ngFor="let objectLwM2M of clientLwM2MFormArray(observeAttrTelemetryFormGroup).controls; let i = index; trackBy: trackByParams" *ngFor="let objectLwM2M of clientLwM2MFormArray.controls; let i = index;"
[formGroupName]="i"> [formGroupName]="i">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title fxLayoutAlign="space-between"> <mat-panel-title fxLayoutAlign="start center">
<div class="tb-panel-title"><b><i>{{ objectLwM2M.get('name').value}}</i></b> (object [<b>{{ objectLwM2M.get('id').value}}</b>])</div> <b><i>{{ objectLwM2M.get('name').value}}</i></b>&nbsp;&lt;id: {{ objectLwM2M.get('id').value}}>
<button type="button"
*ngIf="!disabled"
[fxShow]="objectLwM2M.get('multiple').value"
mat-button mat-icon-button (click)="addInstances($event, objectLwM2M.value)"
matTooltip="{{'device-profile.lwm2m.add-instances-tip' | translate}}"
matTooltipPosition="above">
<mat-icon class="material-icons">{{'add'}}</mat-icon>
</button>
</mat-panel-title> </mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" *ngIf="!disabled">
<button type="button"
[fxShow]="objectLwM2M.get('multiple').value"
mat-button mat-icon-button (click)="addInstances($event, objectLwM2M.value)"
matTooltip="{{'device-profile.lwm2m.add-instances-tip' | translate}}"
matTooltipPosition="above">
<mat-icon class="material-icons">{{'add'}}</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div fxLayout="column" fxLayoutGap="8px" formArrayName="instances"> <ng-template matExpansionPanelContent>
<ng-template matExpansionPanelContent> <div fxLayout="column" fxLayoutGap="8px" formArrayName="instances">
<mat-expansion-panel <mat-expansion-panel
*ngFor="let instances of instancesLwm2mFormArray(objectLwM2M).controls; let y = index;" *ngFor="let instances of instancesLwm2mFormArray(objectLwM2M).controls; let y = index;"
[formGroupName]="y" [formGroupName]="y"
@ -50,7 +51,7 @@
</div> </div>
<div class="checkbox-padding" fxFlex="10"> <div class="checkbox-padding" fxFlex="10">
<mat-checkbox color="primary" <mat-checkbox color="primary"
[formControlName]="observe" [disabled]="this.disabled"
[checked]="getChecked(instances, 'observe')" [checked]="getChecked(instances, 'observe')"
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')" (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
@ -61,7 +62,8 @@
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="checkbox-padding" fxFlex="10"> <div class="checkbox-padding" fxFlex="10">
<mat-checkbox [formControlName]="attribute" color="warn" <mat-checkbox color="warn"
[disabled]="this.disabled"
[checked]="getChecked(instances, 'attribute')" [checked]="getChecked(instances, 'attribute')"
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')" (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')"
@ -72,7 +74,8 @@
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="checkbox-padding" fxFlex="10"> <div class="checkbox-padding" fxFlex="10">
<mat-checkbox [formControlName]="telemetry" color="primary" <mat-checkbox color="primary"
[disabled]="this.disabled"
[checked]="getChecked(instances, 'telemetry')" [checked]="getChecked(instances, 'telemetry')"
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')" (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')"
@ -95,8 +98,8 @@
</tb-profile-lwm2m-observe-attr-telemetry-resource> </tb-profile-lwm2m-observe-attr-telemetry-resource>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</ng-template> </div>
</div> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</mat-accordion> </mat-accordion>
</section> </section>

253
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts

@ -15,7 +15,7 @@
/// ///
import { Component, forwardRef, Input, OnInit, Output } from "@angular/core"; import { Component, forwardRef, Input } from '@angular/core';
import { import {
AbstractControl, AbstractControl,
ControlValueAccessor, ControlValueAccessor,
@ -24,27 +24,18 @@ import {
FormGroup, FormGroup,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
Validators Validators
} from "@angular/forms"; } from '@angular/forms';
import { Store } from "@ngrx/store"; import { Store } from '@ngrx/store';
import { AppState } from "@core/core.state"; import { AppState } from '@core/core.state';
import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { import { Instance, ObjectLwM2M, ResourceLwM2M } from './profile-config.models';
ATTR, import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils';
Instance,
ObjectLwM2M,
OBSERVE,
ResourceLwM2M,
TELEMETRY
} from "./profile-config.models";
import { isNotNullOrUndefined } from 'codelyzer/util/isNotNullOrUndefined';
import { deepClone, isUndefined } from '@core/utils';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { import {
Lwm2mObjectAddInstancesComponent, Lwm2mObjectAddInstancesComponent,
Lwm2mObjectAddInstancesData Lwm2mObjectAddInstancesData
} from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances.component'; } from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances.component';
import { Control } from 'leaflet';
@Component({ @Component({
selector: 'tb-profile-lwm2m-observe-attr-telemetry', selector: 'tb-profile-lwm2m-observe-attr-telemetry',
@ -59,22 +50,17 @@ import { Control } from 'leaflet';
] ]
}) })
export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, OnInit, Validators { export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor {
private requiredValue: boolean;
valuePrev = null as any; valuePrev = null as any;
observeAttrTelemetryFormGroup: FormGroup; observeAttrTelemetryFormGroup: FormGroup;
observe = OBSERVE as string;
attribute = ATTR as string;
telemetry = TELEMETRY as string;
private requiredValue: boolean;
get required(): boolean { get required(): boolean {
return this.requiredValue; return this.requiredValue;
} }
@Input()
disabled: boolean;
@Input() @Input()
set required(value: boolean) { set required(value: boolean) {
const newVal = coerceBooleanProperty(value); const newVal = coerceBooleanProperty(value);
@ -84,10 +70,13 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor,
} }
} }
@Input()
disabled: boolean;
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private fb: FormBuilder, private fb: FormBuilder,
private dialog: MatDialog, private dialog: MatDialog,
private translate: TranslateService) { public translate: TranslateService) {
this.observeAttrTelemetryFormGroup = this.fb.group({ this.observeAttrTelemetryFormGroup = this.fb.group({
clientLwM2M: this.fb.array([]) clientLwM2M: this.fb.array([])
}); });
@ -98,22 +87,17 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor,
}); });
} }
ngOnInit(): void { private propagateChange = (v: any) => { };
}
private propagateChange = (v: any) => {
};
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
private propagateChangeState(value: any): void { private propagateChangeState = (value: any): void => {
if (value) { if (value) {
if (this.valuePrev === null) { if (this.valuePrev === null) {
this.valuePrev = "init"; this.valuePrev = 'init';
} else if (this.valuePrev === "init") { } else if (this.valuePrev === 'init') {
this.valuePrev = value; this.valuePrev = value;
} else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) { } else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) {
this.valuePrev = value; this.valuePrev = value;
@ -139,92 +123,78 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor,
} }
} }
getDisabledState(): boolean {
return this.disabled;
}
writeValue(value: any): void { writeValue(value: any): void {
this.buildClientObjectsLwM2M(value.clientLwM2M); if (isDefinedAndNotNull(value)) {
this.buildClientObjectsLwM2M(value.clientLwM2M);
}
} }
private buildClientObjectsLwM2M(objectsLwM2M: ObjectLwM2M []): void { private buildClientObjectsLwM2M = (objectsLwM2M: ObjectLwM2M []): void => {
this.observeAttrTelemetryFormGroup.setControl('clientLwM2M', this.observeAttrTelemetryFormGroup.setControl('clientLwM2M',
this.createObjectsLwM2M(objectsLwM2M) this.createObjectsLwM2M(objectsLwM2M)
); );
} }
createObjectsLwM2M(objectsLwM2MJson: ObjectLwM2M []): FormArray { private createObjectsLwM2M = (objectsLwM2M: ObjectLwM2M[]): FormArray => {
return this.fb.array(objectsLwM2MJson.map((objectLwM2M) => { return this.fb.array(objectsLwM2M.map((objectLwM2M) => {
return this.fb.group({ return this.fb.group({
id: objectLwM2M.id, id: objectLwM2M.id,
name: objectLwM2M.name, name: objectLwM2M.name,
multiple: objectLwM2M.multiple, multiple: objectLwM2M.multiple,
mandatory: objectLwM2M.mandatory, mandatory: objectLwM2M.mandatory,
instances: this.createInstanceLwM2M(objectLwM2M.instances) instances: this.createInstanceLwM2M(objectLwM2M.instances)
}) });
})) }));
} }
createInstanceLwM2M(instanceLwM2MJson: Instance []): FormArray { private createInstanceLwM2M = (instancesLwM2M: Instance[]): FormArray => {
return this.fb.array(instanceLwM2MJson.map((instanceLwM2M, index) => { return this.fb.array(instancesLwM2M.map((instanceLwM2M) => {
return this.fb.group({ return this.fb.group({
id: instanceLwM2M.id, id: instanceLwM2M.id,
[this.observe]: {value: false, disabled: this.disabled},
[this.attribute]: {value: false, disabled: this.disabled},
[this.telemetry]: {value: false, disabled: this.disabled},
resources: {value: instanceLwM2M.resources, disabled: this.disabled} resources: {value: instanceLwM2M.resources, disabled: this.disabled}
}) });
})) }));
} }
clientLwM2MFormArray(formGroup: FormGroup): FormArray { get clientLwM2MFormArray(): FormArray {
return formGroup.get('clientLwM2M') as FormArray; return this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray;
} }
instancesLwm2mFormArray(objectLwM2M: AbstractControl): FormArray { instancesLwm2mFormArray = (objectLwM2M: AbstractControl): FormArray => {
return objectLwM2M.get('instances') as FormArray; return objectLwM2M.get('instances') as FormArray;
} }
changeInstanceResourcesCheckBox(value: boolean, instance: AbstractControl, type: string): void { changeInstanceResourcesCheckBox = (value: boolean, instance: AbstractControl, type: string): void => {
let resources = instance.get('resources').value as ResourceLwM2M [] const resources = deepClone(instance.get('resources').value as ResourceLwM2M[]);
resources.forEach(resource => resource[type] = value); resources.forEach(resource => resource[type] = value);
instance.get('resources').patchValue(resources); instance.get('resources').patchValue(resources);
this.propagateChange(this.observeAttrTelemetryFormGroup.value); this.propagateChange(this.observeAttrTelemetryFormGroup.value);
} }
updateValidators() { private updateValidators = (): void => {
this.observeAttrTelemetryFormGroup.get('clientLwM2M').setValidators(this.required ? [Validators.required] : []); this.observeAttrTelemetryFormGroup.get('clientLwM2M').setValidators(this.required ? Validators.required : []);
this.observeAttrTelemetryFormGroup.get('clientLwM2M').updateValueAndValidity(); this.observeAttrTelemetryFormGroup.get('clientLwM2M').updateValueAndValidity();
} }
trackByParams(index: number): number { trackByParams = (index: number, element: any): number => {
return index; return index;
} }
getIndeterminate(instance: AbstractControl, type: string) { getIndeterminate = (instance: AbstractControl, type: string): boolean => {
const resources = instance.get('resources').value as ResourceLwM2M []; const resources = instance.get('resources').value as ResourceLwM2M[];
if (isNotNullOrUndefined(resources)) { if (isDefinedAndNotNull(resources)) {
const isType = (element) => element[type] === true; const checkedResource = resources.filter(resource => resource[type]);
let checkedResource = resources.filter(isType); return checkedResource.length !== 0 && checkedResource.length !== resources.length;
if (checkedResource.length === 0) return false;
else if (checkedResource.length === resources.length) {
instance.patchValue({[type]: true});
return false;
} else return true;
} }
return false; return false;
} }
getChecked = (instance: AbstractControl, type: string): boolean => {
getChecked(instance: AbstractControl, type: string) { const resources = instance.get('resources').value as ResourceLwM2M[];
const resources = instance.get('resources').value as ResourceLwM2M []; return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]);
if (isNotNullOrUndefined(resources)) {
return resources.some(resource => resource[type]);
}
return false;
} }
getExpended(objectLwM2M: AbstractControl) { getExpended = (objectLwM2M: AbstractControl): boolean => {
return this.instancesLwm2mFormArray(objectLwM2M).length === 1; return this.instancesLwm2mFormArray(objectLwM2M).length === 1;
} }
@ -234,119 +204,96 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor,
* 2) Field in object: <Mandatory> == Mandatory/Optional * 2) Field in object: <Mandatory> == Mandatory/Optional
* If this field is “Multiple” then the number of Object Instance can be from 0 to many (Object Instance ID MAX_ID=65535). * If this field is “Multiple” then the number of Object Instance can be from 0 to many (Object Instance ID MAX_ID=65535).
* If this field is “Single” then the number of Object Instance can be from 0 to 1. (max count == 1) * If this field is “Single” then the number of Object Instance can be from 0 to 1. (max count == 1)
* If the Object field “Mandatory” is “Mandatory” and the Object field “Instances” is “Single” then, the number of Object Instance MUST be 1. * If the Object field “Mandatory” is “Mandatory” and the Object field “Instances” is “Single” then -
* 1. <MultipleInstances> == Multiple (true), <Mandatory> == Optional (false) => Object Instance ID MIN_ID=0 MAX_ID=65535 (может ни одного не быть) * the number of Object Instance MUST be 1.
* 2. <MultipleInstances> == Multiple (true), <Mandatory> == Mandatory (true) => Object Instance ID MIN_ID=0 MAX_ID=65535 (min один обязательный) * 1. <MultipleInstances> == Multiple (true), <Mandatory> == Optional (false) -
* 3. <MultipleInstances> == Single (false), <Mandatory> == Optional (false) => Object Instance ID cnt_max = 1 cnt_min = 0 (может ни одного не быть) * Object Instance ID MIN_ID=0 MAX_ID=65535 (может ни одного не быть)
* 4. <MultipleInstances> == Single (false), <Mandatory> == Mandatory (true) => Object Instance ID cnt_max = cnt_min = 1 (всегда есть один) * 2. <MultipleInstances> == Multiple (true), <Mandatory> == Mandatory (true) -
* @param $event * Object Instance ID MIN_ID=0 MAX_ID=65535 (min один обязательный)
* @param objectId * 3. <MultipleInstances> == Single (false), <Mandatory> == Optional (false) -
* @param objectName * Object Instance ID cnt_max = 1 cnt_min = 0 (может ни одного не быть)
* 4. <MultipleInstances> == Single (false), <Mandatory> == Mandatory (true) -
* Object Instance ID cnt_max = cnt_min = 1 (всегда есть один)
*/ */
addInstances($event: Event, object: ObjectLwM2M): void { addInstances = ($event: Event, object: ObjectLwM2M): void => {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
$event.preventDefault(); $event.preventDefault();
} }
let instancesIds = new Set<number>();
object.instances.forEach(inst => {
instancesIds.add(inst.id);
});
this.dialog.open<Lwm2mObjectAddInstancesComponent, Lwm2mObjectAddInstancesData, object>(Lwm2mObjectAddInstancesComponent, { this.dialog.open<Lwm2mObjectAddInstancesComponent, Lwm2mObjectAddInstancesData, object>(Lwm2mObjectAddInstancesComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
instancesIds: this.setInstancesIds(object.instances), instancesIds: this.instancesToSetId(object.instances),
objectName: object.name, objectName: object.name,
objectId: object.id objectId: object.id
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res: Lwm2mObjectAddInstancesData | undefined) => { (res: Lwm2mObjectAddInstancesData | undefined) => {
if (isNotNullOrUndefined(res)) { if (isDefinedAndNotNull(res)) {
this.updateInstancesIds(res); this.updateInstancesIds(res);
} }
} }
); );
} }
updateInstancesIds(data: Lwm2mObjectAddInstancesData) { private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => {
let valueNew = new Set<number>(); const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray).controls
let valueOld = new Set<number>(); .find(e => e.value.id === data.objectId) as FormGroup;
data.instancesIds.forEach(value => { const instancesArray = objectLwM2MFormGroup.value.instances as Instance [];
valueNew.add(value); const instancesFormArray = objectLwM2MFormGroup.get('instances') as FormArray;
}) const instance0 = deepClone(instancesFormArray.at(0).value as Instance);
let oldInstances = (this.observeAttrTelemetryFormGroup.get('clientLwM2M').value as ObjectLwM2M []).find(e => e.id == data.objectId).instances; instance0.resources.forEach(r => {
oldInstances.forEach(inst => { r.attribute = false;
valueOld.add(inst.id); r.telemetry = false;
r.observe = false;
}); });
if (JSON.stringify(Array.from(valueOld)) != JSON.stringify(Array.from(valueNew))) { const valueOld = this.instancesToSetId(instancesArray);
let idsDel = this.diffBetweenSet(valueNew, this.deepCloneSet(valueOld)); if (!isEqual(valueOld, data.instancesIds)) {
let idsAdd = this.diffBetweenSet(valueOld, this.deepCloneSet(valueNew)); const idsDel = this.diffBetweenSet(valueOld, data.instancesIds);
const idsAdd = this.diffBetweenSet(data.instancesIds, valueOld);
if (idsAdd.size) { if (idsAdd.size) {
this.addInstancesNew(data.objectId, idsAdd) this.addInstancesNew(idsAdd, objectLwM2MFormGroup, instancesFormArray, instance0);
} }
if (idsDel.size) { if (idsDel.size) {
this.delInstances(data.objectId, idsDel); this.deleteInstances(idsDel, objectLwM2MFormGroup, instancesFormArray, instance0);
} }
} }
} }
delInstances(objectId: number, idsDel: Set<number>): void { private addInstancesNew = (idsAdd: Set<number>, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray,
let isIdIndex = (element) => element.id == objectId; instanceNew: Instance): void => {
let objectIndex = (this.observeAttrTelemetryFormGroup.get('clientLwM2M').value as ObjectLwM2M []).findIndex(isIdIndex); idsAdd.forEach(x => {
idsDel.forEach(x => { this.pushInstance(instancesFormArray, x, instanceNew);
isIdIndex = (element) => element.value.id == x; });
let instancesFormArray = ((this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray).controls[objectIndex].get("instances") as FormArray); (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id);
let instanceIndex = instancesFormArray.controls.findIndex(isIdIndex);
instancesFormArray.removeAt(instanceIndex);
})
} }
addInstancesNew(objectId: number, idsAdd: Set<number>): void { private deleteInstances = (idsDel: Set<number>, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray,
let instancesValue = (this.observeAttrTelemetryFormGroup.get('clientLwM2M').value as ObjectLwM2M []).find(e => e.id == objectId).instances; instance0: Instance): void => {
let instancesFormArray = ((this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray).controls.find(e => e.value.id == objectId).get("instances") as FormArray) as FormArray; idsDel.forEach(x => {
idsAdd.forEach(x => { const instanceIndex = instancesFormArray.value.findIndex(element => element.id === x);
let instanceNew = deepClone(instancesValue[0]) as Instance; instancesFormArray.removeAt(instanceIndex);
instanceNew.id = x;
instanceNew.resources.forEach(r => {
r.attribute = false;
r.telemetry = false;
r.observe = false;
});
instancesFormArray.push(this.fb.group({
id: x,
[this.observe]: {value: false, disabled: this.disabled},
[this.attribute]: {value: false, disabled: this.disabled},
[this.telemetry]: {value: false, disabled: this.disabled},
resources: {value: instanceNew.resources, disabled: this.disabled}
}));
}); });
(instancesFormArray.controls as FormGroup[]).sort((a,b) => a.value.id - b.value.id); if (instancesFormArray.length === 0) {
this.pushInstance(instancesFormArray, 0, instance0);
}
(instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id);
} }
deepCloneSet(oldSet: Set<any>): Set<any> { private pushInstance = (instancesFormArray: FormArray, x: number, instanceNew: Instance): void => {
let newSet = new Set<number>(); instancesFormArray.push(this.fb.group({
oldSet.forEach(p => { id: x,
newSet.add(p); resources: {value: instanceNew.resources, disabled: this.disabled}
}) }));
return newSet;
} }
diffBetweenSet(firstSet: Set<any>, secondSet: Set<any>): Set<any> { private diffBetweenSet<T>(firstSet: Set<T>, secondSet: Set<T>): Set<T> {
firstSet.forEach(p => { return new Set([...Array.from(firstSet)].filter(x => !secondSet.has(x)));
secondSet.delete(p);
})
return secondSet
} }
setInstancesIds(instances: Instance []): Set<number> { private instancesToSetId = (instances: Instance[]): Set<number> => {
let instancesIds = new Set<number>(); return new Set(instances.map(x => x.id));
if (instances && instances.length) {
instances.forEach(inst => {
instancesIds.add(inst.id);
});
}
return instancesIds;
} }
} }

155
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts

@ -14,8 +14,6 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { AbstractControl, ValidationErrors } from '@angular/forms';
export const OBSERVE_ATTR = 'observeAttr'; export const OBSERVE_ATTR = 'observeAttr';
export const OBSERVE = 'observe'; export const OBSERVE = 'observe';
export const ATTR = 'attribute'; export const ATTR = 'attribute';
@ -23,37 +21,29 @@ export const TELEMETRY = 'telemetry';
export const KEY_NAME = 'keyName'; export const KEY_NAME = 'keyName';
export const DEFAULT_ID_SERVER = 123; export const DEFAULT_ID_SERVER = 123;
export const DEFAULT_ID_BOOTSTRAP = 111; export const DEFAULT_ID_BOOTSTRAP = 111;
export const DEFAULT_HOST_NAME = "localhost"; export const DEFAULT_HOST_NAME = 'localhost';
export const DEFAULT_PORT_SERVER_NO_SEC = 5685; export const DEFAULT_PORT_SERVER_NO_SEC = 5685;
export const DEFAULT_PORT_SERVER_SEC = 5686; export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5691;
export const DEFAULT_PORT_SERVER_SEC_CERT = 5688;
export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5689;
export const DEFAULT_PORT_BOOTSTRAP_SEC = 5690;
export const DEFAULT_PORT_BOOTSTRAP_SEC_CERT = 5692;
export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1; export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1;
export const DEFAULT_LIFE_TIME = 300; export const DEFAULT_LIFE_TIME = 300;
export const DEFAULT_DEFAULT_MIN_PERIOD = 1; export const DEFAULT_MIN_PERIOD = 1;
export const DEFAULT_NOTIF_IF_DESIBLED = true; export const DEFAULT_NOTIF_IF_DESIBLED = true;
export const DEFAULT_BINDING = "U"; export const DEFAULT_BINDING = 'U';
export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0; export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0;
export const LEN_MAX_PUBLIC_KEY_PSK = 182; export const LEN_MAX_PUBLIC_KEY_RPK = 182;
export const LEN_MAX_PUBLIC_KEY_RPK_X509 = 3000; export const LEN_MAX_PUBLIC_KEY_X509 = 3000;
export const KEY_IDENT_REGEXP_PSK = /^[0-9a-fA-F]{64,64}$/; export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/;
export const KEY_PRIVATE_REGEXP = /^[0-9a-fA-F]{134,134}$/; export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/;
export const KEY_PUBLIC_REGEXP_PSK = /^[0-9a-fA-F]{182,182}$/;
export const KEY_PUBLIC_REGEXP_X509 = /^[0-9a-fA-F]{0,3000}$/;
export const CAMEL_CASE_REGEXP = /[-_&@.,*+!?^${}()|[\]\\]/g;
export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MIN = 0;
export const INSTANCES_ID_VALUE_MAX = 65535; export const INSTANCES_ID_VALUE_MAX = 65535;
//ok
export enum SECURITY_CONFIG_MODE { export enum SECURITY_CONFIG_MODE {
PSK = 'PSK', PSK = 'PSK',
RPK = 'RPK', RPK = 'RPK',
X509 = 'X509', X509 = 'X509',
NO_SEC = 'NO_SEC' NO_SEC = 'NO_SEC'
} }
//ok
export const SECURITY_CONFIG_MODE_NAMES = new Map<SECURITY_CONFIG_MODE, string>( export const SECURITY_CONFIG_MODE_NAMES = new Map<SECURITY_CONFIG_MODE, string>(
[ [
[SECURITY_CONFIG_MODE.PSK, 'Pre-Shared Key'], [SECURITY_CONFIG_MODE.PSK, 'Pre-Shared Key'],
@ -62,103 +52,88 @@ export const SECURITY_CONFIG_MODE_NAMES = new Map<SECURITY_CONFIG_MODE, string>(
[SECURITY_CONFIG_MODE.NO_SEC, 'No Security'], [SECURITY_CONFIG_MODE.NO_SEC, 'No Security'],
] ]
); );
//ok
export interface BootstrapServersSecurityConfig { export interface BootstrapServersSecurityConfig {
shortId: number, shortId: number;
lifetime: number, lifetime: number;
defaultMinPeriod: number, defaultMinPeriod: number;
notifIfDisabled: boolean, notifIfDisabled: boolean;
binding: string binding: string;
} }
//ok
export interface ServerSecurityConfig { export interface ServerSecurityConfig {
host?: string, host?: string;
port?: number, port?: number;
bootstrapServerIs?: boolean, bootstrapServerIs?: boolean;
securityMode: string, securityMode: string;
clientPublicKeyOrId?: string, clientPublicKeyOrId?: string;
clientSecretKey?: string, clientSecretKey?: string;
serverPublicKey?: string; serverPublicKey?: string;
clientHoldOffTime?: number, clientHoldOffTime?: number;
serverId?: number, serverId?: number;
bootstrapServerAccountTimeout: number bootstrapServerAccountTimeout: number;
} }
//ok
interface BootstrapSecurityConfig { interface BootstrapSecurityConfig {
servers: BootstrapServersSecurityConfig, servers: BootstrapServersSecurityConfig;
bootstrapServer: ServerSecurityConfig, bootstrapServer: ServerSecurityConfig;
lwm2mServer: ServerSecurityConfig lwm2mServer: ServerSecurityConfig;
} }
//ok
export interface ProfileConfigModels { export interface ProfileConfigModels {
bootstrap: BootstrapSecurityConfig, bootstrap: BootstrapSecurityConfig;
observeAttr: { observeAttr: ObservableAttributes;
observe: string [], }
attribute: string [],
telemetry: string [], export interface ObservableAttributes {
keyName: [] observe: string[];
} attribute: string[];
telemetry: string[];
keyName: string[];
} }
//ok
export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig {
return { return {
shortId: DEFAULT_ID_SERVER, shortId: DEFAULT_ID_SERVER,
lifetime: DEFAULT_LIFE_TIME, lifetime: DEFAULT_LIFE_TIME,
defaultMinPeriod: DEFAULT_DEFAULT_MIN_PERIOD, defaultMinPeriod: DEFAULT_MIN_PERIOD,
notifIfDisabled: DEFAULT_NOTIF_IF_DESIBLED, notifIfDisabled: DEFAULT_NOTIF_IF_DESIBLED,
binding: DEFAULT_BINDING binding: DEFAULT_BINDING
} };
} }
//ok
export function getDefaultBootstrapServerSecurityConfig(hostname: any): ServerSecurityConfig { export function getDefaultBootstrapServerSecurityConfig(hostname: any): ServerSecurityConfig {
return { return {
host: hostname, host: hostname,
port: getDefaultPortBootstrap(), port: DEFAULT_PORT_BOOTSTRAP_NO_SEC,
bootstrapServerIs: true, bootstrapServerIs: true,
securityMode: SECURITY_CONFIG_MODE.NO_SEC.toString(), securityMode: SECURITY_CONFIG_MODE.NO_SEC.toString(),
serverPublicKey: '', serverPublicKey: '',
clientHoldOffTime: DEFAULT_CLIENT_HOLD_OFF_TIME, clientHoldOffTime: DEFAULT_CLIENT_HOLD_OFF_TIME,
serverId: DEFAULT_ID_BOOTSTRAP, serverId: DEFAULT_ID_BOOTSTRAP,
bootstrapServerAccountTimeout: DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT bootstrapServerAccountTimeout: DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT
} };
} }
//ok
export function getDefaultLwM2MServerSecurityConfig(hostname): ServerSecurityConfig { export function getDefaultLwM2MServerSecurityConfig(hostname): ServerSecurityConfig {
const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(hostname); const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(hostname);
DefaultLwM2MServerSecurityConfig.bootstrapServerIs = false; DefaultLwM2MServerSecurityConfig.bootstrapServerIs = false;
DefaultLwM2MServerSecurityConfig.port = getDefaultPortServer(); DefaultLwM2MServerSecurityConfig.port = DEFAULT_PORT_SERVER_NO_SEC;
DefaultLwM2MServerSecurityConfig.serverId = DEFAULT_ID_SERVER; DefaultLwM2MServerSecurityConfig.serverId = DEFAULT_ID_SERVER;
return DefaultLwM2MServerSecurityConfig; return DefaultLwM2MServerSecurityConfig;
} }
//ok
export function getDefaultPortBootstrap(securityMode?: string): number {
return (!securityMode || securityMode === SECURITY_CONFIG_MODE.NO_SEC.toString()) ? DEFAULT_PORT_BOOTSTRAP_NO_SEC :
(securityMode === SECURITY_CONFIG_MODE.X509.toString()) ? DEFAULT_PORT_BOOTSTRAP_SEC_CERT : DEFAULT_PORT_BOOTSTRAP_SEC;
}
//ok
export function getDefaultPortServer(securityMode?: string): number {
return (!securityMode || securityMode === SECURITY_CONFIG_MODE.NO_SEC.toString()) ? DEFAULT_PORT_SERVER_NO_SEC :
(securityMode === SECURITY_CONFIG_MODE.X509.toString()) ? DEFAULT_PORT_SERVER_SEC_CERT : DEFAULT_PORT_SERVER_SEC;
}
//ok
function getDefaultProfileBootstrapSecurityConfig(hostname: any): BootstrapSecurityConfig { function getDefaultProfileBootstrapSecurityConfig(hostname: any): BootstrapSecurityConfig {
return { return {
servers: getDefaultBootstrapServersSecurityConfig(), servers: getDefaultBootstrapServersSecurityConfig(),
bootstrapServer: getDefaultBootstrapServerSecurityConfig(hostname), bootstrapServer: getDefaultBootstrapServerSecurityConfig(hostname),
lwm2mServer: getDefaultLwM2MServerSecurityConfig(hostname) lwm2mServer: getDefaultLwM2MServerSecurityConfig(hostname)
} };
} }
//ok
export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels { export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels {
return { return {
bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname)? hostname : DEFAULT_HOST_NAME), bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME),
observeAttr: { observeAttr: {
observe: [], observe: [],
attribute: [], attribute: [],
@ -168,19 +143,18 @@ export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels {
}; };
} }
//ok
export interface ResourceLwM2M { export interface ResourceLwM2M {
id: number, id: number;
name: string, name: string;
observe: boolean, observe: boolean;
attribute: boolean, attribute: boolean;
telemetry: boolean, telemetry: boolean;
keyName: string keyName: string;
} }
//ok
export interface Instance { export interface Instance {
id: number, id: number;
resources: ResourceLwM2M[] resources: ResourceLwM2M[];
} }
/** /**
@ -190,23 +164,10 @@ export interface Instance {
* mandatory == false => Optional * mandatory == false => Optional
*/ */
export interface ObjectLwM2M { export interface ObjectLwM2M {
id: number, id: number;
name: string, name: string;
multiple?: boolean, multiple?: boolean;
mandatory?: boolean, mandatory?: boolean;
instances?: Instance [] instances?: Instance [];
}
export function getChangeInstancesIds (): ChangeInstancesIds {
let changeInstancesIds: ChangeInstancesIds;
changeInstancesIds.add = new Set<number>();
changeInstancesIds.del = new Set<number>();
return changeInstancesIds;
}
export interface ChangeInstancesIds {
add: Set<number>,
del: Set<number>
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html

@ -35,7 +35,7 @@
required required
> >
</tb-entity-gateway-select> </tb-entity-gateway-select>
<div fxLayout="column">profile-tab <div fxLayout="column">
<mat-form-field fxFlex> <mat-form-field fxFlex>
<mat-label>{{'gateway.security-type' | translate }}</mat-label> <mat-label>{{'gateway.security-type' | translate }}</mat-label>
<mat-select formControlName="securityType" > <mat-select formControlName="securityType" >

3
ui-ngx/src/app/modules/home/pages/device/device.module.ts

@ -25,8 +25,7 @@ import { HomeDialogsModule } from '../../dialogs/home-dialogs.module';
import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component';
import { SecurityConfigComponent } from '@home/pages/device/lwm2m/security-config.component'; import { SecurityConfigComponent } from '@home/pages/device/lwm2m/security-config.component';
// TODO: @nickAS21 move to device profile import { SecurityConfigServerComponent } from '@home/pages/device/lwm2m/security-config-server.component';
import {SecurityConfigServerComponent} from "@home/pages/device/lwm2m/security-config-server.component";
import { DefaultDeviceConfigurationComponent } from './data/default-device-configuration.component'; import { DefaultDeviceConfigurationComponent } from './data/default-device-configuration.component';
import { DeviceConfigurationComponent } from './data/device-configuration.component'; import { DeviceConfigurationComponent } from './data/device-configuration.component';
import { DeviceDataComponent } from './data/device-data.component'; import { DeviceDataComponent } from './data/device-data.component';

57
ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html

@ -24,7 +24,7 @@
<mat-select formControlName="securityMode" <mat-select formControlName="securityMode"
(ngModelChange)="securityModeChanged($event)"> (ngModelChange)="securityModeChanged($event)">
<mat-option *ngFor="let securityMode of securityConfigLwM2MTypes" <mat-option *ngFor="let securityMode of securityConfigLwM2MTypes"
[value]="securityMode" > [value]="securityMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }}
</mat-option> </mat-option>
</mat-select> </mat-select>
@ -35,43 +35,64 @@
<div [fxShow]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC"> <div [fxShow]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }}</mat-label> <mat-label>{{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }}</mat-label>
<textarea matInput type="text" rows="3" cols="1" formControlName="clientPublicKeyOrId" #clientPublicKeyOrId maxlength={{lenMaxClientPublicKeyOrId}} <textarea matInput
[required]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC"></textarea> #clientPublicKeyOrId
maxlength={{lenMaxClientPublicKeyOrId}}
cdkTextareaAutosize
cdkAutosizeMinRows="1"
style="overflow:hidden"
cols="1"
formControlName="clientPublicKeyOrId"
[required]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC">
</textarea>
<mat-hint align="end">{{clientPublicKeyOrId.value?.length || 0}}/{{lenMaxClientPublicKeyOrId}}</mat-hint> <mat-hint align="end">{{clientPublicKeyOrId.value?.length || 0}}/{{lenMaxClientPublicKeyOrId}}</mat-hint>
<mat-error *ngIf="serverFormGroup.get('clientPublicKeyOrId').hasError('required')"> <mat-error *ngIf="serverFormGroup.get('clientPublicKeyOrId').hasError('required')">
{{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} {{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }}
<strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong> <strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong>
</mat-error> </mat-error>
<mat-error *ngIf="serverFormGroup.get('clientPublicKeyOrId').hasError('pattern') && <mat-error *ngIf="serverFormGroup.get('clientPublicKeyOrId').hasError('pattern')">
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK">
{{ 'device.lwm2m-security-config.client-key' | translate }} {{ 'device.lwm2m-security-config.client-key' | translate }}
<strong>{{ 'device.lwm2m-security-config.pattern_hex_dec_182' | translate }}</strong> <strong>{{ translate.get('device.lwm2m-security-config.pattern_hex_dec', {
count: 0
}) | async }}</strong>
</mat-error> </mat-error>
<mat-error *ngIf="serverFormGroup.get('clientPublicKeyOrId').hasError('pattern') && <mat-error *ngIf="(serverFormGroup.get('clientPublicKeyOrId').hasError('maxlength') ||
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509"> serverFormGroup.get('clientPublicKeyOrId').hasError('minlength'))">
{{ 'device.lwm2m-security-config.client-key' | translate }} {{ 'device.lwm2m-security-config.client-key' | translate }}
<strong>{{ 'device.lwm2m-security-config.pattern_hex_dec' | translate }}</strong> <strong>{{ translate.get('device.lwm2m-security-config.pattern_hex_dec', {
count: lenMaxClientPublicKeyOrId
}) | async }}</strong>
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'device.lwm2m-security-config.client-secret-key' | translate }}</mat-label> <mat-label>{{ 'device.lwm2m-security-config.client-secret-key' | translate }}</mat-label>
<textarea matInput type="text" rows="2" cols="1" formControlName="clientSecretKey" #clientSecretKey maxlength={{lenMaxClientSecretKey}} <textarea matInput
[required]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC"></textarea> #clientSecretKey
maxlength={{lenMaxClientSecretKey}}
cdkTextareaAutosize
cdkAutosizeMinRows="1"
style="overflow:hidden"
cols="1"
formControlName="clientSecretKey"
[required]="serverFormGroup.get('securityMode').value !== securityConfigLwM2MType.NO_SEC">
</textarea>
<mat-hint align="end">{{clientSecretKey.value?.length || 0}}/{{lenMaxClientSecretKey}}</mat-hint> <mat-hint align="end">{{clientSecretKey.value?.length || 0}}/{{lenMaxClientSecretKey}}</mat-hint>
<mat-error *ngIf="serverFormGroup.get('clientSecretKey').hasError('required')"> <mat-error *ngIf="serverFormGroup.get('clientSecretKey').hasError('required')">
{{ 'device.lwm2m-security-config.client-secret-key' | translate }} {{ 'device.lwm2m-security-config.client-secret-key' | translate }}
<strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong> <strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong>
</mat-error> </mat-error>
<mat-error *ngIf="serverFormGroup.get('clientSecretKey').hasError('pattern') && <mat-error *ngIf="serverFormGroup.get('clientSecretKey').hasError('pattern')">
(serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509)">
{{ 'device.lwm2m-security-config.client-key' | translate }} {{ 'device.lwm2m-security-config.client-key' | translate }}
<strong>{{ 'device.lwm2m-security-config.pattern_hex_dec_134' | translate }}</strong> <strong>{{ translate.get('device.lwm2m-security-config.pattern_hex_dec', {
count: 0
}) | async }}</strong>
</mat-error> </mat-error>
<mat-error *ngIf="serverFormGroup.get('clientSecretKey').hasError('pattern') && <mat-error *ngIf="(serverFormGroup.get('clientSecretKey').hasError('maxlength') ||
(serverFormGroup.get('securityMode').value === securityConfigLwM2MType.PSK)"> serverFormGroup.get('clientSecretKey').hasError('minlength'))">
{{ 'device.lwm2m-security-config.client-key' | translate }} {{ 'device.lwm2m-security-config.client-key' | translate }}
<strong>{{ 'device-profile.lwm2m.pattern_hex_dec_64' | translate }}</strong> <strong>{{ translate.get('device.lwm2m-security-config.pattern_hex_dec', {
count: lenMaxClientSecretKey
}) | async }}</strong>
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>

74
ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts

@ -14,26 +14,23 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {Component, forwardRef, Inject, Input, OnInit, ViewChild} from "@angular/core"; import { Component, forwardRef, Inject, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators
} from "@angular/forms";
import { import {
SECURITY_CONFIG_MODE,
SECURITY_CONFIG_MODE_NAMES,
KEY_IDENT_REGEXP_PSK,
ServerSecurityConfig,
DeviceCredentialsDialogLwm2mData, DeviceCredentialsDialogLwm2mData,
KEY_REGEXP_HEX_DEC,
LEN_MAX_PRIVATE_KEY,
LEN_MAX_PSK, LEN_MAX_PSK,
LEN_MAX_PRIVATE_KEY, LEN_MAX_PUBLIC_KEY_RPK, KEY_PRIVATE_REGEXP, LEN_MAX_PUBLIC_KEY_X509, KEY_PUBLIC_REGEXP_X509 LEN_MAX_PUBLIC_KEY_RPK,
} from "@home/pages/device/lwm2m/security-config.models"; LEN_MAX_PUBLIC_KEY_X509,
import {Store} from "@ngrx/store"; SECURITY_CONFIG_MODE,
import {AppState} from "@core/core.state"; SECURITY_CONFIG_MODE_NAMES,
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; ServerSecurityConfig
import {PageComponent} from "@shared/components/page.component"; } from '@home/pages/device/lwm2m/security-config.models';
import {MatPaginator} from "@angular/material/paginator"; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { PageComponent } from '@shared/components/page.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
@ -54,13 +51,13 @@ export class SecurityConfigServerComponent extends PageComponent implements OnIn
securityConfigLwM2MType = SECURITY_CONFIG_MODE; securityConfigLwM2MType = SECURITY_CONFIG_MODE;
securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE); securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE);
credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES; credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES;
lenMaxClientPublicKeyOrId = LEN_MAX_PSK; lenMinClientPublicKeyOrId = 0;
lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK;
lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY;
lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY;
@Input() serverFormGroup: FormGroup; @Input() serverFormGroup: FormGroup;
@ViewChild(MatPaginator) paginator: MatPaginator;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData,
public dialogRef: MatDialogRef<SecurityConfigServerComponent, object>, public dialogRef: MatDialogRef<SecurityConfigServerComponent, object>,
@ -73,40 +70,59 @@ export class SecurityConfigServerComponent extends PageComponent implements OnIn
this.registerDisableOnLoadFormControl(this.serverFormGroup.get('securityMode')); this.registerDisableOnLoadFormControl(this.serverFormGroup.get('securityMode'));
} }
updateValueFields(serverData: ServerSecurityConfig): void { private updateValueFields(serverData: ServerSecurityConfig): void {
this.serverFormGroup.patchValue(serverData, {emitEvent: false}); this.serverFormGroup.patchValue(serverData, {emitEvent: false});
const securityMode = this.serverFormGroup.get('securityMode').value as SECURITY_CONFIG_MODE; const securityMode = this.serverFormGroup.get('securityMode').value as SECURITY_CONFIG_MODE;
this.updateValidate(securityMode); this.updateValidate(securityMode);
} }
updateValidate(securityMode: SECURITY_CONFIG_MODE): void { private updateValidate(securityMode: SECURITY_CONFIG_MODE): void {
switch (securityMode) { switch (securityMode) {
case SECURITY_CONFIG_MODE.NO_SEC: case SECURITY_CONFIG_MODE.NO_SEC:
this.serverFormGroup.get('clientPublicKeyOrId').setValidators([]); this.serverFormGroup.get('clientPublicKeyOrId').setValidators([]);
this.serverFormGroup.get('clientSecretKey').setValidators([]); this.serverFormGroup.get('clientSecretKey').setValidators([]);
break; break;
case SECURITY_CONFIG_MODE.PSK: case SECURITY_CONFIG_MODE.PSK:
this.lenMinClientPublicKeyOrId = 0;
this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK;
this.lenMinClientSecretKey = LEN_MAX_PSK;
this.lenMaxClientSecretKey = LEN_MAX_PSK; this.lenMaxClientSecretKey = LEN_MAX_PSK;
this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required]); this.setValidatorsSecurity(securityMode);
this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, Validators.pattern(KEY_IDENT_REGEXP_PSK)]);
break; break;
case SECURITY_CONFIG_MODE.RPK: case SECURITY_CONFIG_MODE.RPK:
this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509; this.lenMinClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK;
this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK;
this.lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY;
this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY;
this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_X509)]); this.setValidatorsSecurity(securityMode);
this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, Validators.pattern(KEY_PRIVATE_REGEXP)]);
break; break;
case SECURITY_CONFIG_MODE.X509: case SECURITY_CONFIG_MODE.X509:
this.lenMinClientPublicKeyOrId = 0;
this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509; this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509;
this.lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY;
this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY;
this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_X509)]); this.setValidatorsSecurity(securityMode);
this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, Validators.pattern(KEY_PRIVATE_REGEXP)]);
break; break;
} }
this.serverFormGroup.updateValueAndValidity(); this.serverFormGroup.updateValueAndValidity();
} }
private setValidatorsSecurity = (securityMode: SECURITY_CONFIG_MODE): void => {
if (securityMode === SECURITY_CONFIG_MODE.PSK) {
this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required]);
} else {
this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required,
Validators.pattern(KEY_REGEXP_HEX_DEC),
Validators.minLength(this.lenMinClientPublicKeyOrId),
Validators.maxLength(this.lenMaxClientPublicKeyOrId)]);
}
this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required,
Validators.pattern(KEY_REGEXP_HEX_DEC),
Validators.minLength(this.lenMinClientSecretKey),
Validators.maxLength(this.lenMaxClientSecretKey)]);
}
securityModeChanged(securityMode: SECURITY_CONFIG_MODE): void { securityModeChanged(securityMode: SECURITY_CONFIG_MODE): void {
this.updateValidate(securityMode); this.updateValidate(securityMode);
} }

212
ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html

@ -35,110 +35,130 @@
<strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong> <strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong>
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-tab-group dynamicHeight #tabGroup (selectedTabChange)="tabChanged($event)"> <mat-tab-group dynamicHeight (selectedTabChange)="tabChanged($event)">
<mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}"> <mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}">
<div class="mat-padding"> <ng-template matTabContent>
<mat-form-field class="mat-block"> <div class="mat-padding">
<mat-label translate>device.lwm2m-security-config.mode</mat-label>
<mat-select formControlName="securityConfigClientMode"
(ngModelChange)="securityConfigClientModeChanged($event)">
<mat-option *ngFor="let securityConfigClientMode of securityConfigLwM2MTypes"
[value]="securityConfigClientMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }}
</mat-option>
</mat-select>
</mat-form-field>
<div [fxShow]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'device.lwm2m-security-config.identity' | translate }}</mat-label> <mat-label translate>device.lwm2m-security-config.mode</mat-label>
<input matInput type="text" formControlName="identityPSK" <mat-select formControlName="securityConfigClientMode"
[required]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK"> (ngModelChange)="securityConfigClientModeChanged($event)">
<mat-error *ngIf="lwm2mConfigFormGroup.get('identityPSK').hasError('required')"> <mat-option *ngFor="let securityConfigClientMode of securityConfigLwM2MTypes"
{{ 'device.lwm2m-security-config.identity' | translate }} [value]="securityConfigClientMode">
<strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong> {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }}
</mat-error> </mat-option>
</mat-select>
</mat-form-field> </mat-form-field>
</div> <div *ngIf="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<div [fxShow]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.RPK || <mat-form-field class="mat-block">
<mat-label>{{ 'device.lwm2m-security-config.identity' | translate }}</mat-label>
<input matInput type="text" formControlName="identityPSK"
[required]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<mat-error *ngIf="lwm2mConfigFormGroup.get('identityPSK').hasError('required')">
{{ 'device.lwm2m-security-config.identity' | translate }}
<strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong>
</mat-error>
</mat-form-field>
</div>
<div *ngIf="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.RPK ||
lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK"> lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'device.lwm2m-security-config.client-key' | translate }}</mat-label> <mat-label>{{ 'device.lwm2m-security-config.client-key' | translate }}</mat-label>
<textarea matInput #clientKey maxlength={{lenMinMaxClient}} type="text" rows="3" cols="1" <textarea matInput
formControlName="clientKey" #clientKey
[required]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.RPK || maxlength="{{lenMaxKeyClient}}"
lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK"></textarea> cdkTextareaAutosize
<mat-hint align="end">{{clientKey.value?.length || 0}}/{{lenMaxKeyClient}}</mat-hint> cdkAutosizeMinRows="1"
<mat-error *ngIf="lwm2mConfigFormGroup.get('clientKey').hasError('required')"> cols="1"
{{ 'device.lwm2m-security-config.client-key' | translate }} formControlName="clientKey"
<strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong> style="overflow:hidden"
</mat-error> [required]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.RPK ||
<mat-error *ngIf="lwm2mConfigFormGroup.get('clientKey').hasError('pattern') && lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK">
lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK"> </textarea>
{{ 'device.lwm2m-security-config.client-key' | translate }} <mat-hint align="end">{{clientKey.value?.length || 0}}/{{lenMaxKeyClient}}</mat-hint>
<strong>{{ 'device-profile.lwm2m-security-config.pattern_hex_dec_64' | translate }}</strong> <mat-error *ngIf="lwm2mConfigFormGroup.get('clientKey').hasError('required')">
</mat-error> {{ 'device.lwm2m-security-config.client-key' | translate }}
<mat-error *ngIf="lwm2mConfigFormGroup.get('clientKey').hasError('pattern') && <strong>{{ 'device.lwm2m-security-config.required' | translate }}</strong>
lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.RPK"> </mat-error>
{{ 'device.lwm2m-security-config.client-key' | translate }} <mat-error *ngIf="lwm2mConfigFormGroup.get('clientKey').hasError('pattern')">
<strong>{{ 'device.lwm2m-security-config.pattern_hex_dec_182' | translate }}</strong> {{ 'device.lwm2m-security-config.client-key' | translate }}
</mat-error> <strong>{{ translate.get('device.lwm2m-security-config.pattern_hex_dec', {
</mat-form-field> count: 0
</div> }) | async }}</strong>
<div [fxShow]="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.X509"> </mat-error>
<mat-checkbox formControlName="clientCertificate" color="primary"> <mat-error *ngIf="(lwm2mConfigFormGroup.get('clientKey').hasError('maxlength') ||
{{ 'device.lwm2m-security-config.client-certificate' | translate }} lwm2mConfigFormGroup.get('clientKey').hasError('minlength'))">
</mat-checkbox> {{ 'device.lwm2m-security-config.client-key' | translate }}
<strong>{{ translate.get('device.lwm2m-security-config.pattern_hex_dec', {
count: lenMaxKeyClient
}) | async }}</strong>
</mat-error>
</mat-form-field>
</div>
<div *ngIf="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.X509">
<mat-checkbox formControlName="clientCertificate" color="primary">
{{ 'device.lwm2m-security-config.client-certificate' | translate }}
</mat-checkbox>
</div>
</div> </div>
</div> </ng-template>
</mat-tab> </mat-tab>
<mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}"> <mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}">
<div class="mat-padding"> <ng-template matTabContent>
<mat-accordion multi="true" class="mat-body-1"> <div class="mat-padding">
<mat-expansion-panel> <mat-accordion multi="true" class="mat-body-1">
<mat-expansion-panel-header> <mat-expansion-panel>
<mat-panel-title> <mat-expansion-panel-header>
<div <mat-panel-title>
class="tb-panel-title">{{ 'device.lwm2m-security-config.bootstrap-server' | translate | uppercase }}</div> <div
</mat-panel-title> class="tb-panel-title">{{ 'device.lwm2m-security-config.bootstrap-server' | translate | uppercase }}</div>
</mat-expansion-panel-header> </mat-panel-title>
<div class="mat-padding"> </mat-expansion-panel-header>
<tb-security-config-server-lwm2m <ng-template matExpansionPanelContent>
[formControlName]="bootstrapServer" <div class="mat-padding">
[serverFormGroup]="bootstrapFormGroup"> <tb-security-config-server-lwm2m
</tb-security-config-server-lwm2m> [formControlName]="bootstrapServer"
</div> [serverFormGroup]="bootstrapFormGroup">
</mat-expansion-panel> </tb-security-config-server-lwm2m>
</mat-accordion> </div>
<mat-accordion multi="true" class="mat-body-1"> </ng-template>
<mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel-header> </mat-accordion>
<mat-panel-title> <mat-accordion multi="true" class="mat-body-1">
<div <mat-expansion-panel>
class="tb-panel-title">{{ 'device.lwm2m-security-config.lwm2m-server' | translate | uppercase }}</div> <mat-expansion-panel-header>
</mat-panel-title> <mat-panel-title>
</mat-expansion-panel-header> <div
<div class="mat-padding"> class="tb-panel-title">{{ 'device.lwm2m-security-config.lwm2m-server' | translate | uppercase }}</div>
<tb-security-config-server-lwm2m </mat-panel-title>
[formControlName]="lwm2mServer" </mat-expansion-panel-header>
[serverFormGroup]="lwm2mServerFormGroup"> <ng-template matExpansionPanelContent>
</tb-security-config-server-lwm2m> <div class="mat-padding">
</div> <tb-security-config-server-lwm2m
</mat-expansion-panel> [formControlName]="lwm2mServer"
</mat-accordion> [serverFormGroup]="lwm2mServerFormGroup">
</div> </tb-security-config-server-lwm2m>
</div>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>
</ng-template>
</mat-tab> </mat-tab>
<mat-tab label="{{ 'device.lwm2m-security-config.config-json-tab' | translate }}"> <mat-tab label="{{ 'device.lwm2m-security-config.config-json-tab' | translate }}">
<div class="mat-padding"> <ng-template matTabContent>
<fieldset [disabled]="isLoading$ | async"> <div class="mat-padding">
<tb-json-object-edit <fieldset [disabled]="isLoading$ | async">
[formControlName]="formControlNameJsonAllConfig" <tb-json-object-edit
label="{{ 'device.lwm2m-value' | translate }}" [formControlName]="formControlNameJsonAllConfig"
validateContent="true" label="{{ 'device.lwm2m-value' | translate }}"
[required]="true" validateContent="true"
[fillHeight]="false"> [required]="true"
</tb-json-object-edit> [fillHeight]="false">
</fieldset> </tb-json-object-edit>
</div> </fieldset>
</div>
</ng-template>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
</fieldset> </fieldset>
@ -148,7 +168,7 @@
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()">
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-button mat-raised-button color="primary" <button mat-button mat-raised-button color="primary"

213
ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.ts

@ -15,37 +15,35 @@
/// ///
import {Component, Inject, OnInit} from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import {DialogComponent} from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import {Store} from '@ngrx/store'; import { Store } from '@ngrx/store';
import {AppState} from '@core/core.state'; import { AppState } from '@core/core.state';
import {Router} from '@angular/router'; import { Router } from '@angular/router';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {TranslateService} from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { import {
SECURITY_CONFIG_MODE_NAMES,
SECURITY_CONFIG_MODE,
SecurityConfigModels,
ClientSecurityConfigPSK,
ClientSecurityConfigRPK,
JSON_ALL_CONFIG,
KEY_IDENT_REGEXP_PSK,
KEY_PUBLIC_REGEXP_PSK,
DeviceCredentialsDialogLwm2mData,
BOOTSTRAP_SERVER, BOOTSTRAP_SERVER,
BOOTSTRAP_SERVERS, BOOTSTRAP_SERVERS,
LWM2M_SERVER, ClientSecurityConfigNoSEC,
ClientSecurityConfigPSK,
ClientSecurityConfigRPK,
ClientSecurityConfigX509, ClientSecurityConfigX509,
ClientSecurityConfigNO_SEC, DeviceCredentialsDialogLwm2mData,
getDefaultClientSecurityConfigType, getDefaultClientSecurityConfigType,
JSON_ALL_CONFIG,
KEY_REGEXP_HEX_DEC,
LEN_MAX_PSK, LEN_MAX_PSK,
LEN_MAX_PUBLIC_KEY_RPK LEN_MAX_PUBLIC_KEY_RPK,
} from "./security-config.models"; LWM2M_SERVER,
import {WINDOW} from "@core/services/window.service"; SECURITY_CONFIG_MODE,
import {MatTabChangeEvent, MatTabGroup} from "@angular/material/tabs"; SECURITY_CONFIG_MODE_NAMES,
import {MatTab} from "@angular/material/tabs/tab"; SecurityConfigModels
} from './security-config.models';
import { WINDOW } from '@core/services/window.service';
import { MatTabChangeEvent } from '@angular/material/tabs';
import { MatTab } from '@angular/material/tabs/tab';
@Component({ @Component({
selector: 'tb-security-config-lwm2m', selector: 'tb-security-config-lwm2m',
@ -68,15 +66,15 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
lwm2mServer: string; lwm2mServer: string;
jsonObserveData: {}; jsonObserveData: {};
lenMaxKeyClient = LEN_MAX_PSK; lenMaxKeyClient = LEN_MAX_PSK;
tabPrevious: MatTab tabPrevious: MatTab;
tabIndexPrevious = 0 as number; tabIndexPrevious = 0;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData,
public dialogRef: MatDialogRef<SecurityConfigComponent, object>, public dialogRef: MatDialogRef<SecurityConfigComponent, object>,
public fb: FormBuilder, public fb: FormBuilder,
private translate: TranslateService, public translate: TranslateService,
@Inject(WINDOW) private window: Window) { @Inject(WINDOW) private window: Window) {
super(store, router, dialogRef); super(store, router, dialogRef);
} }
@ -85,13 +83,13 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)) as SecurityConfigModels; this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)) as SecurityConfigModels;
this.initConstants(); this.initConstants();
this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup(); this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup();
this.title = this.translate.instant('device.lwm2m-security-info') + ": " + this.data.endPoint this.title = this.translate.instant('device.lwm2m-security-info') + ': ' + this.data.endPoint;
this.lwm2mConfigFormGroup.get('clientCertificate').disable(); this.lwm2mConfigFormGroup.get('clientCertificate').disable();
this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value); this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value);
this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode')); this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode'));
} }
initConstants(): void { private initConstants = (): void => {
this.bootstrapServers = BOOTSTRAP_SERVERS; this.bootstrapServers = BOOTSTRAP_SERVERS;
this.bootstrapServer = BOOTSTRAP_SERVER; this.bootstrapServer = BOOTSTRAP_SERVER;
this.lwm2mServer = LWM2M_SERVER; this.lwm2mServer = LWM2M_SERVER;
@ -112,24 +110,24 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
return this.lwm2mConfigFormGroup.get('observeFormGroup') as FormGroup; return this.lwm2mConfigFormGroup.get('observeFormGroup') as FormGroup;
} }
initClientSecurityConfig(jsonAllConfig: SecurityConfigModels): void { private initClientSecurityConfig = (jsonAllConfig: SecurityConfigModels): void => {
switch (jsonAllConfig.client.securityConfigClientMode.toString()) { switch (jsonAllConfig.client.securityConfigClientMode) {
case SECURITY_CONFIG_MODE.NO_SEC.toString(): case SECURITY_CONFIG_MODE.NO_SEC:
break; break;
case SECURITY_CONFIG_MODE.PSK.toString(): case SECURITY_CONFIG_MODE.PSK:
const clientSecurityConfigPSK = jsonAllConfig.client as ClientSecurityConfigPSK; const clientSecurityConfigPSK = jsonAllConfig.client as ClientSecurityConfigPSK;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
identityPSK: clientSecurityConfigPSK.identity, identityPSK: clientSecurityConfigPSK.identity,
clientKey: clientSecurityConfigPSK.key, clientKey: clientSecurityConfigPSK.key,
}, {emitEvent: false}); }, {emitEvent: false});
break; break;
case SECURITY_CONFIG_MODE.RPK.toString(): case SECURITY_CONFIG_MODE.RPK:
const clientSecurityConfigRPK = jsonAllConfig.client as ClientSecurityConfigRPK; const clientSecurityConfigRPK = jsonAllConfig.client as ClientSecurityConfigRPK;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
clientKey: clientSecurityConfigRPK.key, clientKey: clientSecurityConfigRPK.key,
}, {emitEvent: false}); }, {emitEvent: false});
break; break;
case SECURITY_CONFIG_MODE.X509.toString(): case SECURITY_CONFIG_MODE.X509:
const clientSecurityConfigX509 = jsonAllConfig.client as ClientSecurityConfigX509; const clientSecurityConfigX509 = jsonAllConfig.client as ClientSecurityConfigX509;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
clientCertificate: clientSecurityConfigX509.x509 clientCertificate: clientSecurityConfigX509.x509
@ -139,106 +137,110 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
this.securityConfigClientUpdateValidators(this.lwm2mConfigFormGroup.get('securityConfigClientMode').value); this.securityConfigClientUpdateValidators(this.lwm2mConfigFormGroup.get('securityConfigClientMode').value);
} }
securityConfigClientModeChanged(mode: SECURITY_CONFIG_MODE): void { securityConfigClientModeChanged = (mode: SECURITY_CONFIG_MODE): void => {
switch (mode) { switch (mode) {
case SECURITY_CONFIG_MODE.NO_SEC: case SECURITY_CONFIG_MODE.NO_SEC:
let clientSecurityConfigNO_SEC = getDefaultClientSecurityConfigType(mode) as ClientSecurityConfigNO_SEC; const clientSecurityConfigNoSEC = getDefaultClientSecurityConfigType(mode) as ClientSecurityConfigNoSEC;
this.jsonAllConfig.client = clientSecurityConfigNO_SEC; this.jsonAllConfig.client = clientSecurityConfigNoSEC;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
jsonAllConfig: this.jsonAllConfig, jsonAllConfig: this.jsonAllConfig,
clientCertificate: false clientCertificate: false
}, {emitEvent: true}); }, {emitEvent: false});
break; break;
case SECURITY_CONFIG_MODE.PSK: case SECURITY_CONFIG_MODE.PSK:
let clientSecurityConfigPSK = getDefaultClientSecurityConfigType(mode, this.lwm2mConfigFormGroup.get('endPoint').value) as ClientSecurityConfigPSK; const clientSecurityConfigPSK = getDefaultClientSecurityConfigType(mode, this.lwm2mConfigFormGroup.get('endPoint')
.value) as ClientSecurityConfigPSK;
clientSecurityConfigPSK.identity = this.data.endPoint; clientSecurityConfigPSK.identity = this.data.endPoint;
clientSecurityConfigPSK.key = this.lwm2mConfigFormGroup.get('clientKey').value; clientSecurityConfigPSK.key = this.lwm2mConfigFormGroup.get('clientKey').value;
this.jsonAllConfig.client = clientSecurityConfigPSK; this.jsonAllConfig.client = clientSecurityConfigPSK;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
identityPSK: clientSecurityConfigPSK.identity, identityPSK: clientSecurityConfigPSK.identity,
clientCertificate: false clientCertificate: false
}, {emitEvent: true}); }, {emitEvent: false});
break; break;
case SECURITY_CONFIG_MODE.RPK: case SECURITY_CONFIG_MODE.RPK:
let clientSecurityConfigRPK = getDefaultClientSecurityConfigType(mode) as ClientSecurityConfigRPK; const clientSecurityConfigRPK = getDefaultClientSecurityConfigType(mode) as ClientSecurityConfigRPK;
clientSecurityConfigRPK.key = this.lwm2mConfigFormGroup.get('clientKey').value; clientSecurityConfigRPK.key = this.lwm2mConfigFormGroup.get('clientKey').value;
this.jsonAllConfig.client = clientSecurityConfigRPK; this.jsonAllConfig.client = clientSecurityConfigRPK;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
jsonAllConfig: this.jsonAllConfig,
clientCertificate: false clientCertificate: false
}, {emitEvent: true}) }, {emitEvent: false});
break; break;
case SECURITY_CONFIG_MODE.X509: case SECURITY_CONFIG_MODE.X509:
let clientSecurityConfigX509 = getDefaultClientSecurityConfigType(mode) as ClientSecurityConfigX509; this.jsonAllConfig.client = getDefaultClientSecurityConfigType(mode) as ClientSecurityConfigX509;
this.jsonAllConfig.client = clientSecurityConfigX509;
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
jsonAllConfig: this.jsonAllConfig,
clientCertificate: true clientCertificate: true
}, {emitEvent: true}) }, {emitEvent: false});
break; break;
} }
this.securityConfigClientUpdateValidators(mode); this.securityConfigClientUpdateValidators(mode);
} }
securityConfigClientUpdateValidators(mode: SECURITY_CONFIG_MODE): void { private securityConfigClientUpdateValidators = (mode: SECURITY_CONFIG_MODE): void => {
switch (mode) { switch (mode) {
case SECURITY_CONFIG_MODE.NO_SEC: case SECURITY_CONFIG_MODE.NO_SEC:
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]); this.setValidatorsNoSecX509();
this.lwm2mConfigFormGroup.get('identityPSK').updateValueAndValidity();
this.lwm2mConfigFormGroup.get('clientKey').setValidators([]);
this.lwm2mConfigFormGroup.get('clientKey').updateValueAndValidity();
break; break;
case SECURITY_CONFIG_MODE.PSK: case SECURITY_CONFIG_MODE.PSK:
this.lenMaxKeyClient = LEN_MAX_PSK; this.lenMaxKeyClient = LEN_MAX_PSK;
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]); this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('identityPSK').updateValueAndValidity();
this.lwm2mConfigFormGroup.get('clientKey').setValidators([Validators.required, Validators.pattern(KEY_IDENT_REGEXP_PSK)]);
this.lwm2mConfigFormGroup.get('clientKey').updateValueAndValidity();
break; break;
case SECURITY_CONFIG_MODE.RPK: case SECURITY_CONFIG_MODE.RPK:
this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK;
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]); this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('identityPSK').updateValueAndValidity();
this.lwm2mConfigFormGroup.get('clientKey').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_PSK)]);
this.lwm2mConfigFormGroup.get('clientKey').updateValueAndValidity();
break; break;
case SECURITY_CONFIG_MODE.X509: case SECURITY_CONFIG_MODE.X509:
this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK;
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]); this.setValidatorsNoSecX509();
this.lwm2mConfigFormGroup.get('identityPSK').updateValueAndValidity();
this.lwm2mConfigFormGroup.get('clientKey').setValidators([]);
this.lwm2mConfigFormGroup.get('clientKey').updateValueAndValidity();
break; break;
} }
this.lwm2mConfigFormGroup.updateValueAndValidity();
}
private setValidatorsNoSecX509 = (): void => {
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]);
this.lwm2mConfigFormGroup.get('clientKey').setValidators([]);
}
private setValidatorsPskRpk = (mode: SECURITY_CONFIG_MODE): void => {
if (mode === SECURITY_CONFIG_MODE.PSK) {
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([Validators.required]);
} else {
this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]);
}
this.lwm2mConfigFormGroup.get('clientKey').setValidators([Validators.required,
Validators.pattern(KEY_REGEXP_HEX_DEC),
Validators.maxLength(this.lenMaxKeyClient), Validators.minLength(this.lenMaxKeyClient)]);
} }
tabChanged = (tabChangeEvent: MatTabChangeEvent): void => { tabChanged = (tabChangeEvent: MatTabChangeEvent): void => {
if (this.tabIndexPrevious !== tabChangeEvent.index) this.upDateValueToJson(); if (this.tabIndexPrevious !== tabChangeEvent.index) { this.upDateValueToJson(); }
this.tabIndexPrevious = tabChangeEvent.index; this.tabIndexPrevious = tabChangeEvent.index;
} }
upDateValueToJson(): void { private upDateValueToJson(): void {
switch (this.tabIndexPrevious) { switch (this.tabIndexPrevious) {
case 0: case 0:
this.upDateValueToJsonTab_0(); this.upDateValueToJsonTab0();
break; break;
case 1: case 1:
this.upDateValueToJsonTab_1(); this.upDateValueToJsonTab1();
break; break;
case 2: case 2:
this.upDateValueToJsonTab_2(); this.upDateValueToJsonTab2();
break; break;
} }
} }
upDateValueToJsonTab_0(): void { private upDateValueToJsonTab0 = (): void => {
if (this.lwm2mConfigFormGroup !== null) { if (this.lwm2mConfigFormGroup !== null) {
if (!this.lwm2mConfigFormGroup.get('endPoint').pristine && this.lwm2mConfigFormGroup.get('endPoint').valid) { if (!this.lwm2mConfigFormGroup.get('endPoint').pristine && this.lwm2mConfigFormGroup.get('endPoint').valid) {
this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value; this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value;
// Client mode == PSK // Client mode == PSK
if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === SECURITY_CONFIG_MODE.PSK) { if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === SECURITY_CONFIG_MODE.PSK) {
this.jsonAllConfig.client["endpoint"] = this.data.endPoint; const endPoint = 'endpoint';
this.jsonAllConfig.client["endpoint"].markAsPristine({ this.jsonAllConfig.client[endPoint] = this.data.endPoint;
this.jsonAllConfig.client[endPoint].markAsPristine({
onlySelf: true onlySelf: true
}); });
this.upDateJsonAllConfig(); this.upDateJsonAllConfig();
@ -261,7 +263,7 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
} }
} }
upDateValueToJsonTab_1(): void { private upDateValueToJsonTab1 = (): void => {
if (this.lwm2mConfigFormGroup !== null) { if (this.lwm2mConfigFormGroup !== null) {
if (this.bootstrapFormGroup !== null && !this.bootstrapFormGroup.pristine && this.bootstrapFormGroup.valid) { if (this.bootstrapFormGroup !== null && !this.bootstrapFormGroup.pristine && this.bootstrapFormGroup.valid) {
this.jsonAllConfig.bootstrap.bootstrapServer = this.bootstrapFormGroup.value; this.jsonAllConfig.bootstrap.bootstrapServer = this.bootstrapFormGroup.value;
@ -281,8 +283,9 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
} }
} }
upDateValueToJsonTab_2(): void { private upDateValueToJsonTab2 = (): void => {
if (!this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).pristine && this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).valid) { if (!this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).pristine &&
this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).valid) {
this.jsonAllConfig = this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).value; this.jsonAllConfig = this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).value;
this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).markAsPristine({ this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).markAsPristine({
onlySelf: true onlySelf: true
@ -290,15 +293,17 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
} }
} }
updateIdentityPSK(): void { private updateIdentityPSK = (): void => {
if (this.lwm2mConfigFormGroup.get('bootstrapServer').value['securityMode'] === SECURITY_CONFIG_MODE.PSK.toString()) { const securityMode = 'securityMode';
if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) {
this.lwm2mConfigFormGroup.get('bootstrapFormGroup').patchValue({ this.lwm2mConfigFormGroup.get('bootstrapFormGroup').patchValue({
clientPublicKeyOrId: this.lwm2mConfigFormGroup.get('identityPSK').value clientPublicKeyOrId: this.lwm2mConfigFormGroup.get('identityPSK').value
}); });
this.jsonAllConfig.client['identity'] = this.lwm2mConfigFormGroup.get('identityPSK').value; const identity = 'identity';
this.jsonAllConfig.client[identity] = this.lwm2mConfigFormGroup.get('identityPSK').value;
this.upDateJsonAllConfig(); this.upDateJsonAllConfig();
} }
if (this.lwm2mConfigFormGroup.get('lwm2mServer').value['securityMode'] === SECURITY_CONFIG_MODE.PSK.toString()) { if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) {
this.lwm2mConfigFormGroup.get('lwm2mServerFormGroup').patchValue({ this.lwm2mConfigFormGroup.get('lwm2mServerFormGroup').patchValue({
clientPublicKeyOrId: this.lwm2mConfigFormGroup.get('identityPSK').value clientPublicKeyOrId: this.lwm2mConfigFormGroup.get('identityPSK').value
}); });
@ -307,24 +312,26 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
} }
} }
updateClientKey(): void { private updateClientKey = (): void => {
this.jsonAllConfig.client["key"] = this.lwm2mConfigFormGroup.get('clientKey').value; const key = 'key';
if (this.lwm2mConfigFormGroup.get('bootstrapServer').value['securityMode'] === SECURITY_CONFIG_MODE.PSK.toString()) { const securityMode = 'securityMode';
this.jsonAllConfig.client[key] = this.lwm2mConfigFormGroup.get('clientKey').value;
if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) {
this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue({ this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue({
clientSecretKey: this.jsonAllConfig.client["key"] clientSecretKey: this.jsonAllConfig.client[key]
}, {emitEvent: false}); }, {emitEvent: false});
this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client["key"]; this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client[key];
} }
if (this.lwm2mConfigFormGroup.get('lwm2mServer').value['securityMode'] === SECURITY_CONFIG_MODE.PSK.toString()) { if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) {
this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue({ this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue({
clientSecretKey: this.jsonAllConfig.client["key"] clientSecretKey: this.jsonAllConfig.client[key]
}, {emitEvent: false}); }, {emitEvent: false});
this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client["key"]; this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client[key];
} }
this.upDateJsonAllConfig(); this.upDateJsonAllConfig();
} }
upDateJsonAllConfig(): void { private upDateJsonAllConfig = (): void => {
this.data.jsonAllConfig = JSON.parse(JSON.stringify(this.jsonAllConfig)); this.data.jsonAllConfig = JSON.parse(JSON.stringify(this.jsonAllConfig));
this.lwm2mConfigFormGroup.patchValue({ this.lwm2mConfigFormGroup.patchValue({
jsonAllConfig: JSON.parse(JSON.stringify(this.jsonAllConfig)) jsonAllConfig: JSON.parse(JSON.stringify(this.jsonAllConfig))
@ -332,17 +339,10 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
this.lwm2mConfigFormGroup.markAsDirty(); this.lwm2mConfigFormGroup.markAsDirty();
} }
upDateBootstrapFormGroup(): void { private initLwm2mConfigFormGroup = (): FormGroup => {
this.data.jsonAllConfig = JSON.parse(JSON.stringify(this.jsonAllConfig));
this.lwm2mConfigFormGroup.patchValue({
jsonAllConfig: JSON.parse(JSON.stringify(this.jsonAllConfig))
}, {emitEvent: false});
this.lwm2mConfigFormGroup.markAsDirty();
}
initLwm2mConfigFormGroup(): FormGroup {
if (SECURITY_CONFIG_MODE[this.jsonAllConfig.client.securityConfigClientMode.toString()] === SECURITY_CONFIG_MODE.PSK) { if (SECURITY_CONFIG_MODE[this.jsonAllConfig.client.securityConfigClientMode.toString()] === SECURITY_CONFIG_MODE.PSK) {
this.data.endPoint = this.jsonAllConfig.client['endpoint']; const endpoint = 'endpoint';
this.data.endPoint = this.jsonAllConfig.client[endpoint];
} }
return this.fb.group({ return this.fb.group({
securityConfigClientMode: [SECURITY_CONFIG_MODE[this.jsonAllConfig.client.securityConfigClientMode.toString()], []], securityConfigClientMode: [SECURITY_CONFIG_MODE[this.jsonAllConfig.client.securityConfigClientMode.toString()], []],
@ -351,27 +351,28 @@ export class SecurityConfigComponent extends DialogComponent<SecurityConfigCompo
clientCertificate: [false, []], clientCertificate: [false, []],
bootstrapServer: [this.jsonAllConfig.bootstrap[this.bootstrapServer], []], bootstrapServer: [this.jsonAllConfig.bootstrap[this.bootstrapServer], []],
lwm2mServer: [this.jsonAllConfig.bootstrap[this.lwm2mServer], []], lwm2mServer: [this.jsonAllConfig.bootstrap[this.lwm2mServer], []],
bootstrapFormGroup: this.getServerGroup(true), bootstrapFormGroup: this.getServerGroup(),
lwm2mServerFormGroup: this.getServerGroup(false), lwm2mServerFormGroup: this.getServerGroup(),
endPoint: [this.data.endPoint, []], endPoint: [this.data.endPoint, []],
jsonAllConfig: [this.jsonAllConfig, []] jsonAllConfig: [this.jsonAllConfig, []]
}); });
} }
getServerGroup(bootstrapServerIs: boolean): FormGroup { private getServerGroup = (): FormGroup => {
return this.fb.group({ return this.fb.group({
securityMode: [this.fb.control(SECURITY_CONFIG_MODE.NO_SEC), []], securityMode: [this.fb.control(SECURITY_CONFIG_MODE.NO_SEC), []],
clientPublicKeyOrId: ['', []], clientPublicKeyOrId: ['', []],
clientSecretKey: ['', []] clientSecretKey: ['', []]
}) });
} }
save(): void { save(): void {
this.upDateValueToJson(); this.upDateValueToJson();
this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\"').join(''); this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\'').join('');
this.data.jsonAllConfig = this.jsonAllConfig; this.data.jsonAllConfig = this.jsonAllConfig;
if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === SECURITY_CONFIG_MODE.PSK) { if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === SECURITY_CONFIG_MODE.PSK) {
this.data.endPoint = this.data.jsonAllConfig.client["identity"]; const identity = 'identity';
this.data.endPoint = this.data.jsonAllConfig.client[identity];
} }
this.dialogRef.close(this.data); this.dialogRef.close(this.data);
} }

55
ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.models.ts

@ -25,15 +25,12 @@ export const LEN_MAX_PSK = 64;
export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PRIVATE_KEY = 134;
export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_RPK = 182;
export const LEN_MAX_PUBLIC_KEY_X509 = 3000; export const LEN_MAX_PUBLIC_KEY_X509 = 3000;
export const KEY_IDENT_REGEXP_PSK = /^[0-9a-fA-F]{64,64}$/; export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/;
export const KEY_PRIVATE_REGEXP = /^[0-9a-fA-F]{134,134}$/;
export const KEY_PUBLIC_REGEXP_PSK = /^[0-9a-fA-F]{182,182}$/;
export const KEY_PUBLIC_REGEXP_X509 = /^[0-9a-fA-F]{0,3000}$/;
export interface DeviceCredentialsDialogLwm2mData { export interface DeviceCredentialsDialogLwm2mData {
jsonAllConfig?: SecurityConfigModels; jsonAllConfig?: SecurityConfigModels;
endPoint?: string; endPoint?: string;
isNew?: boolean;
} }
export enum SECURITY_CONFIG_MODE { export enum SECURITY_CONFIG_MODE {
@ -56,43 +53,43 @@ export type ClientSecurityConfigType =
ClientSecurityConfigPSK ClientSecurityConfigPSK
| ClientSecurityConfigRPK | ClientSecurityConfigRPK
| ClientSecurityConfigX509 | ClientSecurityConfigX509
| ClientSecurityConfigNO_SEC; | ClientSecurityConfigNoSEC;
export interface ClientSecurityConfigPSK { export interface ClientSecurityConfigPSK {
securityConfigClientMode: string, securityConfigClientMode: string;
endpoint: string, endpoint: string;
identity: string, identity: string;
key: string key: string;
} }
export interface ClientSecurityConfigRPK { export interface ClientSecurityConfigRPK {
securityConfigClientMode: string, securityConfigClientMode: string;
key: string key: string;
} }
export interface ClientSecurityConfigX509 { export interface ClientSecurityConfigX509 {
securityConfigClientMode: string, securityConfigClientMode: string;
x509: boolean x509: boolean;
} }
export interface ClientSecurityConfigNO_SEC { export interface ClientSecurityConfigNoSEC {
securityConfigClientMode: string securityConfigClientMode: string;
} }
export interface ServerSecurityConfig { export interface ServerSecurityConfig {
securityMode: string, securityMode: string;
clientPublicKeyOrId?: string, clientPublicKeyOrId?: string;
clientSecretKey?: string clientSecretKey?: string;
} }
interface BootstrapSecurityConfig { interface BootstrapSecurityConfig {
bootstrapServer: ServerSecurityConfig, bootstrapServer: ServerSecurityConfig;
lwm2mServer: ServerSecurityConfig lwm2mServer: ServerSecurityConfig;
} }
export interface SecurityConfigModels { export interface SecurityConfigModels {
client: ClientSecurityConfigType, client: ClientSecurityConfigType;
bootstrap: BootstrapSecurityConfig bootstrap: BootstrapSecurityConfig;
} }
export function getDefaultClientSecurityConfigType(securityConfigMode: SECURITY_CONFIG_MODE, endPoint?: string): ClientSecurityConfigType { export function getDefaultClientSecurityConfigType(securityConfigMode: SECURITY_CONFIG_MODE, endPoint?: string): ClientSecurityConfigType {
@ -104,24 +101,24 @@ export function getDefaultClientSecurityConfigType(securityConfigMode: SECURITY_
endpoint: endPoint, endpoint: endPoint,
identity: endPoint, identity: endPoint,
key: '' key: ''
} };
break; break;
case SECURITY_CONFIG_MODE.RPK: case SECURITY_CONFIG_MODE.RPK:
security = { security = {
securityConfigClientMode: '', securityConfigClientMode: '',
key: '' key: ''
} };
break; break;
case SECURITY_CONFIG_MODE.X509: case SECURITY_CONFIG_MODE.X509:
security = { security = {
securityConfigClientMode: '', securityConfigClientMode: '',
x509: true x509: true
} };
break; break;
case SECURITY_CONFIG_MODE.NO_SEC: case SECURITY_CONFIG_MODE.NO_SEC:
security = { security = {
securityConfigClientMode: '' securityConfigClientMode: ''
} };
break; break;
} }
security.securityConfigClientMode = securityConfigMode.toString(); security.securityConfigClientMode = securityConfigMode.toString();
@ -133,14 +130,14 @@ export function getDefaultServerSecurityConfig(): ServerSecurityConfig {
securityMode: SECURITY_CONFIG_MODE.NO_SEC.toString(), securityMode: SECURITY_CONFIG_MODE.NO_SEC.toString(),
clientPublicKeyOrId: '', clientPublicKeyOrId: '',
clientSecretKey: '' clientSecretKey: ''
} };
} }
function getDefaultBootstrapSecurityConfig(): BootstrapSecurityConfig { function getDefaultBootstrapSecurityConfig(): BootstrapSecurityConfig {
return { return {
bootstrapServer: getDefaultServerSecurityConfig(), bootstrapServer: getDefaultServerSecurityConfig(),
lwm2mServer: getDefaultServerSecurityConfig() lwm2mServer: getDefaultServerSecurityConfig()
} };
} }
export function getDefaultSecurityConfig(): SecurityConfigModels { export function getDefaultSecurityConfig(): SecurityConfigModels {

6
ui-ngx/src/app/shared/components/json-object-edit.component.ts

@ -61,7 +61,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
@Input() editorStyle: { [klass: string]: any }; @Input() editorStyle: { [klass: string]: any };
@Input() sort: Function; @Input() sort: (key: string, value: any) => any;
private requiredValue: boolean; private requiredValue: boolean;
@ -227,8 +227,8 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
if (this.modelValue) { if (this.modelValue) {
this.contentValue = JSON.stringify(this.modelValue, isUndefined(this.sort) ? undefined : this.contentValue = JSON.stringify(this.modelValue, isUndefined(this.sort) ? undefined :
(key, value) => { (key, objectValue) => {
return this.sort(key, value) return this.sort(key, objectValue);
}, 2); }, 2);
this.objectValid = true; this.objectValid = true;
} else { } else {

2
ui-ngx/src/app/shared/models/device.models.ts

@ -454,8 +454,6 @@ export interface DeviceCredentials extends BaseData<DeviceCredentialsId> {
credentialsType: DeviceCredentialsType; credentialsType: DeviceCredentialsType;
credentialsId: string; credentialsId: string;
credentialsValue: string; credentialsValue: string;
credentialsLwKey: string;
credentialsLwValue: string;
} }
export interface DeviceCredentialMQTTBasic { export interface DeviceCredentialMQTTBasic {

11
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -893,9 +893,8 @@
"lwm2m-server": "LwM2M Server", "lwm2m-server": "LwM2M Server",
"client-publicKey-or-id": "Client Public Key or Id", "client-publicKey-or-id": "Client Public Key or Id",
"client-secret-key": "Client Secret Key", "client-secret-key": "Client Secret Key",
"config-json-tab": "Json Client Security Config", "config-json-tab": "Json Client Security Config",
"pattern_hex_dec_64": "must be hex decimal format and 64 characters", "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }"
"pattern_hex_dec_182": "must be hex decimal format and 182 characters"
}, },
"client-id": "Client ID", "client-id": "Client ID",
"client-id-pattern": "Contains invalid character.", "client-id-pattern": "Contains invalid character.",
@ -1103,6 +1102,7 @@
"no-objects-matching": "No objects matching '{{object}}' were found.", "no-objects-matching": "No objects matching '{{object}}' were found.",
"valid-id-instance-no-min": "Instance number '{{instance}}' no validated. Min value='{{min}}'", "valid-id-instance-no-min": "Instance number '{{instance}}' no validated. Min value='{{min}}'",
"valid-id-instance-no-max": "Instance number '{{instance}}' no validated. Max value='{{max}}'", "valid-id-instance-no-max": "Instance number '{{instance}}' no validated. Max value='{{max}}'",
"valid-id-instance": "Instance number '{{instance}}' no validated. { count, plural, 1 {Max value='{{max}}'} 2 {Min value='{{min}}'} other {Must be only number} }",
"model-tab": "LWM2M Model", "model-tab": "LWM2M Model",
"add-instances-tip": "Add new instances", "add-instances-tip": "Add new instances",
"instances-list": "Instances list", "instances-list": "Instances list",
@ -1121,10 +1121,7 @@
"key-name_label": "Key Name in Camel format", "key-name_label": "Key Name in Camel format",
"required": " value is required.", "required": " value is required.",
"mode": "Security config mode", "mode": "Security config mode",
"pattern_hex_dec_64": "must be hex decimal format and 64 characters", "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }",
"pattern_hex_dec_134": "must be hex decimal format and 134 characters",
"pattern_hex_dec_182": "must be hex decimal format and 182 characters",
"pattern_hex_dec": "must be hex decimal format",
"bootstrap-tab": "Bootstrap", "bootstrap-tab": "Bootstrap",
"servers": "Servers", "servers": "Servers",
"short-id": "Short ID", "short-id": "Short ID",

Loading…
Cancel
Save