Commit 7667afaeae02ab3dcc6a8bb389ad6f0ab14cb441

Authored by Andrew Shvayka
Committed by GitHub
1 parent 460cec99

Develop/lwm2m (#3826)

* LwM2M - Start transport

* LwM2M - Test endpoint

* LwM2M - Test endpoint

* LwM2M - Test add xml

* LwM2M device registration

* LwM2M - add get from client, add attributes and telemetry upgrade from registration client

* LwM2M - add get from client, add attributes and telemetry upgrade from registration client

* LwM2M implementation

* LwM2M - add to service telemetry and attribute

* LwM2M add to service attribute and telemetry

* LwM2M - add LWM2M_CREDENTIALS to DeviceCredentialsType

* LwM2M - add LWM2M_CREDENTIALS to DeviceCredentialsType

* LwM2M - add transport.process

* LwM2M - delete from yml tenantid, PSK -ok

* LwM2M - yml del tenantId

* LwM2M - add RPK

* LwM2M - add connect only x509 certificate. Crate certificates in serverKeyStore.jks and clientKeyStore.jks

* LwM2M - add no_sec

* LwM2M - add RPK & PSK integration test with app Client

* LwM2M - add RPK & PSK integration test with app Client

* LwM2M - add read JKS from file

* LwM2M - add read JKS from file

* LwM2M - add bootstrap cert

* LwM2M - add bootstrap RPK

* LwM2M - add bootstrap No_sec

* LwM2M - cleaned the code

* LwM2M - add to 3.0 in UI credentials lwm2m

* LwM2M - add to 3.0 in UI credentials lwm2m

* LwM2M - add to 3.0 in UI credentials lwm2m

* LwM2M - fix bug CoAP transport

* LwM2M: UI - add Json to credentials

* LwM2M: Back - add command "/3/0/5" - trigger client

* LwM2M: fix bug Json edit dialog

* LwM2M: fix bug Json edit dialog

* lwm2m: fix bug Json edit dialog: add validate

* lwm2m: UI add tabs

* lwm2m: UI add tabs (cleaner)

* lwm2m: add interface SecurityConfigModels

* lwm2m: add interface SecurityConfigModels2

* lwm2m: change html

* lwm2m: UI add bootstrap component

* lwm2m: UI add bootstrap component with FormControl

* lwm2m: UI add start Observe

* lwm2m: UI - correct

* lwm2m: UI - correct

* lwm2m: UI - add Validator: BS RPK, X509

* lwm2m: UI - add Observe

* lwm2m: UI - finish Observe

* lwm2m: UI - fix bug config-service update identity

* lwm2m: Bootstarp&Sewrver All config secure

* lwm2m: Bootstarp&Sewrver All config secure for new Front format

* lwm2m: Bootstarp&Sewrver Different config secure for new Front format

* lwm2m: Add attributes Gui and Backend

* lwm2m: Add attributes Gui and Backend final

* lwm2m: Add telemetrys to Gui

* lwm2m: Add Attribute & telemetry in Gui to instance

* lwm2m: Optimize Attr/Telemetry

* lwm2m: Optimize Attr/Telemetry

* lwm2m: Optimize Attr/Telemetry

* lwm2m: Optimize Attr/Telemetry for mobile

* lwm2m: Model folder

* lwm2m: Ok on AWS: NoSec, PSK, X509
bad registration - RPK

* lwm2m: KeyStore start only one

* lwm2m: Server observe ok

* lwm2m: Server fix bug finish session without remove

* lwm2m: Server add function installValue

* lwm2m: Server add function getAttrTelemetry to tingsboard

* lwm2m: Server add function installValue

* lwm2m: Server add function update Telemetry, Attr from observe

* lwm2m: Server add comments

* lwm2m: Server add session listener

* lwm2m: Server add onGetChangeCredentials with analyze

* lwm2m: Server add onGetChangeCredentials with analyze Onserve add

* lwm2m: Server: updated algorithm for analyzing dynamic changes in attributes / telemetry / observation

* lwm2m: fix bug:  "ngx-flowchart" compile

* lwm2m: get value resource OPAQUE - byte [] to HexString

* lwm2m: change path to base

* lwm2m: fix bug COAP & lwm2m

* Lwm2m_3_2: back: cleaner, test bootstrap-ok front: restore

* Lwm2m_3_2: back: del SynchronousRegistrationListener.java

* Lwm2m_3_2: front: start profile lwm2m UI

* Lwm2m_3_2: front&back: add to profile lwm2m (api, getModels...)

* Lwm2m_3_2: back: fix bug from commented front: add update change observe/attribute/telemetry to config json

* Lwm2m_3_2: back: fix bug from commented front: add update change observe/attribute/telemetry to config json (2)

* Lwm2m_3_2: back: fix bug from commented front: add update change observe/attribute/telemetry to config json (3)

* Lwm2m_3_2: back: fix bug from commented front: add update change observe/attribute/telemetry to config json (4)

* Lwm2m_3_2: front: add update change bootstrapConfig and save to config json

* Lwm2m_3_2: update after merge master

* lwm2m: fix bug proto

* lwm2m: fix bug in yml keyStore.jks

* lwm2m: fix bug tests

* lwm2m: front: add nameThingsboard

* lwm2m: fix bug Autowired lwm2mContext, caseCamel

* lwm2m: back-end^ start api /lwm2m/deviceProfile/bootstrap

* lwm2m: back-end: add method read models from resources

* lwm2m: back-end/front: add and finish api bootstrapConfig

* lwm2m: back-end: add decode profile

* lwm2m: back-end: add new bin in transport api

* lwm2m: add microservice lwm2m and docker lwm2m.

* lwm2m: add microservice lwm2m and docker lwm2m (fix bug)

* lwm2m: front: start fix bug disabled resources

* lwm2m: master to lwm2m merge, front add change attribute, telemetry

* lwm2m: front PR

* lwm2m: front add sort keyName to Json on the start

* lwm2m: front add instances

* lwm2m: front add/del instances FormGrp Value

* lwm2m: Merge remote-tracking branch 'origin/master' into develop/lwm2m
# Conflicts:
#	common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
#	common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java
#	ui-ngx/src/app/modules/home/components/home-components.module.ts

* lwm2m: Merge remote-tracking branch 'origin/master' into develop/lwm2m

* lwm2m: Front: del sort after add/del instance

* lwm2m: Front: fix bug reindex FormArray after update

* Lwm2m: Front fix bug add/del instans

* Lwm2m: Front finish1 profile

* Lwm2m: Back add profile to ModelClient

* Lwm2m: Back add form profile sent thingsboard: attr/tel/observe

* Lwm2m: Back -> fix bug: serverKeyStore.jks] Unable to load KeyStore  files server

* Lwm2m: Back -> fix bug: onRegistered an unReg

* Lwm2m: Back -> add updateProfiles

* Lwm2m: Back -> add updateDevice and updateProfile dynamic

* Lwm2m: Back -> error if CoapCode not access

* Lwm2m: Front -> clear credential

* Lwm2m: Front ->  credential fix bug button "save"

* Lwm2m: Back ->  add telemetry logLwm2m

Co-authored-by: nickAS21 <nick@avalr.com.ua>
Showing 49 changed files with 4439 additions and 58 deletions

Too many changes to show.

To preserve performance only 49 of 574 files are displayed.

... ... @@ -86,6 +86,10 @@
86 86 <artifactId>coap</artifactId>
87 87 </dependency>
88 88 <dependency>
  89 + <groupId>org.thingsboard.common.transport</groupId>
  90 + <artifactId>lwm2m</artifactId>
  91 + </dependency>
  92 + <dependency>
89 93 <groupId>org.thingsboard</groupId>
90 94 <artifactId>dao</artifactId>
91 95 </dependency>
... ...
... ... @@ -62,7 +62,7 @@ public class DeviceActor extends ContextAwareActor {
62 62 processor.processAttributesUpdate(ctx, (DeviceAttributesEventNotificationMsg) msg);
63 63 break;
64 64 case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
65   - processor.processCredentialsUpdate();
  65 + processor.processCredentialsUpdate(msg);
66 66 break;
67 67 case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
68 68 processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
... ...
... ... @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
24 24 import org.apache.commons.collections.CollectionUtils;
25 25 import org.thingsboard.rule.engine.api.RpcError;
26 26 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
  27 +import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
27 28 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
28 29 import org.thingsboard.server.actors.ActorSystemContext;
29 30 import org.thingsboard.server.actors.TbActorCtx;
... ... @@ -36,6 +37,9 @@ import org.thingsboard.server.common.data.kv.AttributeKey;
36 37 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
37 38 import org.thingsboard.server.common.data.kv.KvEntry;
38 39 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  40 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  41 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  42 +import org.thingsboard.server.common.msg.TbActorMsg;
39 43 import org.thingsboard.server.common.msg.TbMsgMetaData;
40 44 import org.thingsboard.server.common.msg.queue.TbCallback;
41 45 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
... ... @@ -61,6 +65,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM
61 65 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
62 66 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
63 67 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
  68 +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
64 69 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
65 70 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
66 71 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
... ... @@ -450,11 +455,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
450 455 dumpSessions();
451 456 }
452 457
453   - void processCredentialsUpdate() {
454   - sessions.forEach(this::notifyTransportAboutClosedSession);
455   - attributeSubscriptions.clear();
456   - rpcSubscriptions.clear();
457   - dumpSessions();
  458 + void processCredentialsUpdate(TbActorMsg msg) {
  459 + if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
  460 + log.info("1) LwM2Mtype: ");
  461 + sessions.forEach((k, v) -> {
  462 + notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
  463 + });
  464 + } else {
  465 + sessions.forEach(this::notifyTransportAboutClosedSession);
  466 + attributeSubscriptions.clear();
  467 + rpcSubscriptions.clear();
  468 + dumpSessions();
  469 +
  470 + }
458 471 }
459 472
460 473 private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) {
... ... @@ -465,6 +478,18 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
465 478 systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
466 479 }
467 480
  481 + void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
  482 + log.info("2) LwM2Mtype: ");
  483 + TransportProtos.ToTransportUpdateCredentialsProto.Builder notification = TransportProtos.ToTransportUpdateCredentialsProto.newBuilder();
  484 + notification.addCredentialsId(deviceCredentials.getCredentialsId());
  485 + notification.addCredentialsValue(deviceCredentials.getCredentialsValue());
  486 + ToTransportMsg msg = ToTransportMsg.newBuilder()
  487 + .setSessionIdMSB(sessionId.getMostSignificantBits())
  488 + .setSessionIdLSB(sessionId.getLeastSignificantBits())
  489 + .setToTransportUpdateCredentialsNotification(notification).build();
  490 + systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
  491 + }
  492 +
468 493 void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
469 494 this.deviceName = msg.getDeviceName();
470 495 this.deviceType = msg.getDeviceType();
... ...
... ... @@ -92,6 +92,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
92 92 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
93 93 import org.thingsboard.server.queue.util.TbCoreComponent;
94 94 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  95 +import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
95 96 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
96 97 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
97 98 import org.thingsboard.server.service.queue.TbClusterService;
... ... @@ -211,6 +212,9 @@ public abstract class BaseController {
211 212 @Autowired
212 213 protected TbDeviceProfileCache deviceProfileCache;
213 214
  215 + @Autowired
  216 + protected LwM2MModelsRepository lwM2MModelsRepository;
  217 +
214 218 @Value("${server.log_controller_error_stack_trace}")
215 219 @Getter
216 220 private boolean logControllerErrorStackTrace;
... ...
... ... @@ -278,9 +278,8 @@ public class DeviceController extends BaseController {
278 278 try {
279 279 Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS);
280 280 DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials));
281   -
282   - tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()), null);
283   -
  281 + //log.info("0 LwM2M CredentialsUpdate start)
  282 + tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null);
284 283 logEntityAction(device.getId(), device,
285 284 device.getCustomerId(),
286 285 ActionType.CREDENTIALS_UPDATED, null, deviceCredentials);
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.security.access.prepost.PreAuthorize;
  20 +import org.springframework.web.bind.annotation.*;
  21 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  22 +import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
  23 +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
  24 +import org.thingsboard.server.common.data.page.PageData;
  25 +import org.thingsboard.server.common.data.page.PageLink;
  26 +import org.thingsboard.server.queue.util.TbCoreComponent;
  27 +
  28 +import java.util.List;
  29 +
  30 +@Slf4j
  31 +@RestController
  32 +@TbCoreComponent
  33 +@RequestMapping("/api")
  34 +public class DeviceLwm2mController extends BaseController {
  35 +
  36 +
  37 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  38 + @RequestMapping(value = "/lwm2m/deviceProfile/{objectIds}", method = RequestMethod.GET)
  39 + @ResponseBody
  40 + public List<LwM2mObject> getLwm2mListObjects(@PathVariable("objectIds") int[] objectIds) throws ThingsboardException {
  41 + try {
  42 + return lwM2MModelsRepository.getLwm2mObjects(objectIds, null);
  43 + } catch (Exception e) {
  44 + throw handleException(e);
  45 + }
  46 + }
  47 +
  48 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  49 + @RequestMapping(value = "/lwm2m/deviceProfile/objects", params = {"pageSize", "page"}, method = RequestMethod.GET)
  50 + @ResponseBody
  51 + public PageData<LwM2mObject> getLwm2mListObjects(@RequestParam int pageSize,
  52 + @RequestParam int page,
  53 + @RequestParam(required = false) String textSearch,
  54 + @RequestParam(required = false) String sortProperty,
  55 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  56 + try {
  57 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  58 + return checkNotNull(lwM2MModelsRepository.findDeviceLwm2mObjects(getTenantId(), pageLink));
  59 + } catch (Exception e) {
  60 + throw handleException(e);
  61 + }
  62 + }
  63 +
  64 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  65 + @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)
  66 + @ResponseBody
  67 + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode,
  68 + @PathVariable("bootstrapServerIs") boolean bootstrapServerIs) throws ThingsboardException {
  69 + try {
  70 + return lwM2MModelsRepository.getBootstrapSecurityInfo(securityMode, bootstrapServerIs);
  71 + } catch (Exception e) {
  72 + throw handleException(e);
  73 + }
  74 + }
  75 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.lwm2m;
  17 +
  18 +
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.model.ObjectModel;
  21 +import org.eclipse.leshan.core.util.Hex;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  24 +import org.springframework.data.domain.PageImpl;
  25 +import org.springframework.stereotype.Service;
  26 +import org.thingsboard.server.common.data.lwm2m.*;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.page.PageData;
  29 +import org.thingsboard.server.common.data.page.PageLink;
  30 +import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
  31 +import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
  32 +import org.thingsboard.server.dao.service.Validator;
  33 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  34 +
  35 +import java.math.BigInteger;
  36 +import java.security.*;
  37 +import java.security.cert.CertificateEncodingException;
  38 +import java.security.cert.X509Certificate;
  39 +import java.security.spec.ECGenParameterSpec;
  40 +import java.security.spec.ECParameterSpec;
  41 +import java.security.spec.ECPublicKeySpec;
  42 +import java.security.spec.ECPoint;
  43 +import java.security.spec.KeySpec;
  44 +import java.util.List;
  45 +import java.util.ArrayList;
  46 +import java.util.function.Predicate;
  47 +import java.util.stream.Collectors;
  48 +import java.util.stream.IntStream;
  49 +
  50 +import static org.thingsboard.server.dao.service.Validator.validateId;
  51 +
  52 +@Slf4j
  53 +@Service
  54 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
  55 +public class LwM2MModelsRepository {
  56 +
  57 + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  58 +
  59 + @Autowired
  60 + LwM2MTransportConfigServer contextServer;
  61 +
  62 +
  63 + @Autowired
  64 + LwM2MTransportConfigBootstrap contextBootStrap;
  65 +
  66 + /**
  67 + * @param objectIds
  68 + * @param textSearch
  69 + * @return list of LwM2mObject
  70 + * Filter by Predicate (uses objectIds, if objectIds is null then it uses textSearch,
  71 + * if textSearch is null then it uses AllList from List<ObjectModel>)
  72 + */
  73 + public List<LwM2mObject> getLwm2mObjects(int[] objectIds, String textSearch) {
  74 + return getLwm2mObjects((objectIds != null && objectIds.length > 0) ?
  75 + (ObjectModel element) -> IntStream.of(objectIds).anyMatch(x -> x == element.id) :
  76 + (textSearch != null && !textSearch.isEmpty()) ? (ObjectModel element) -> element.name.contains(textSearch) : null);
  77 + }
  78 +
  79 + /**
  80 + * @param predicate
  81 + * @return list of LwM2mObject
  82 + */
  83 + private List<LwM2mObject> getLwm2mObjects(Predicate<? super ObjectModel> predicate) {
  84 + List<LwM2mObject> lwM2mObjects = new ArrayList<>();
  85 + List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValue() :
  86 + contextServer.getModelsValue().stream()
  87 + .filter(predicate)
  88 + .collect(Collectors.toList());
  89 + listObjects.forEach(obj -> {
  90 + LwM2mObject lwM2mObject = new LwM2mObject();
  91 + lwM2mObject.setId(obj.id);
  92 + lwM2mObject.setName(obj.name);
  93 + lwM2mObject.setMultiple(obj.multiple);
  94 + lwM2mObject.setMandatory(obj.mandatory);
  95 + LwM2mInstance instance = new LwM2mInstance();
  96 + instance.setId(0);
  97 + List<LwM2mResource> resources = new ArrayList<>();
  98 + obj.resources.forEach((k, v) -> {
  99 + if (!v.operations.isExecutable()) {
  100 + LwM2mResource resource = new LwM2mResource(k, v.name, false, false, false);
  101 + resources.add(resource);
  102 + }
  103 + });
  104 + instance.setResources(resources.stream().toArray(LwM2mResource[]::new));
  105 + lwM2mObject.setInstances(new LwM2mInstance[]{instance});
  106 + lwM2mObjects.add(lwM2mObject);
  107 + });
  108 + return lwM2mObjects;
  109 + }
  110 +
  111 + /**
  112 + * @param tenantId
  113 + * @param pageLink
  114 + * @return List of LwM2mObject in PageData format
  115 + */
  116 + public PageData<LwM2mObject> findDeviceLwm2mObjects(TenantId tenantId, PageLink pageLink) {
  117 + log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink);
  118 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  119 + Validator.validatePageLink(pageLink);
  120 + return this.findLwm2mListObjects(pageLink);
  121 + }
  122 +
  123 + /**
  124 + * @param pageLink
  125 + * @return List of LwM2mObject in PageData format, filter == TextSearch
  126 + * PageNumber = 1, PageSize = List<LwM2mObject>.size()
  127 + */
  128 + public PageData<LwM2mObject> findLwm2mListObjects(PageLink pageLink) {
  129 + PageImpl page = new PageImpl(getLwm2mObjects(null, pageLink.getTextSearch()));
  130 + PageData pageData = new PageData(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext());
  131 + return pageData;
  132 + }
  133 +
  134 + /**
  135 + *
  136 + * @param securityMode
  137 + * @param bootstrapServerIs
  138 + * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
  139 + */
  140 + public ServerSecurityConfig getBootstrapSecurityInfo(String securityMode, boolean bootstrapServerIs) {
  141 + LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase());
  142 + return getBootstrapServer(bootstrapServerIs, lwM2MSecurityMode);
  143 + }
  144 +
  145 + /**
  146 + *
  147 + * @param bootstrapServerIs
  148 + * @param mode
  149 + * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
  150 + */
  151 + private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) {
  152 + ServerSecurityConfig bsServ = new ServerSecurityConfig();
  153 + if (bootstrapServerIs) {
  154 + switch (mode) {
  155 + case NO_SEC:
  156 + bsServ.setHost(contextBootStrap.getBootstrapHost());
  157 + bsServ.setPort(contextBootStrap.getBootstrapPort());
  158 + bsServ.setServerPublicKey("");
  159 + break;
  160 + case PSK:
  161 + bsServ.setHost(contextBootStrap.getBootstrapSecureHost());
  162 + bsServ.setPort(contextBootStrap.getBootstrapSecurePort());
  163 + bsServ.setServerPublicKey("");
  164 + break;
  165 + case RPK:
  166 + bsServ.setHost(contextBootStrap.getBootstrapSecureHost());
  167 + bsServ.setPort(contextBootStrap.getBootstrapSecurePort());
  168 + bsServ.setServerPublicKey(getRPKPublicKey(this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY()));
  169 + break;
  170 + case X509:
  171 + bsServ.setHost(contextBootStrap.getBootstrapSecureHost());
  172 + bsServ.setPort(contextBootStrap.getBootstrapSecurePortCert());
  173 + bsServ.setServerPublicKey(getServerPublicKeyX509(contextBootStrap.getBootstrapAlias()));
  174 + break;
  175 + default:
  176 + break;
  177 + }
  178 + } else {
  179 + bsServ.setBootstrapServerIs(bootstrapServerIs);
  180 + bsServ.setServerId(123);
  181 + switch (mode) {
  182 + case NO_SEC:
  183 + bsServ.setHost(contextServer.getServerHost());
  184 + bsServ.setPort(contextServer.getServerPort());
  185 + bsServ.setServerPublicKey("");
  186 + break;
  187 + case PSK:
  188 + bsServ.setHost(contextServer.getServerSecureHost());
  189 + bsServ.setPort(contextServer.getServerSecurePort());
  190 + bsServ.setServerPublicKey("");
  191 + break;
  192 + case RPK:
  193 + bsServ.setHost(contextServer.getServerSecureHost());
  194 + bsServ.setPort(contextServer.getServerSecurePort());
  195 + bsServ.setServerPublicKey(getRPKPublicKey(this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY()));
  196 + break;
  197 + case X509:
  198 + bsServ.setHost(contextServer.getServerSecureHost());
  199 + bsServ.setPort(contextServer.getServerSecurePortCert());
  200 + bsServ.setServerPublicKey(getServerPublicKeyX509(contextServer.getServerAlias()));
  201 + break;
  202 + default:
  203 + break;
  204 + }
  205 + }
  206 + return bsServ;
  207 + }
  208 +
  209 + /**
  210 + *
  211 + * @param alias
  212 + * @return PublicKey format HexString or null
  213 + */
  214 + private String getServerPublicKeyX509 (String alias) {
  215 + try {
  216 + X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias);
  217 + return Hex.encodeHexString(serverCertificate.getEncoded());
  218 + } catch (CertificateEncodingException | KeyStoreException e) {
  219 + e.printStackTrace();
  220 + }
  221 + return null;
  222 + }
  223 +
  224 + /**
  225 + *
  226 + * @param publicServerX
  227 + * @param publicServerY
  228 + * @return PublicKey format HexString or null
  229 + */
  230 + private String getRPKPublicKey(String publicServerX, String publicServerY) {
  231 + try {
  232 + /** Get Elliptic Curve Parameter spec for secp256r1 */
  233 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  234 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  235 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  236 + if (publicServerX != null && !publicServerX.isEmpty() && publicServerY != null && !publicServerY.isEmpty()) {
  237 + /** Get point values */
  238 + byte[] publicX = Hex.decodeHex(publicServerX.toCharArray());
  239 + byte[] publicY = Hex.decodeHex(publicServerY.toCharArray());
  240 + /** Create key specs */
  241 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  242 + parameterSpec);
  243 + /** Get keys */
  244 + PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  245 + if (publicKey != null && publicKey.getEncoded().length > 0 ) {
  246 + return Hex.encodeHexString(publicKey.getEncoded());
  247 + }
  248 + }
  249 + } catch (GeneralSecurityException | IllegalArgumentException e) {
  250 + log.error("[{}] Failed generate Server RPK for profile", e.getMessage());
  251 + throw new RuntimeException(e);
  252 + }
  253 + return null;
  254 + }
  255 +}
  256 +
... ...
... ... @@ -53,6 +53,8 @@ public class DefaultDeviceAuthService implements DeviceAuthService {
53 53 return DeviceAuthResult.of(credentials.getDeviceId());
54 54 case X509_CERTIFICATE:
55 55 return DeviceAuthResult.of(credentials.getDeviceId());
  56 + case LWM2M_CREDENTIALS:
  57 + return DeviceAuthResult.of(credentials.getDeviceId());
56 58 default:
57 59 return DeviceAuthResult.of("Credentials Type is not supported yet!");
58 60 }
... ... @@ -65,4 +67,4 @@ public class DefaultDeviceAuthService implements DeviceAuthService {
65 67 }
66 68 }
67 69
68   -}
\ No newline at end of file
  70 +}
... ...
... ... @@ -201,6 +201,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
201 201 }
202 202
203 203 @Override
  204 +
204 205 public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
205 206 saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
206 207 }
... ...
... ... @@ -69,6 +69,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponse
69 69 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
70 70 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
71 71 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
  72 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
72 73 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
73 74 import org.thingsboard.server.queue.util.TbCoreComponent;
74 75 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
... ... @@ -149,6 +150,13 @@ public class DefaultTransportApiService implements TransportApiService {
149 150 } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) {
150 151 return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()),
151 152 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  153 + } else if (transportApiRequestMsg.hasLwM2MRequestMsg()) {
  154 + return Futures.transform(handle(transportApiRequestMsg.getLwM2MRequestMsg()),
  155 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  156 + } else if (transportApiRequestMsg.hasValidateDeviceLwM2MCredentialsRequestMsg()) {
  157 + ValidateDeviceLwM2MCredentialsRequestMsg msg = transportApiRequestMsg.getValidateDeviceLwM2MCredentialsRequestMsg();
  158 + return Futures.transform(validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS),
  159 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
152 160 } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) {
153 161 return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()),
154 162 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
... ... @@ -396,4 +404,40 @@ public class DefaultTransportApiService implements TransportApiService {
396 404 return TransportApiResponseMsg.newBuilder()
397 405 .setValidateCredResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build();
398 406 }
  407 +
  408 + private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.LwM2MRequestMsg requestMsg) {
  409 + if (requestMsg.hasRegistrationMsg()) {
  410 + return handleRegistration(requestMsg.getRegistrationMsg());
  411 + } else {
  412 + return Futures.immediateFailedFuture(new RuntimeException("Not supported!"));
  413 + }
  414 + }
  415 +
  416 + private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) {
  417 + TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId()));
  418 + String deviceName = msg.getEndpoint();
  419 + Lock deviceCreationLock = deviceCreationLocks.computeIfAbsent(deviceName, id -> new ReentrantLock());
  420 + deviceCreationLock.lock();
  421 + try {
  422 + Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
  423 + if (device == null) {
  424 + device = new Device();
  425 + device.setTenantId(tenantId);
  426 + device.setName(deviceName);
  427 + device.setType("LwM2M");
  428 + device = deviceService.saveDevice(device);
  429 + deviceStateService.onDeviceAdded(device);
  430 + }
  431 + TransportProtos.LwM2MRegistrationResponseMsg registrationResponseMsg =
  432 + TransportProtos.LwM2MRegistrationResponseMsg.newBuilder()
  433 + .setDeviceInfo(getDeviceInfoProto(device)).build();
  434 + TransportProtos.LwM2MResponseMsg responseMsg = TransportProtos.LwM2MResponseMsg.newBuilder().setRegistrationMsg(registrationResponseMsg).build();
  435 + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setLwM2MResponseMsg(responseMsg).build());
  436 + } catch (JsonProcessingException e) {
  437 + log.warn("[{}][{}] Failed to lookup device by gateway id and name", tenantId, deviceName, e);
  438 + throw new RuntimeException(e);
  439 + } finally {
  440 + deviceCreationLock.unlock();
  441 + }
  442 + }
399 443 }
... ...
  1 + ______ __ _ __ __
  2 + /_ __/ / /_ (_) ____ ____ _ _____ / /_ ____ ____ _ _____ ____/ /
  3 + / / / __ \ / / / __ \ / __ `/ / ___/ / __ \ / __ \ / __ `/ / ___/ / __ /
  4 + / / / / / / / / / / / / / /_/ / (__ ) / /_/ // /_/ // /_/ / / / / /_/ /
  5 +/_/ /_/ /_/ /_/ /_/ /_/ \__, / /____/ /_.___/ \____/ \__,_/ /_/ \__,_/
  6 + /____/
  7 +
1 8 ===================================================
2 9 :: ${application.title} :: ${application.formatted-version}
3 10 ===================================================
... ...
... ... @@ -433,7 +433,7 @@ spring:
433 433 database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}"
434 434 datasource:
435 435 driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}"
436   - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
  436 + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_ce_3_2_2}"
437 437 username: "${SPRING_DATASOURCE_USERNAME:postgres}"
438 438 password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
439 439 hikari:
... ... @@ -488,7 +488,7 @@ js:
488 488 # Built-in JVM JavaScript environment properties
489 489 local:
490 490 # Use Sandboxed (secured) JVM JavaScript environment
491   - use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:true}"
  491 + use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:false}"
492 492 # Specify thread pool size for JavaScript sandbox resource monitor
493 493 monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
494 494 # Maximum CPU time in milliseconds allowed for script execution
... ... @@ -565,6 +565,74 @@ transport:
565 565 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
566 566 bind_port: "${COAP_BIND_PORT:5683}"
567 567 timeout: "${COAP_TIMEOUT:10000}"
  568 + # Local LwM2M transport parameters
  569 + lwm2m:
  570 + # Enable/disable lvm2m transport protocol.
  571 + enabled: "${LWM2M_ENABLED:true}"
  572 + # We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to
  573 + # send a Confirmable message to the time when an acknowledgement is no longer expected.
  574 + # DEFAULT_TIMEOUT = 2 * 60 * 1000l; 2 min in ms
  575 + timeout: "${LWM2M_TIMEOUT:120000}"
  576 +# model_path_file: "${LWM2M_MODEL_PATH_FILE:./common/transport/lwm2m/src/main/resources/models/}"
  577 + model_path_file: "${LWM2M_MODEL_PATH_FILE:}"
  578 + support_deprecated_ciphers_enable: "${LWM2M_SUPPORT_DEPRECATED_CIPHERS_ENABLED:true}"
  579 + secure:
  580 + # Only Certificate_x509:
  581 + # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
  582 + # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
  583 + key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}"
  584 + # key_store_type: "${LWM2M_KEYSTORE_TYPE:PKCS12}"
  585 +# key_store_path_file: "${KEY_STORE_PATH_FILE:/usr/share/thingsboard/conf/credentials/serverKeyStore.jks}"
  586 + key_store_path_file: "${KEY_STORE_PATH_FILE:}"
  587 + key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}"
  588 + root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
  589 + enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}"
  590 + server:
  591 + bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
  592 + bind_port: "${LWM2M_BIND_PORT:5685}"
  593 + bind_port_cert: "${LWM2M_BIND_PORT_CERT:5687}"
  594 + secure:
  595 + start_all: "${START_SERVER_ALL:true}"
  596 + #leshan.core (V1_1)
  597 + #DTLS security modes:
  598 + #0: Pre-Shared Key mode
  599 + #1: Raw Public Key mode
  600 + #2: Certificate mode X509
  601 + #3: NoSec mode *
  602 + #OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A (add)
  603 + #4: Certificate mode X509 with EST
  604 + # If only startAll == false
  605 + dtls_mode: "${LWM2M_SECURITY_MODE:1}"
  606 + bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
  607 + bind_port: "${LWM2M_BIND_PORT_SEC:5686}"
  608 + bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT:5688}"
  609 + # Only RPK: Public & Private Key
  610 +# create_rpk: "${CREATE_RPK:}"
  611 + public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}"
  612 + public_y: "${LWM2M_SERVER_PUBLIC_Y:30c9237e946a3a1692c1cafaa01a238a077f632c99371348337512363f28212b}"
  613 + private_s: "${LWM2M_SERVER_PRIVATE_S:274671fe40ce937b8a6352cf0a418e8a39e4bf0bb9bf74c910db953c20c73802}"
  614 + # Only Certificate_x509:
  615 + alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
  616 + bootstrap:
  617 + enable: "${BOOTSTRAP:true}"
  618 + bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
  619 + bind_port: "${LWM2M_BIND_PORT_BS:5689}"
  620 + bind_port_cert: "${LWM2M_BIND_PORT_SER_BS:5691}"
  621 + secure:
  622 + start_all: "${START_BOOTSTRAP_ALL:true}"
  623 + # If only startAll == false
  624 + dtls_mode: "${LWM2M_SECURITY_MODE_BS:1}"
  625 + bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
  626 + bind_port: "${LWM2M_BIND_PORT_SEC_BS:5690}"
  627 + bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT_BS:5692}"
  628 + # Only RPK: Public & Private Key
  629 + public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}"
  630 + public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}"
  631 + private_s: "${LWM2M_SERVER_PRIVATE_S_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}"
  632 + # Only Certificate_x509:
  633 + alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}"
  634 + # Redis
  635 + redis_url: "${LWM2M_REDIS_URL:''}"
568 636
569 637 swagger:
570 638 api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}"
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.lwm2m;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class LwM2mInstance {
  22 + int id;
  23 + LwM2mResource [] resources;
  24 +
  25 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.lwm2m;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class LwM2mObject {
  22 + int id;
  23 + String name;
  24 + boolean multiple;
  25 + boolean mandatory;
  26 + LwM2mInstance [] instances;
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.lwm2m;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +
  21 +import java.util.stream.Stream;
  22 +
  23 +@Data
  24 +@AllArgsConstructor
  25 +public class LwM2mResource {
  26 + int id;
  27 + String name;
  28 + boolean observe;
  29 + boolean attribute;
  30 + boolean telemetry;
  31 + String keyName;
  32 +
  33 + public LwM2mResource(int id, String name, boolean observe, boolean attribute, boolean telemetry) {
  34 + this.id = id;
  35 + this.name = name;
  36 + this.observe = observe;
  37 + this.attribute = attribute;
  38 + this.telemetry = telemetry;
  39 + this.keyName = getCamelCase (this.name);
  40 + }
  41 +
  42 + private String getCamelCase (String name) {
  43 + name = name.replaceAll("-", " ");
  44 + name = name.replaceAll("_", " ");
  45 + String [] nameCamel1 = name.split(" ");
  46 + String [] nameCamel2 = new String[nameCamel1.length];
  47 + int[] idx = { 0 };
  48 + Stream.of(nameCamel1).forEach((s -> {
  49 + nameCamel2[idx[0]] = toProperCase(idx[0]++, s);
  50 + }));
  51 + return String.join("", nameCamel2);
  52 + }
  53 +
  54 + private String toProperCase(int idx, String s) {
  55 + if (!s.isEmpty() && s.length()> 0) {
  56 + String s1 = (idx == 0) ? s.substring(0, 1).toLowerCase() : s.substring(0, 1).toUpperCase();
  57 + String s2 = "";
  58 + if (s.length()> 1) s2 = s.substring(1).toLowerCase();
  59 + s = s1 + s2;
  60 + }
  61 + return s;
  62 + }
  63 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.lwm2m;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +
  21 +@Data
  22 +public class ServerSecurityConfig {
  23 + String host;
  24 + Integer port;
  25 + String serverPublicKey;
  26 + @Builder.Default
  27 + boolean bootstrapServerIs = true;
  28 + @Builder.Default
  29 + Integer clientHoldOffTime = 1;
  30 + @Builder.Default
  31 + Integer serverId = 111;
  32 + @Builder.Default
  33 + Integer bootstrapServerAccountTimeout = 0;
  34 +}
... ...
... ... @@ -19,6 +19,8 @@ public enum DeviceCredentialsType {
19 19
20 20 ACCESS_TOKEN,
21 21 X509_CERTIFICATE,
22   - MQTT_BASIC
  22 + MQTT_BASIC,
  23 + LWM2M_CREDENTIALS
  24 +
23 25
24 26 }
... ...
... ... @@ -183,6 +183,47 @@ message GetEntityProfileRequestMsg {
183 183 int64 entityIdLSB = 3;
184 184 }
185 185
  186 +message LwM2MRegistrationRequestMsg {
  187 + string tenantId = 1;
  188 + string endpoint = 2;
  189 +}
  190 +
  191 +message LwM2MRegistrationResponseMsg {
  192 + DeviceInfoProto deviceInfo = 1;
  193 +}
  194 +
  195 +message LwM2MRequestMsg {
  196 + LwM2MRegistrationRequestMsg registrationMsg = 1;
  197 +}
  198 +
  199 +message LwM2MResponseMsg {
  200 + LwM2MRegistrationResponseMsg registrationMsg = 1;
  201 +}
  202 +
  203 +message ValidateDeviceLwM2MCredentialsRequestMsg {
  204 + string credentialsId = 1;
  205 +}
  206 +
  207 +message ToTransportUpdateCredentialsProto {
  208 + repeated string credentialsId = 1;
  209 + repeated string credentialsValue = 2;
  210 +}
  211 +
  212 +message GetTenantRoutingInfoRequestMsg {
  213 + int64 tenantIdMSB = 1;
  214 + int64 tenantIdLSB = 2;
  215 +}
  216 +
  217 +message GetTenantRoutingInfoResponseMsg {
  218 + bool isolatedTbCore = 1;
  219 + bool isolatedTbRuleEngine = 2;
  220 +}
  221 +
  222 +message GetDeviceProfileRequestMsg {
  223 + int64 profileIdMSB = 1;
  224 + int64 profileIdLSB = 2;
  225 +}
  226 +
186 227 message GetEntityProfileResponseMsg {
187 228 string entityType = 1;
188 229 bytes data = 2;
... ... @@ -479,8 +520,10 @@ message TransportApiRequestMsg {
479 520 ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2;
480 521 GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3;
481 522 GetEntityProfileRequestMsg entityProfileRequestMsg = 4;
  523 + LwM2MRequestMsg lwM2MRequestMsg = 5;
482 524 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
483 525 ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
  526 + ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
484 527 }
485 528
486 529 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -489,6 +532,7 @@ message TransportApiResponseMsg {
489 532 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
490 533 GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
491 534 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
  535 + LwM2MResponseMsg lwM2MResponseMsg = 6;
492 536 }
493 537
494 538 /* Messages that are handled by ThingsBoard Core Service */
... ... @@ -529,10 +573,10 @@ message ToTransportMsg {
529 573 AttributeUpdateNotificationMsg attributeUpdateNotification = 5;
530 574 ToDeviceRpcRequestMsg toDeviceRequest = 6;
531 575 ToServerRpcResponseMsg toServerResponse = 7;
532   - /* For Tenant, TenantProfile and DeviceProfile */
533 576 EntityUpdateMsg entityUpdateMsg = 8;
534 577 EntityDeleteMsg entityDeleteMsg = 9;
535 578 ProvisionDeviceResponseMsg provisionResponse = 10;
  579 + ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11;
536 580 }
537 581
538 582 message UsageStatsKVProto{
... ...
... ... @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component;
24 24 import org.thingsboard.server.common.transport.TransportContext;
25 25 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
26 26
  27 +
27 28 /**
28 29 * Created by ashvayka on 18.10.18.
29 30 */
... ...
... ... @@ -16,14 +16,15 @@
16 16 package org.thingsboard.server.transport.coap;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.californium.core.CoapObserveRelation;
19 20 import org.eclipse.californium.core.CoapResource;
  21 +import org.eclipse.californium.core.coap.CoAP;
20 22 import org.eclipse.californium.core.coap.CoAP.ResponseCode;
21 23 import org.eclipse.californium.core.coap.Request;
  24 +import org.eclipse.californium.core.network.Endpoint;
22 25 import org.eclipse.californium.core.network.Exchange;
23   -import org.eclipse.californium.core.network.ExchangeObserver;
24 26 import org.eclipse.californium.core.server.resources.CoapExchange;
25 27 import org.eclipse.californium.core.server.resources.Resource;
26   -import org.springframework.util.ReflectionUtils;
27 28 import org.thingsboard.server.common.data.DataConstants;
28 29 import org.thingsboard.server.common.data.DeviceTransportType;
29 30 import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
... ... @@ -40,26 +41,26 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes
40 41 import org.thingsboard.server.gen.transport.TransportProtos;
41 42 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
42 43
43   -import java.lang.reflect.Field;
44 44 import java.util.List;
45 45 import java.util.Optional;
46 46 import java.util.Set;
47 47 import java.util.UUID;
  48 +import java.util.Timer;
  49 +import java.util.TimerTask;
48 50 import java.util.concurrent.ConcurrentHashMap;
49 51 import java.util.concurrent.ConcurrentMap;
  52 +import java.util.concurrent.ScheduledThreadPoolExecutor;
50 53 import java.util.concurrent.atomic.AtomicInteger;
51 54 import java.util.function.Consumer;
52 55
53 56 @Slf4j
54 57 public class CoapTransportResource extends CoapResource {
55   - // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]]
56 58 private static final int ACCESS_TOKEN_POSITION = 3;
57 59 private static final int FEATURE_TYPE_POSITION = 4;
58 60 private static final int REQUEST_ID_POSITION = 5;
59 61
60 62 private final CoapTransportContext transportContext;
61 63 private final TransportService transportService;
62   - private final Field observerField;
63 64 private final long timeout;
64 65 private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
65 66 private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
... ... @@ -73,9 +74,20 @@ public class CoapTransportResource extends CoapResource {
73 74 // This is important to turn off existing observable logic in
74 75 // CoapResource. We will have our own observe monitoring due to 1:1
75 76 // observe relationship.
76   - this.setObservable(false);
77   - observerField = ReflectionUtils.findField(Exchange.class, "observer");
78   - observerField.setAccessible(true);
  77 + this.setObservable(true); // enable observing
  78 + this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs
  79 + this.getAttributes().setObservable(); // mark observable in the Link-Format
  80 + // schedule a periodic update task, otherwise let events call changed()
  81 + Timer timer = new Timer();
  82 + timer.schedule(new UpdateTask(), 0, 5000);
  83 + }
  84 +
  85 + private class UpdateTask extends TimerTask {
  86 + @Override
  87 + public void run() {
  88 + // .. periodic update of the resource
  89 + changed(); // notify all observers
  90 + }
79 91 }
80 92
81 93 @Override
... ... @@ -187,9 +199,7 @@ public class CoapTransportResource extends CoapResource {
187 199 new CoapOkCallback(exchange));
188 200 break;
189 201 case SUBSCRIBE_ATTRIBUTES_REQUEST:
190   - attributeSubscriptions.add(sessionId);
191   - advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced),
192   - registerAsyncCoapSession(exchange, request, sessionInfo, sessionId)));
  202 + transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout());
193 203 transportService.process(sessionInfo,
194 204 TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(),
195 205 new CoapNoOpCallback(exchange));
... ... @@ -206,8 +216,6 @@ public class CoapTransportResource extends CoapResource {
206 216 break;
207 217 case SUBSCRIBE_RPC_COMMANDS_REQUEST:
208 218 rpcSubscriptions.add(sessionId);
209   - advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced),
210   - registerAsyncCoapSession(exchange, request, sessionInfo, sessionId)));
211 219 transportService.process(sessionInfo,
212 220 TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
213 221 new CoapNoOpCallback(exchange));
... ... @@ -243,9 +251,6 @@ public class CoapTransportResource extends CoapResource {
243 251 } catch (AdaptorException e) {
244 252 log.trace("[{}] Failed to decode message: ", sessionId, e);
245 253 exchange.respond(ResponseCode.BAD_REQUEST);
246   - } catch (IllegalAccessException e) {
247   - log.trace("[{}] Failed to process message: ", sessionId, e);
248   - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
249 254 }
250 255 }));
251 256 }
... ... @@ -459,24 +464,17 @@ public class CoapTransportResource extends CoapResource {
459 464 }
460 465 }
461 466
462   - public class CoapExchangeObserverProxy implements ExchangeObserver {
463   -
464   - private final ExchangeObserver proxy;
465   - private final String token;
466   -
467   - CoapExchangeObserverProxy(ExchangeObserver proxy, String token) {
468   - super();
469   - this.proxy = proxy;
470   - this.token = token;
471   - }
472   -
473   - @Override
474   - public void completed(Exchange exchange) {
475   - proxy.completed(exchange);
476   - TransportProtos.SessionInfoProto session = tokenToSessionIdMap.remove(token);
477   - if (session != null) {
478   - closeAndDeregister(session);
479   - }
  467 + public class CoapExchangeObserverProxy extends CoapObserveRelation {
  468 +
  469 + /**
  470 + * Constructs a new CoapObserveRelation with the specified request.
  471 + *
  472 + * @param request the request
  473 + * @param endpoint the endpoint
  474 + * @param executor
  475 + */
  476 + protected CoapExchangeObserverProxy(Request request, Endpoint endpoint, ScheduledThreadPoolExecutor executor) {
  477 + super(request, endpoint, executor);
480 478 }
481 479 }
482 480
... ...
... ... @@ -18,8 +18,9 @@ package org.thingsboard.server.transport.coap;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.eclipse.californium.core.CoapResource;
20 20 import org.eclipse.californium.core.CoapServer;
  21 +
21 22 import org.eclipse.californium.core.network.CoapEndpoint;
22   -import org.eclipse.californium.core.network.config.NetworkConfig;
  23 +import org.eclipse.californium.core.network.CoapEndpoint.Builder;
23 24 import org.springframework.beans.factory.annotation.Autowired;
24 25 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
25 26 import org.springframework.stereotype.Service;
... ... @@ -47,11 +48,15 @@ public class CoapTransportService {
47 48 public void init() throws UnknownHostException {
48 49 log.info("Starting CoAP transport...");
49 50 log.info("Starting CoAP transport server");
50   - this.server = new CoapServer(NetworkConfig.createStandardWithoutFile());
  51 + this.server = new CoapServer();
51 52 createResources();
52 53 InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
53 54 InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
54   - server.addEndpoint(new CoapEndpoint(sockAddr));
  55 + Builder builder = new Builder();
  56 + builder.setInetSocketAddress(sockAddr);
  57 + CoapEndpoint coapEndpoint = builder.build();
  58 +
  59 + server.addEndpoint(coapEndpoint);
55 60 server.start();
56 61 log.info("CoAP transport started!");
57 62 }
... ...
... ... @@ -31,6 +31,7 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter;
31 31 import org.thingsboard.server.gen.transport.TransportProtos;
32 32 import org.thingsboard.server.transport.coap.CoapTransportResource;
33 33
  34 +
34 35 import java.util.Arrays;
35 36 import java.util.HashSet;
36 37 import java.util.List;
... ...
... ... @@ -28,6 +28,7 @@ import org.eclipse.californium.core.CoapClient;
28 28 import org.eclipse.californium.core.CoapHandler;
29 29 import org.eclipse.californium.core.CoapResponse;
30 30 import org.eclipse.californium.core.coap.MediaTypeRegistry;
  31 +import org.eclipse.californium.elements.exception.ConnectorException;
31 32 import org.thingsboard.server.common.msg.session.FeatureType;
32 33 import org.slf4j.Logger;
33 34 import org.slf4j.LoggerFactory;
... ... @@ -61,7 +62,7 @@ public class DeviceEmulator {
61 62 this.attributesClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.ATTRIBUTES));
62 63 this.telemetryClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.TELEMETRY));
63 64 this.rpcClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.RPC));
64   - this.keys = keys.split(",");
  65 + this.keys = (keys != null && !keys.isEmpty()) ? keys.split(",") : null;
65 66 }
66 67
67 68 public void start() {
... ... @@ -72,11 +73,8 @@ public class DeviceEmulator {
72 73 try {
73 74 sendObserveRequest(rpcClient);
74 75 while (!Thread.interrupted()) {
75   -
76   -
77 76 sendRequest(attributesClient, createAttributesRequest());
78 77 sendRequest(telemetryClient, createTelemetryRequest());
79   -
80 78 Thread.sleep(1000);
81 79 }
82 80 } catch (Exception e) {
... ... @@ -84,8 +82,8 @@ public class DeviceEmulator {
84 82 }
85 83 }
86 84
87   - private void sendRequest(CoapClient client, JsonNode request) throws JsonProcessingException {
88   - CoapResponse telemetryResponse = client.setTimeout(60000).post(mapper.writeValueAsString(request),
  85 + private void sendRequest(CoapClient client, JsonNode request) throws IOException, ConnectorException {
  86 + CoapResponse telemetryResponse = client.setTimeout((long) 60000).post(mapper.writeValueAsString(request),
89 87 MediaTypeRegistry.APPLICATION_JSON);
90 88 log.info("Response: {}, {}", telemetryResponse.getCode(), telemetryResponse.getResponseText());
91 89 }
... ... @@ -113,6 +111,7 @@ public class DeviceEmulator {
113 111
114 112 @Override
115 113 public void onError() {
  114 + log.info("Command Response Ack Error, No connect");
116 115 //Do nothing
117 116 }
118 117 }, mapper.writeValueAsString(response), MediaTypeRegistry.APPLICATION_JSON);
... ... @@ -157,6 +156,15 @@ public class DeviceEmulator {
157 156 if (args.length != 4) {
158 157 System.out.println("Usage: java -jar " + DeviceEmulator.class.getSimpleName() + ".jar host port device_token keys");
159 158 }
  159 + /**
  160 + * DeviceEmulator(String host, int port, String token, String keys)
  161 + * args[]:
  162 + * host = "localhost",
  163 + * port = 0,
  164 + * token = "{Tokrn device from thingboard}"), kSzbDRGwaZqZ6Y25gTLF
  165 + * keys = "{Telemetry}"
  166 + *
  167 + */
160 168 final DeviceEmulator emulator = new DeviceEmulator(args[0], Integer.parseInt(args[1]), args[2], args[3]);
161 169 emulator.start();
162 170 Runtime.getRuntime().addShutdownHook(new Thread() {
... ... @@ -167,7 +175,6 @@ public class DeviceEmulator {
167 175 });
168 176 }
169 177
170   -
171 178 private String getFeatureTokenUrl(String host, int port, String token, FeatureType featureType) {
172 179 return getBaseUrl(host, port) + token + "/" + featureType.name().toLowerCase();
173 180 }
... ...
... ... @@ -327,6 +327,7 @@ public class DeviceApiController {
327 327 public void onToServerRpcResponse(ToServerRpcResponseMsg msg) {
328 328 responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
329 329 }
  330 +
330 331 }
331 332
332 333 private void reportActivity(SessionInfoProto sessionInfo) {
... ...
  1 +
  2 +<!--
  3 +
  4 + Copyright © 2016-2020 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  20 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  21 + <modelVersion>4.0.0</modelVersion>
  22 + <parent>
  23 + <groupId>org.thingsboard.common</groupId>
  24 + <version>3.2.0-SNAPSHOT</version>
  25 + <artifactId>transport</artifactId>
  26 + </parent>
  27 + <groupId>org.thingsboard.common.transport</groupId>
  28 + <artifactId>lwm2m</artifactId>
  29 + <packaging>jar</packaging>
  30 +
  31 + <name>Thingsboard LwM2M Transport Common</name>
  32 + <url>https://thingsboard.io</url>
  33 +
  34 + <properties>
  35 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  36 + <main.dir>${basedir}/../../..</main.dir>
  37 + </properties>
  38 +
  39 + <dependencies>
  40 + <dependency>
  41 + <groupId>org.thingsboard.common.transport</groupId>
  42 + <artifactId>transport-api</artifactId>
  43 + </dependency>
  44 + <dependency>
  45 + <groupId>org.springframework</groupId>
  46 + <artifactId>spring-context-support</artifactId>
  47 + </dependency>
  48 + <dependency>
  49 + <groupId>org.springframework</groupId>
  50 + <artifactId>spring-context</artifactId>
  51 + </dependency>
  52 + <dependency>
  53 + <groupId>org.slf4j</groupId>
  54 + <artifactId>slf4j-api</artifactId>
  55 + </dependency>
  56 + <dependency>
  57 + <groupId>org.slf4j</groupId>
  58 + <artifactId>log4j-over-slf4j</artifactId>
  59 + </dependency>
  60 + <dependency>
  61 + <groupId>ch.qos.logback</groupId>
  62 + <artifactId>logback-core</artifactId>
  63 + </dependency>
  64 + <dependency>
  65 + <groupId>ch.qos.logback</groupId>
  66 + <artifactId>logback-classic</artifactId>
  67 + </dependency>
  68 + <!-- lechan start -->
  69 + <dependency>
  70 + <groupId>org.eclipse.leshan</groupId>
  71 + <artifactId>leshan-server-cf</artifactId>
  72 + </dependency>
  73 +<!-- <dependency>-->
  74 +<!-- <groupId>org.eclipse.leshan</groupId>-->
  75 +<!-- <artifactId>leshan-server-cf</artifactId>-->
  76 +<!-- </dependency> -->
  77 + <dependency>
  78 + <groupId>org.eclipse.leshan</groupId>
  79 + <artifactId>leshan-client-cf</artifactId>
  80 + </dependency>
  81 +
  82 + <dependency>
  83 + <groupId>org.eclipse.leshan</groupId>
  84 + <artifactId>leshan-server-redis</artifactId>
  85 + </dependency>
  86 +<!-- <dependency>-->
  87 +<!-- <groupId>org.eclipse.californium</groupId>-->
  88 +<!-- <artifactId>californium-core</artifactId>-->
  89 +<!-- </dependency>-->
  90 + <!-- leshan finish -->
  91 +
  92 + <dependency>
  93 + <groupId>org.springframework.boot</groupId>
  94 + <artifactId>spring-boot-starter-test</artifactId>
  95 + <scope>test</scope>
  96 + </dependency>
  97 + <dependency>
  98 + <groupId>junit</groupId>
  99 + <artifactId>junit</artifactId>
  100 + <scope>test</scope>
  101 + </dependency>
  102 + <dependency>
  103 + <groupId>org.mockito</groupId>
  104 + <artifactId>mockito-all</artifactId>
  105 + <scope>test</scope>
  106 + </dependency>
  107 + <dependency>
  108 + <groupId>org.eclipse.californium</groupId>
  109 + <artifactId>californium-core</artifactId>
  110 + <version>${californium.version}</version>
  111 + <type>test-jar</type>
  112 + <scope>test</scope>
  113 + </dependency>
  114 + <dependency>
  115 + <groupId>org.eclipse.californium</groupId>
  116 + <artifactId>element-connector</artifactId>
  117 + <version>${californium.version}</version>
  118 + <type>test-jar</type>
  119 + <scope>test</scope>
  120 + </dependency>
  121 + </dependencies>
  122 +
  123 +</project>
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  20 +import org.eclipse.leshan.core.model.StaticModel;
  21 +import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
  22 +import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
  23 +import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  26 +import org.springframework.context.annotation.Bean;
  27 +import org.springframework.context.annotation.Primary;
  28 +import org.springframework.stereotype.Component;
  29 +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore;
  30 +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore;
  31 +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MSetSecurityStoreBootstrap;
  32 +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
  33 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  34 +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
  35 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
  36 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK;
  37 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig;
  38 +
  39 +@Slf4j
  40 +@Component
  41 +@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')")
  42 +public class LwM2MTransportBootstrapServerConfiguration {
  43 +
  44 + @Autowired
  45 + private LwM2MTransportContextBootstrap contextBs;
  46 +
  47 + @Autowired
  48 + private LwM2MTransportContextServer contextS;
  49 +
  50 + @Autowired
  51 + private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
  52 +
  53 + @Autowired
  54 + private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
  55 +
  56 +
  57 + @Primary
  58 + @Bean(name = "leshanBootstrapCert")
  59 + public LeshanBootstrapServer getLeshanBootstrapServerCert() {
  60 + log.info("Prepare and start BootstrapServerCert... PostConstruct");
  61 + return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortCert(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortCert(), X509);
  62 + }
  63 +
  64 + @Bean(name = "leshanBootstrapRPK")
  65 + public LeshanBootstrapServer getLeshanBootstrapServerRPK() {
  66 + log.info("Prepare and start BootstrapServerRPK... PostConstruct");
  67 + return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPort(), this.contextBs.getCtxBootStrap().getBootstrapSecurePort(), RPK);
  68 + }
  69 +
  70 + public LeshanBootstrapServer getLeshanBootstrapServer(Integer bootstrapPort, Integer bootstrapSecurePort, LwM2MSecurityMode dtlsMode) {
  71 + LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
  72 + builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPort);
  73 + builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapSecureHost(), bootstrapSecurePort);
  74 +
  75 + /** Create CoAP Config */
  76 + builder.setCoapConfig(getCoapConfig ());
  77 +
  78 + /** ConfigStore */
  79 + builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
  80 +
  81 + /** SecurityStore */
  82 + builder.setSecurityStore(lwM2MBootstrapSecurityStore);
  83 +
  84 + /** Define model provider (Create Models )*/
  85 + builder.setModel(new StaticModel(contextS.getCtxServer().getModelsValue()));
  86 +
  87 + /** Create and Set DTLS Config */
  88 + DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
  89 + dtlsConfig.setRecommendedCipherSuitesOnly(contextS.getCtxServer().isSupportDeprecatedCiphersEnable());
  90 + builder.setDtlsConfig(dtlsConfig);
  91 +
  92 + /** Create credentials */
  93 + new LwM2MSetSecurityStoreBootstrap(builder, contextBs, contextS, dtlsMode);
  94 +
  95 + BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore);
  96 + builder.setSessionManager(sessionManager);
  97 +
  98 + /** Create BootstrapServer */
  99 + return builder.build();
  100 + }
  101 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.beans.factory.annotation.Qualifier;
  21 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  22 +import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  24 +
  25 +import javax.annotation.PostConstruct;
  26 +import javax.annotation.PreDestroy;
  27 +
  28 +@Slf4j
  29 +@Service
  30 +@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')")
  31 +public class LwM2MTransportBootstrapServerInitializer {
  32 +
  33 + @Autowired
  34 + @Qualifier("leshanBootstrapCert")
  35 + private LeshanBootstrapServer lhBServerCert;
  36 +
  37 + @Autowired
  38 + @Qualifier("leshanBootstrapRPK")
  39 + private LeshanBootstrapServer lhBServerRPK;
  40 +
  41 + @Autowired
  42 + private LwM2MTransportContextBootstrap contextBS;
  43 +
  44 + @PostConstruct
  45 + public void init() {
  46 + if (this.contextBS.getCtxBootStrap().isBootstrapStartAll()) {
  47 + this.lhBServerCert.start();
  48 + this.lhBServerRPK.start();
  49 + }
  50 + else {
  51 + if (this.contextBS.getCtxBootStrap().getBootStrapDtlsMode() == LwM2MSecurityMode.X509.code) {
  52 + this.lhBServerCert.start();
  53 + }
  54 + else {
  55 + this.lhBServerRPK.start();
  56 + }
  57 + }
  58 + }
  59 +
  60 + @PreDestroy
  61 + public void shutdown() throws InterruptedException {
  62 + log.info("Stopping LwM2M transport Bootstrap Server!");
  63 + try {
  64 + lhBServerCert.destroy();
  65 + lhBServerRPK.destroy();
  66 + } finally {
  67 + }
  68 + log.info("LwM2M transport Bootstrap Server stopped!");
  69 + }
  70 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap;
  17 +/**
  18 + * Copyright © 2016-2020 The Thingsboard Authors
  19 + *
  20 + * Licensed under the Apache License, Version 2.0 (the "License");
  21 + * you may not use this file except in compliance with the License.
  22 + * You may obtain a copy of the License at
  23 + *
  24 + * http://www.apache.org/licenses/LICENSE-2.0
  25 + *
  26 + * Unless required by applicable law or agreed to in writing, software
  27 + * distributed under the License is distributed on an "AS IS" BASIS,
  28 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29 + * See the License for the specific language governing permissions and
  30 + * limitations under the License.
  31 + */
  32 +
  33 +import lombok.extern.slf4j.Slf4j;
  34 +import org.springframework.beans.factory.annotation.Autowired;
  35 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  36 +import org.springframework.stereotype.Component;
  37 +import org.thingsboard.server.common.transport.TransportContext;
  38 +import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
  39 +
  40 +import javax.annotation.PostConstruct;
  41 +
  42 +
  43 +@Slf4j
  44 +@Component
  45 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith'")
  46 +public class LwM2MTransportContextBootstrap extends TransportContext {
  47 +
  48 + private LwM2MTransportConfigBootstrap ctxBootStrap;
  49 + @Autowired
  50 + LwM2MTransportConfigBootstrap lwM2MTransportConfigBootstarp;
  51 +
  52 + @PostConstruct
  53 + public void init() {
  54 + this.ctxBootStrap = lwM2MTransportConfigBootstarp;
  55 + }
  56 +
  57 + public LwM2MTransportConfigBootstrap getCtxBootStrap() {
  58 + return this.ctxBootStrap;
  59 + }
  60 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import org.eclipse.leshan.core.SecurityMode;
  21 +import org.eclipse.leshan.core.request.BindingMode;
  22 +import org.eclipse.leshan.core.util.Hex;
  23 +import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
  24 +
  25 +import java.nio.charset.StandardCharsets;
  26 +
  27 +@Data
  28 +public class LwM2MBootstrapConfig {
  29 + /**
  30 + * interface BootstrapSecurityConfig
  31 + * servers: BootstrapServersSecurityConfig,
  32 + * bootstrapServer: ServerSecurityConfig,
  33 + * lwm2mServer: ServerSecurityConfig
  34 + * }
  35 + */
  36 + /** -servers
  37 + * shortId: number,
  38 + * lifetime: number,
  39 + * defaultMinPeriod: number,
  40 + * notifIfDisabled: boolean,
  41 + * binding: string
  42 + * */
  43 + @Builder.Default
  44 + LwM2MBootstrapServers servers;
  45 +
  46 + /** -bootstrapServer, lwm2mServer
  47 + * interface ServerSecurityConfig
  48 + * host?: string,
  49 + * port?: number,
  50 + * isBootstrapServer?: boolean,
  51 + * securityMode: string,
  52 + * clientPublicKeyOrId?: string,
  53 + * clientSecretKey?: string,
  54 + * serverPublicKey?: string;
  55 + * clientHoldOffTime?: number,
  56 + * serverId?: number,
  57 + * bootstrapServerAccountTimeout: number
  58 + * */
  59 + LwM2MServerBootstrap bootstrapServer;
  60 +
  61 + LwM2MServerBootstrap lwm2mServer;
  62 +
  63 + public BootstrapConfig getLwM2MBootstrapConfig() {
  64 + BootstrapConfig configBs = new BootstrapConfig();
  65 + /** Delete old security objects */
  66 + configBs.toDelete.add("/0");
  67 + configBs.toDelete.add("/1");
  68 + /** Server Configuration (object 1) as defined in LWM2M 1.0.x TS. */
  69 + BootstrapConfig.ServerConfig server0 = new BootstrapConfig.ServerConfig();
  70 + server0.shortId = servers.getShortId();
  71 + server0.lifetime = servers.getLifetime();
  72 + server0.defaultMinPeriod = servers.getDefaultMinPeriod();
  73 + server0.notifIfDisabled = servers.isNotifIfDisabled();
  74 + server0.binding = BindingMode.valueOf(servers.getBinding());
  75 + configBs.servers.put(0, server0);
  76 + /** Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */
  77 + this.bootstrapServer.setBootstrapServerIs(true);
  78 + configBs.security.put(0, setServerSecuruty(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId()));
  79 + /** Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */
  80 + configBs.security.put(1, setServerSecuruty(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId()));
  81 + return configBs;
  82 + }
  83 +
  84 + private BootstrapConfig.ServerSecurity setServerSecuruty(String host, Integer port, boolean bootstrapServer, String securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) {
  85 + BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity();
  86 + serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port);
  87 + serverSecurity.bootstrapServer = bootstrapServer;
  88 + serverSecurity.securityMode = SecurityMode.valueOf(securityMode);
  89 + serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode);
  90 + serverSecurity.serverPublicKey = (serverPublicKey != null && !serverPublicKey.isEmpty()) ? Hex.decodeHex(serverPublicKey.toCharArray()) : new byte[]{};
  91 + serverSecurity.secretKey = (secretKey != null && !secretKey.isEmpty()) ? Hex.decodeHex(secretKey.toCharArray()) : new byte[]{};
  92 + serverSecurity.serverId = serverId;
  93 + return serverSecurity;
  94 + }
  95 +
  96 + private byte[] setPublicKeyOrId(String publicKeyOrIdStr, String securityMode) {
  97 + byte[] publicKey = (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} :
  98 + SecurityMode.valueOf(securityMode).equals(SecurityMode.PSK) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) :
  99 + Hex.decodeHex(publicKeyOrIdStr.toCharArray());
  100 + return publicKey;
  101 + }
  102 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.gson.JsonObject;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.eclipse.leshan.core.SecurityMode;
  23 +import org.eclipse.leshan.core.util.Hex;
  24 +import org.eclipse.leshan.core.util.SecurityUtil;
  25 +import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
  26 +import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore;
  27 +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
  28 +import org.eclipse.leshan.server.security.BootstrapSecurityStore;
  29 +import org.eclipse.leshan.server.security.SecurityInfo;
  30 +import org.springframework.beans.factory.annotation.Autowired;
  31 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  32 +import org.springframework.stereotype.Component;
  33 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo;
  34 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  35 +import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
  36 +import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
  37 +
  38 +import java.io.IOException;
  39 +import java.security.GeneralSecurityException;
  40 +import java.util.Arrays;
  41 +import java.util.List;
  42 +
  43 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.*;
  44 +
  45 +@Slf4j
  46 +@Component("LwM2MBootstrapSecurityStore")
  47 +@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')")
  48 +public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
  49 +
  50 + private final EditableBootstrapConfigStore bootstrapConfigStore;
  51 +
  52 + @Autowired
  53 + LwM2MGetSecurityInfo lwM2MGetSecurityInfo;
  54 +
  55 + public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore) {
  56 + this.bootstrapConfigStore = bootstrapConfigStore;
  57 + }
  58 +
  59 + @Override
  60 + public List<SecurityInfo> getAllByEndpoint(String endPoint) {
  61 + String endPointKey = endPoint;
  62 + ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(endPointKey, TypeServer.BOOTSTRAP);
  63 + if (store.getBootstrapJsonCredential() != null) {
  64 + /** add value to store from BootstrapJson */
  65 + this.setBootstrapConfigScurityInfo(store);
  66 + BootstrapConfig bsConfigNew = store.getBootstrapConfig();
  67 + if (bsConfigNew != null) {
  68 + try {
  69 + for (String config : bootstrapConfigStore.getAll().keySet()) {
  70 + if (config.equals(endPoint)) {
  71 + bootstrapConfigStore.remove(config);
  72 + }
  73 + }
  74 + bootstrapConfigStore.add(endPoint, bsConfigNew);
  75 + } catch (InvalidConfigurationException e) {
  76 + e.printStackTrace();
  77 + }
  78 + return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo());
  79 + }
  80 + }
  81 + return null;
  82 + }
  83 +
  84 + @Override
  85 + public SecurityInfo getByIdentity(String identity) {
  86 + ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.BOOTSTRAP);
  87 + /** add value to store from BootstrapJson */
  88 + this.setBootstrapConfigScurityInfo(store);
  89 +
  90 + if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
  91 + BootstrapConfig bsConfig = store.getBootstrapConfig();
  92 + if (bsConfig.security != null) {
  93 + try {
  94 + bootstrapConfigStore.add(store.getEndPoint(), bsConfig);
  95 + } catch (InvalidConfigurationException e) {
  96 + e.printStackTrace();
  97 + }
  98 + return store.getSecurityInfo();
  99 + }
  100 + }
  101 + return null;
  102 + }
  103 +
  104 + private void setBootstrapConfigScurityInfo(ReadResultSecurityStore store) {
  105 + /** BootstrapConfig */
  106 + LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
  107 + if (lwM2MBootstrapConfig != null) {
  108 + /** Security info */
  109 + switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) {
  110 + /** Use RPK only */
  111 + case PSK:
  112 + store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(),
  113 + lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(),
  114 + Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray())));
  115 + store.setSecurityMode(SecurityMode.PSK.code);
  116 + break;
  117 + case RPK:
  118 + try {
  119 + store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndPoint(),
  120 + SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray()))));
  121 + store.setSecurityMode(SecurityMode.RPK.code);
  122 + break;
  123 + } catch (IOException | GeneralSecurityException e) {
  124 + log.error("Unable to decode Client public key for [{}] [{}]", store.getEndPoint(), e.getMessage());
  125 + }
  126 + case X509:
  127 + store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndPoint()));
  128 + store.setSecurityMode(SecurityMode.X509.code);
  129 + break;
  130 + case NO_SEC:
  131 + store.setSecurityMode(SecurityMode.NO_SEC.code);
  132 + store.setSecurityInfo(null);
  133 + break;
  134 + default:
  135 + }
  136 + BootstrapConfig bootstrapConfig = lwM2MBootstrapConfig.getLwM2MBootstrapConfig();
  137 + store.setBootstrapConfig(bootstrapConfig);
  138 + }
  139 + }
  140 +
  141 + private LwM2MBootstrapConfig getParametersBootstrap(ReadResultSecurityStore store) {
  142 + try {
  143 + JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential();
  144 + ObjectMapper mapper = new ObjectMapper();
  145 + LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class);
  146 + JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile());
  147 + lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class);
  148 + LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class);
  149 + LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class);
  150 + if (getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
  151 + lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
  152 + lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
  153 + return lwM2MBootstrapConfig;
  154 + }
  155 + else {
  156 + log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
  157 + log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
  158 + String logMsg = String.format(LOG_LW2M_ERROR + " getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint());
  159 +// sentLogsToThingsboard(logMsg, store.getEndPoint());
  160 + return null;
  161 + }
  162 + } catch (JsonProcessingException e) {
  163 + log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage());
  164 + return null;
  165 + }
  166 + }
  167 +
  168 + /**
  169 + * Bootstrap security have to sync between (bootstrapServer in credential and bootstrapServer in profile)
  170 + * and (lwm2mServer in credential and lwm2mServer in profile
  171 + * @param bootstrapFromCredential - Bootstrap -> Security of bootstrapServer in credential
  172 + * @param profileServerBootstrap - Bootstrap -> Security of bootstrapServer in profile
  173 + * @param lwm2mFromCredential - Bootstrap -> Security of lwm2mServer in credential
  174 + * @param profileLwm2mServer - Bootstrap -> Security of lwm2mServer in profile
  175 + * @return false if not sync between SecurityMode of Bootstrap credential and profile
  176 + */
  177 + private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) {
  178 + return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) &&
  179 + lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode()));
  180 + }
  181 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +
  21 +@Data
  22 +public class LwM2MBootstrapServers {
  23 + @Builder.Default
  24 + private Integer shortId = 123;
  25 + @Builder.Default
  26 + private Integer lifetime = 300;
  27 + @Builder.Default
  28 + private Integer defaultMinPeriod = 1;
  29 + @Builder.Default
  30 + private boolean notifIfDisabled = true;
  31 + @Builder.Default
  32 + private String binding = "U";
  33 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
  20 +import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
  21 +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
  22 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  23 +import org.springframework.stereotype.Component;
  24 +import java.util.Map;
  25 +import java.util.concurrent.locks.Lock;
  26 +import java.util.concurrent.locks.ReadWriteLock;
  27 +import java.util.concurrent.locks.ReentrantReadWriteLock;
  28 +
  29 +@Slf4j
  30 +@Component("LwM2MInMemoryBootstrapConfigStore")
  31 +@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')")
  32 +public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigStore {
  33 + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  34 + private final Lock readLock = readWriteLock.readLock();
  35 + private final Lock writeLock = readWriteLock.writeLock();
  36 +
  37 + @Override
  38 + public Map<String, BootstrapConfig> getAll() {
  39 + readLock.lock();
  40 + try {
  41 + return super.getAll();
  42 + } finally {
  43 + readLock.unlock();
  44 + }
  45 + }
  46 +
  47 + @Override
  48 + public void add(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
  49 + writeLock.lock();
  50 + try {
  51 + addToStore(endpoint, config);
  52 + } finally {
  53 + writeLock.unlock();
  54 + }
  55 + }
  56 +
  57 + @Override
  58 + public BootstrapConfig remove(String enpoint) {
  59 + writeLock.lock();
  60 + try {
  61 + BootstrapConfig res = super.remove(enpoint);
  62 + return res;
  63 + } finally {
  64 + writeLock.unlock();
  65 + }
  66 + }
  67 +
  68 + public void addToStore(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
  69 + super.add(endpoint, config);
  70 + }
  71 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.leshan.core.SecurityMode;
  22 +
  23 +@Slf4j
  24 +@Data
  25 +public class LwM2MServerBootstrap {
  26 +
  27 + @Builder.Default
  28 + String clientPublicKeyOrId = "";
  29 + @Builder.Default
  30 + String clientSecretKey = "";
  31 + @Builder.Default
  32 + String serverPublicKey = "";
  33 + @Builder.Default
  34 + Integer clientHoldOffTime = 1;
  35 + @Builder.Default
  36 + Integer bootstrapServerAccountTimeout = 0;
  37 +
  38 + @Builder.Default
  39 + String host = "0.0.0.0";
  40 + @Builder.Default
  41 + Integer port = 0;
  42 +
  43 + @Builder.Default
  44 + String securityMode = SecurityMode.NO_SEC.name();
  45 +
  46 + @Builder.Default
  47 + Integer serverId = 123;
  48 + @Builder.Default
  49 + boolean bootstrapServerIs = false;
  50 +
  51 + public LwM2MServerBootstrap(){};
  52 +
  53 + public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) {
  54 + this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId();
  55 + this.clientSecretKey = bootstrapFromCredential.getClientSecretKey();
  56 + this.serverPublicKey = profileServerBootstrap.getServerPublicKey();
  57 + this.clientHoldOffTime = profileServerBootstrap.getClientHoldOffTime();
  58 + this.bootstrapServerAccountTimeout = profileServerBootstrap.getBootstrapServerAccountTimeout();
  59 + this.host = (profileServerBootstrap.getHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getHost();
  60 + this.port = profileServerBootstrap.getPort();
  61 + this.securityMode = profileServerBootstrap.getSecurityMode();
  62 + this.serverId = profileServerBootstrap.getServerId();
  63 + this.bootstrapServerIs = profileServerBootstrap.bootstrapServerIs;
  64 + }
  65 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.util.Hex;
  21 +import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
  22 +import org.eclipse.leshan.server.security.EditableSecurityStore;
  23 +import org.thingsboard.server.transport.lwm2m.bootstrap.LwM2MTransportContextBootstrap;
  24 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  25 +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
  26 +
  27 +import java.math.BigInteger;
  28 +import java.security.AlgorithmParameters;
  29 +import java.security.KeyStore;
  30 +import java.security.PublicKey;
  31 +import java.security.PrivateKey;
  32 +import java.security.KeyFactory;
  33 +import java.security.GeneralSecurityException;
  34 +import java.security.KeyStoreException;
  35 +import java.security.cert.X509Certificate;
  36 +import java.security.interfaces.ECPublicKey;
  37 +import java.security.spec.ECGenParameterSpec;
  38 +import java.security.spec.ECParameterSpec;
  39 +import java.security.spec.ECPublicKeySpec;
  40 +import java.security.spec.ECPoint;
  41 +import java.security.spec.KeySpec;
  42 +import java.security.spec.ECPrivateKeySpec;
  43 +import java.util.Arrays;
  44 +
  45 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
  46 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
  47 +
  48 +@Slf4j
  49 +@Data
  50 +public class LwM2MSetSecurityStoreBootstrap {
  51 +
  52 + private KeyStore keyStore;
  53 + private PublicKey publicKey;
  54 + private PrivateKey privateKey;
  55 + private LwM2MTransportContextBootstrap contextBs;
  56 + private LwM2MTransportContextServer contextS;
  57 + private LeshanBootstrapServerBuilder builder;
  58 + EditableSecurityStore securityStore;
  59 +
  60 + public LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MTransportContextBootstrap contextBs, LwM2MTransportContextServer contextS, LwM2MSecurityMode dtlsMode) {
  61 + this.builder = builder;
  62 + this.contextBs = contextBs;
  63 + this.contextS = contextS;
  64 + /** Set securityStore with new registrationStore */
  65 +
  66 + switch (dtlsMode) {
  67 + /** Use No_Sec only */
  68 + case NO_SEC:
  69 + setServerWithX509Cert(NO_SEC.code);
  70 + break;
  71 + /** Use PSK/RPK */
  72 + case PSK:
  73 + case RPK:
  74 + setRPK();
  75 + break;
  76 + case X509:
  77 + setServerWithX509Cert(X509.code);
  78 + break;
  79 + /** Use X509_EST only */
  80 + case X509_EST:
  81 + // TODO support sentinel pool and make pool configurable
  82 + break;
  83 + /** Use ather X509, PSK, No_Sec ?? */
  84 + default:
  85 + break;
  86 + }
  87 + }
  88 +
  89 + private void setRPK() {
  90 + try {
  91 + /** Get Elliptic Curve Parameter spec for secp256r1 */
  92 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  93 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  94 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  95 + if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) {
  96 + /** Get point values */
  97 + byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray());
  98 + byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray());
  99 + /** Create key specs */
  100 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  101 + parameterSpec);
  102 + /** Get keys */
  103 + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  104 + }
  105 + if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) {
  106 + /** Get point values */
  107 + byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray());
  108 + /** Create key specs */
  109 + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
  110 + /** Get keys */
  111 + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
  112 + }
  113 + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
  114 + this.privateKey != null && this.privateKey.getEncoded().length > 0) {
  115 + this.builder.setPublicKey(this.publicKey);
  116 + this.builder.setPrivateKey(this.privateKey);
  117 + this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey);
  118 + getParamsRPK();
  119 + }
  120 + } catch (GeneralSecurityException | IllegalArgumentException e) {
  121 + log.error("[{}] Failed generate Server PSK/RPK", e.getMessage());
  122 + throw new RuntimeException(e);
  123 + }
  124 + }
  125 +
  126 + private void setServerWithX509Cert(int securityModeCode) {
  127 + try {
  128 + if (this.contextS.getCtxServer().getKeyStoreValue() != null) {
  129 + KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue();
  130 + setBuilderX509();
  131 + X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias());
  132 + if (rootCAX509Cert != null && securityModeCode == X509.code) {
  133 + X509Certificate[] trustedCertificates = new X509Certificate[1];
  134 + trustedCertificates[0] = rootCAX509Cert;
  135 + this.builder.setTrustedCertificates(trustedCertificates);
  136 + } else {
  137 + /** by default trust all */
  138 + this.builder.setTrustedCertificates(new X509Certificate[0]);
  139 + }
  140 + }
  141 + else {
  142 + /** by default trust all */
  143 + this.builder.setTrustedCertificates(new X509Certificate[0]);
  144 + log.error("Unable to load X509 files for BootStrapServer");
  145 + }
  146 + } catch (KeyStoreException ex) {
  147 + log.error("[{}] Unable to load X509 files server", ex.getMessage());
  148 + }
  149 +
  150 + }
  151 +
  152 + private void setBuilderX509() {
  153 + /**
  154 + * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
  155 + * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
  156 + */
  157 + try {
  158 + X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias());
  159 + this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray());
  160 + if (this.privateKey != null && this.privateKey.getEncoded().length > 0) {
  161 + this.builder.setPrivateKey(this.privateKey);
  162 + }
  163 + if (serverCertificate != null) {
  164 + this.builder.setCertificateChain(new X509Certificate[]{serverCertificate});
  165 + this.contextBs.getCtxBootStrap().setBootstrapCertificate(serverCertificate);
  166 + }
  167 + } catch (Exception ex) {
  168 + log.error("[{}] Unable to load KeyStore files server", ex.getMessage());
  169 + }
  170 + }
  171 +
  172 + private void getParamsRPK() {
  173 + if (this.publicKey instanceof ECPublicKey) {
  174 + /** Get x coordinate */
  175 + byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray();
  176 + if (x[0] == 0)
  177 + x = Arrays.copyOfRange(x, 1, x.length);
  178 +
  179 + /** Get Y coordinate */
  180 + byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray();
  181 + if (y[0] == 0)
  182 + y = Arrays.copyOfRange(y, 1, y.length);
  183 +
  184 + /** Get Curves params */
  185 + String params = ((ECPublicKey) this.publicKey).getParams().toString();
  186 + log.info(
  187 + " \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]",
  188 + params, Hex.encodeHexString(x), Hex.encodeHexString(y),
  189 + Hex.encodeHexString(this.publicKey.getEncoded()),
  190 + Hex.encodeHexString(this.privateKey.getEncoded()));
  191 + } else {
  192 + throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported).");
  193 + }
  194 + }
  195 +
  196 +// private void getParamsX509() {
  197 +// try {
  198 +// log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]",
  199 +// Hex.encodeHexString(this.certificate.getEncoded()),
  200 +// Hex.encodeHexString(this.privateKey.getEncoded()));
  201 +// } catch (CertificateEncodingException e) {
  202 +// e.printStackTrace();
  203 +// }
  204 +// }
  205 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.core.request.Identity;
  20 +import org.eclipse.leshan.server.bootstrap.BootstrapSession;
  21 +import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
  22 +import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
  23 +import org.eclipse.leshan.server.security.BootstrapSecurityStore;
  24 +import org.eclipse.leshan.server.security.SecurityChecker;
  25 +import org.eclipse.leshan.server.security.SecurityInfo;
  26 +
  27 +import java.util.Arrays;
  28 +import java.util.List;
  29 +
  30 +@Slf4j
  31 +public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
  32 +
  33 + private BootstrapSecurityStore bsSecurityStore;
  34 + private SecurityChecker securityChecker;
  35 +
  36 + /**
  37 + * Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new
  38 + * {@link BootstrapSession}.
  39 + *
  40 + * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}.
  41 + */
  42 + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) {
  43 + this(bsSecurityStore, new SecurityChecker());
  44 + }
  45 +
  46 + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker) {
  47 + super(bsSecurityStore);
  48 + this.bsSecurityStore = bsSecurityStore;
  49 + this.securityChecker = securityChecker;
  50 + }
  51 +
  52 + @Override
  53 + public BootstrapSession begin(String endpoint, Identity clientIdentity) {
  54 + boolean authorized;
  55 + if (bsSecurityStore != null) {
  56 + List<SecurityInfo> securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? Arrays.asList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())) : bsSecurityStore.getAllByEndpoint(endpoint);
  57 + log.info("Bootstrap session started securityInfos: [{}]", securityInfos);
  58 + authorized = securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos);
  59 + } else {
  60 + authorized = true;
  61 + }
  62 + DefaultBootstrapSession session = new DefaultBootstrapSession(endpoint, clientIdentity, authorized);
  63 + log.info("Bootstrap session started : {}", session);
  64 + return session;
  65 + }
  66 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.core.util.Hex;
  20 +import java.security.SecureRandom;
  21 +import java.security.KeyPairGenerator;
  22 +import java.security.KeyPair;
  23 +import java.security.PrivateKey;
  24 +import java.security.PublicKey;
  25 +import java.security.NoSuchAlgorithmException;
  26 +import java.security.NoSuchProviderException;
  27 +import java.security.InvalidAlgorithmParameterException;
  28 +import java.security.interfaces.ECPublicKey;
  29 +import java.security.spec.ECGenParameterSpec;
  30 +import java.util.Arrays;
  31 +
  32 +@Slf4j
  33 +public class LWM2MGenerationPSkRPkECC {
  34 +
  35 + public LWM2MGenerationPSkRPkECC(Integer dtlsMode) {
  36 + switch (LwM2MSecurityMode.fromSecurityMode(dtlsMode)) {
  37 + case PSK:
  38 + generationPSkKey();
  39 + break;
  40 + case RPK:
  41 + generationRPKECCKey();
  42 + }
  43 + }
  44 +
  45 + public LWM2MGenerationPSkRPkECC() {
  46 + generationPSkKey();
  47 + generationRPKECCKey();
  48 + }
  49 +
  50 + private void generationPSkKey() {
  51 + /** PSK */
  52 + int lenPSkKey = 32;
  53 + /** Start PSK
  54 + * Clients and Servers MUST support PSK keys of up to 64 bytes in length, as required by [RFC7925]
  55 + * SecureRandom object must be unpredictable, and all SecureRandom output sequences must be cryptographically strong, as described in [RFC4086]
  56 + * */
  57 + SecureRandom randomPSK = new SecureRandom();
  58 + byte bytesPSK[] = new byte[lenPSkKey];
  59 + randomPSK.nextBytes(bytesPSK);
  60 + log.info("\nCreating new PSK: \n for the next start PSK -> security key: [{}]", Hex.encodeHexString(bytesPSK));
  61 + }
  62 +
  63 + private void generationRPKECCKey() {
  64 + /** RPK */
  65 + String algorithm = "EC";
  66 + String provider = "SunEC";
  67 + String nameParameterSpec = "secp256r1";
  68 +
  69 + /** Start RPK
  70 + * Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
  71 + * */
  72 + KeyPairGenerator kpg = null;
  73 + try {
  74 + kpg = KeyPairGenerator.getInstance(algorithm, provider);
  75 + } catch (NoSuchAlgorithmException e) {
  76 + e.printStackTrace();
  77 + } catch (NoSuchProviderException e) {
  78 + e.printStackTrace();
  79 + }
  80 + ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec);
  81 + try {
  82 + kpg.initialize(ecsp);
  83 + } catch (InvalidAlgorithmParameterException e) {
  84 + e.printStackTrace();
  85 + }
  86 +
  87 + KeyPair kp = kpg.genKeyPair();
  88 + PrivateKey privKey = kp.getPrivate();
  89 + PublicKey pubKey = kp.getPublic();
  90 +
  91 + if (pubKey instanceof ECPublicKey) {
  92 + ECPublicKey ecPublicKey = (ECPublicKey) pubKey;
  93 + /** Get x coordinate */
  94 + byte[] x = ecPublicKey.getW().getAffineX().toByteArray();
  95 + if (x[0] == 0)
  96 + x = Arrays.copyOfRange(x, 1, x.length);
  97 +
  98 + /** Get Y coordinate */
  99 + byte[] y = ecPublicKey.getW().getAffineY().toByteArray();
  100 + if (y[0] == 0)
  101 + y = Arrays.copyOfRange(y, 1, y.length);
  102 +
  103 + /** Get Curves params */
  104 + String privHex = Hex.encodeHexString(privKey.getEncoded());
  105 + log.info("\nCreating new RPK for the next start... \n" +
  106 + " Elliptic Curve parameters : [{}] \n" +
  107 + " public_x : [{}] \n" +
  108 + " public_y : [{}] \n" +
  109 + " private_s : [{}] \n" +
  110 + " Public Key (Hex): [{}]\n" +
  111 + " Private Key (Hex): [{}]",
  112 + ecPublicKey.getParams().toString(),
  113 + Hex.encodeHexString(x),
  114 + Hex.encodeHexString(y),
  115 + privHex.substring(privHex.length() - 64),
  116 + Hex.encodeHexString(pubKey.getEncoded()),
  117 + Hex.encodeHexString(privKey.getEncoded()));
  118 + }
  119 + }
  120 +}
  121 +
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import com.google.gson.JsonObject;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.util.Hex;
  21 +import org.eclipse.leshan.core.util.SecurityUtil;
  22 +import org.eclipse.leshan.server.security.SecurityInfo;
  23 +import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  25 +import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.common.data.DeviceProfile;
  27 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
  29 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
  30 +import org.thingsboard.server.transport.lwm2m.bootstrap.LwM2MTransportContextBootstrap;
  31 +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer;
  32 +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler;
  33 +import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
  34 +
  35 +import java.io.IOException;
  36 +import java.security.GeneralSecurityException;
  37 +import java.security.PublicKey;
  38 +import java.util.Optional;
  39 +import java.util.concurrent.CountDownLatch;
  40 +import java.util.concurrent.TimeUnit;
  41 +
  42 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*;
  43 +
  44 +@Slf4j
  45 +@Component("LwM2MGetSecurityInfo")
  46 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  47 +public class LwM2MGetSecurityInfo {
  48 +
  49 + @Autowired
  50 + public LwM2MTransportContextServer contextS;
  51 +
  52 + @Autowired
  53 + public LwM2MTransportContextBootstrap contextBS;
  54 +
  55 +
  56 + public ReadResultSecurityStore getSecurityInfo(String endPoint, TypeServer keyValue) {
  57 + CountDownLatch latch = new CountDownLatch(1);
  58 + final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1];
  59 + contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endPoint).build(),
  60 + new TransportServiceCallback<ValidateDeviceCredentialsResponseMsg>() {
  61 + @Override
  62 + public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
  63 + String credentialsBody = msg.getCredentialsBody();
  64 + resultSecurityStore[0] = putSecurityInfo(endPoint, msg.getDeviceInfo().getDeviceName(), credentialsBody, keyValue);
  65 + resultSecurityStore[0].setMsg(msg);
  66 + Optional<DeviceProfile> deviceProfileOpt = LwM2MTransportHandler.decode(msg.getProfileBody().toByteArray());
  67 + deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile));
  68 + latch.countDown();
  69 + }
  70 +
  71 + @Override
  72 + public void onError(Throwable e) {
  73 + log.trace("[{}] Failed to process credentials PSK: {}", endPoint, e);
  74 + resultSecurityStore[0] = putSecurityInfo(endPoint, null, null, null);
  75 + latch.countDown();
  76 + }
  77 + });
  78 + try {
  79 + latch.await(contextS.getCtxServer().getTimeout(), TimeUnit.MILLISECONDS);
  80 + } catch (InterruptedException e) {
  81 + e.printStackTrace();
  82 + }
  83 + return resultSecurityStore[0];
  84 + }
  85 +
  86 + private ReadResultSecurityStore putSecurityInfo(String endPoint, String deviceName, String jsonStr, TypeServer keyValue) {
  87 + ReadResultSecurityStore result = new ReadResultSecurityStore();
  88 + JsonObject objectMsg = LwM2MTransportHandler.validateJson(jsonStr);
  89 + if (objectMsg != null && !objectMsg.isJsonNull()) {
  90 + JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null;
  91 + /**
  92 + * Only PSK
  93 + */
  94 + String endPointPsk = (objectMsg.has("client")
  95 + && objectMsg.get("client").getAsJsonObject().has("endpoint")
  96 + && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null;
  97 + endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk;
  98 + if (object != null && !object.isJsonNull()) {
  99 + if (keyValue.equals(TypeServer.BOOTSTRAP)) {
  100 + result.setBootstrapJsonCredential(object);
  101 + result.setEndPoint(endPoint);
  102 + } else {
  103 + LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase());
  104 + switch (lwM2MSecurityMode) {
  105 + case NO_SEC:
  106 + getClientSecurityInfoNoSec(result);
  107 + break;
  108 + case PSK:
  109 + getClientSecurityInfoPSK(result, endPoint, object);
  110 + break;
  111 + case RPK:
  112 + getClientSecurityInfoRPK(result, endPoint, object);
  113 + break;
  114 + case X509:
  115 + getClientSecurityInfoX509(result, endPoint);
  116 + break;
  117 + default:
  118 + break;
  119 + }
  120 + }
  121 + }
  122 + }
  123 + return result;
  124 + }
  125 +
  126 + private void getClientSecurityInfoNoSec(ReadResultSecurityStore result) {
  127 + result.setSecurityInfo(null);
  128 + result.setSecurityMode(NO_SEC.code);
  129 + }
  130 +
  131 + private void getClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) {
  132 + /** PSK Deserialization */
  133 + String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null;
  134 + if (identity != null && !identity.isEmpty()) {
  135 + try {
  136 + byte[] key = (object.has("key") && object.get("key").isJsonPrimitive()) ? Hex.decodeHex(object.get("key").getAsString().toCharArray()) : null;
  137 + if (key != null && key.length > 0) {
  138 + if (endPoint != null && !endPoint.isEmpty()) {
  139 + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endPoint, identity, key));
  140 + result.setSecurityMode(PSK.code);
  141 + }
  142 + }
  143 + } catch (IllegalArgumentException e) {
  144 + log.error("Missing PSK key: " + e.getMessage());
  145 + }
  146 + } else {
  147 + log.error("Missing PSK identity");
  148 + }
  149 + }
  150 +
  151 + private void getClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) {
  152 + try {
  153 + if (object.has("key") && object.get("key").isJsonPrimitive()) {
  154 + byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray());
  155 + PublicKey key = SecurityUtil.publicKey.decode(rpkkey);
  156 + result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key));
  157 + result.setSecurityMode(RPK.code);
  158 + } else {
  159 + log.error("Missing RPK key");
  160 + }
  161 + } catch (IllegalArgumentException | IOException | GeneralSecurityException e) {
  162 + log.error("RPK: Invalid security info content: " + e.getMessage());
  163 + }
  164 + }
  165 +
  166 + private void getClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) {
  167 + result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint));
  168 + result.setSecurityMode(X509.code);
  169 + }
  170 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +public enum LwM2MSecurityMode {
  19 +
  20 + PSK(0, "psk"),
  21 + RPK(1, "rpk"),
  22 + X509(2, "x509"),
  23 + NO_SEC(3, "no_sec"),
  24 + X509_EST(4, "x509_est"),
  25 + REDIS(7, "redis"),
  26 + DEFAULT_MODE(255, "default_mode");
  27 +
  28 + public int code;
  29 + public String subEndpoint;
  30 +
  31 + LwM2MSecurityMode(int code, String subEndpoint) {
  32 + this.code = code;
  33 + this.subEndpoint = subEndpoint;
  34 + }
  35 +
  36 + public static LwM2MSecurityMode fromSecurityMode(long code) {
  37 + return fromSecurityMode((int) code);
  38 + }
  39 +
  40 + public static LwM2MSecurityMode fromSecurityMode(int code) {
  41 + for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) {
  42 + if (sm.code == code) {
  43 + return sm;
  44 + }
  45 + }
  46 + throw new IllegalArgumentException(String.format("Unsupported security code : %d", code));
  47 + }
  48 +
  49 +
  50 + public static LwM2MSecurityMode fromSecurityMode(String subEndpoint) {
  51 + for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) {
  52 + if (sm.subEndpoint.equals(subEndpoint)) {
  53 + return sm;
  54 + }
  55 + }
  56 + throw new IllegalArgumentException(String.format("Unsupported security subEndpoint : %d", subEndpoint));
  57 + }
  58 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.util.Hex;
  21 +import java.math.BigInteger;
  22 +import java.security.PrivateKey;
  23 +import java.security.PublicKey;
  24 +import java.security.AlgorithmParameters;
  25 +import java.security.KeyFactory;
  26 +import java.security.GeneralSecurityException;
  27 +import java.security.cert.Certificate;
  28 +import java.security.cert.X509Certificate;
  29 +import java.security.spec.ECGenParameterSpec;
  30 +import java.security.spec.ECParameterSpec;
  31 +import java.security.spec.ECPublicKeySpec;
  32 +import java.security.spec.KeySpec;
  33 +import java.security.spec.ECPrivateKeySpec;
  34 +import java.security.spec.ECPoint;
  35 +import java.util.List;
  36 +
  37 +@Slf4j
  38 +@Data
  39 +public class LwM2mRPkCredentials {
  40 + private PublicKey serverPublicKey;
  41 + private PrivateKey serverPrivateKey;
  42 + private X509Certificate certificate;
  43 + private List<Certificate> trustStore;
  44 +
  45 + /**
  46 + * create All key RPK credentials
  47 + * @param publX
  48 + * @param publY
  49 + * @param privS
  50 + */
  51 + public LwM2mRPkCredentials(String publX, String publY, String privS) {
  52 + generatePublicKeyRPK(publX, publY, privS);
  53 + }
  54 +
  55 + private void generatePublicKeyRPK(String publX, String publY, String privS) {
  56 + try {
  57 + /**Get Elliptic Curve Parameter spec for secp256r1 */
  58 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  59 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  60 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  61 + if (publX != null && !publX.isEmpty() && publY != null && !publY.isEmpty()) {
  62 + // Get point values
  63 + byte[] publicX = Hex.decodeHex(publX.toCharArray());
  64 + byte[] publicY = Hex.decodeHex(publY.toCharArray());
  65 + /** Create key specs */
  66 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  67 + parameterSpec);
  68 + /** Get keys */
  69 + this.serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  70 + }
  71 + if (privS != null && !privS.isEmpty()) {
  72 + /** Get point values */
  73 + byte[] privateS = Hex.decodeHex(privS.toCharArray());
  74 + /** Create key specs */
  75 + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
  76 + /** Get keys */
  77 + this.serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
  78 + }
  79 + } catch (GeneralSecurityException | IllegalArgumentException e) {
  80 + log.error("[{}] Failed generate Server KeyRPK", e.getMessage());
  81 + throw new RuntimeException(e);
  82 + }
  83 + }
  84 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import com.google.gson.JsonObject;
  19 +import lombok.Builder;
  20 +import lombok.Data;
  21 +import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
  22 +import org.eclipse.leshan.server.security.SecurityInfo;
  23 +import org.thingsboard.server.common.data.DeviceProfile;
  24 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
  25 +
  26 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE;
  27 +
  28 +@Data
  29 +public class ReadResultSecurityStore {
  30 + private ValidateDeviceCredentialsResponseMsg msg;
  31 + private SecurityInfo securityInfo;
  32 + @Builder.Default
  33 + private int securityMode = DEFAULT_MODE.code;
  34 +
  35 + /** bootstrap */
  36 + DeviceProfile deviceProfile;
  37 + JsonObject bootstrapJsonCredential;
  38 + String endPoint;
  39 + BootstrapConfig bootstrapConfig;
  40 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import io.netty.util.concurrent.Future;
  19 +import io.netty.util.concurrent.GenericFutureListener;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.common.data.Device;
  22 +import org.thingsboard.server.common.data.DeviceProfile;
  23 +import org.thingsboard.server.common.transport.SessionMsgListener;
  24 +import org.thingsboard.server.gen.transport.TransportProtos;
  25 +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
  27 +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
  29 +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
  30 +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
  31 +
  32 +import java.util.Optional;
  33 +
  34 +@Slf4j
  35 +public class LwM2MSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
  36 + private LwM2MTransportService service;
  37 + private TransportProtos.SessionInfoProto sessionInfo;
  38 +
  39 + LwM2MSessionMsgListener(LwM2MTransportService service, TransportProtos.SessionInfoProto sessionInfo) {
  40 + this.service = service;
  41 + this.sessionInfo = sessionInfo;
  42 + }
  43 +
  44 + @Override
  45 + public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) {
  46 + log.info("[{}] attributesResponse", getAttributesResponse);
  47 + }
  48 +
  49 + @Override
  50 + public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) {
  51 + this.service.onAttributeUpdate(attributeUpdateNotification, this.sessionInfo);
  52 + }
  53 +
  54 + @Override
  55 + public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) {
  56 + log.info("[{}] sessionCloseNotification", sessionCloseNotification);
  57 + }
  58 +
  59 + @Override
  60 + public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) {
  61 + this.service.onToTransportUpdateCredentials(updateCredentials);
  62 + }
  63 +
  64 + @Override
  65 + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  66 + this.service.onDeviceProfileUpdate(sessionInfo, deviceProfile);
  67 + }
  68 +
  69 + @Override
  70 + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
  71 + this.service.onDeviceUpdate(sessionInfo, device, deviceProfileOpt);
  72 + }
  73 +
  74 + @Override
  75 + public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
  76 + log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
  77 + }
  78 +
  79 + @Override
  80 + public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) {
  81 + log.info("[{}] toServerRpcResponse", toServerResponse);
  82 + }
  83 +
  84 + @Override
  85 + public void operationComplete(Future<? super Void> future) throws Exception {
  86 + log.info("[{}] operationComplete", future);
  87 + }
  88 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +/**
  18 + * Copyright © 2016-2020 The Thingsboard Authors
  19 + *
  20 + * Licensed under the Apache License, Version 2.0 (the "License");
  21 + * you may not use this file except in compliance with the License.
  22 + * You may obtain a copy of the License at
  23 + *
  24 + * http://www.apache.org/licenses/LICENSE-2.0
  25 + *
  26 + * Unless required by applicable law or agreed to in writing, software
  27 + * distributed under the License is distributed on an "AS IS" BASIS,
  28 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29 + * See the License for the specific language governing permissions and
  30 + * limitations under the License.
  31 + */
  32 +
  33 +import lombok.extern.slf4j.Slf4j;
  34 +import org.springframework.beans.factory.annotation.Autowired;
  35 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  36 +import org.springframework.stereotype.Component;
  37 +import org.thingsboard.server.common.transport.TransportContext;
  38 +import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
  39 +
  40 +import javax.annotation.PostConstruct;
  41 +
  42 +@Slf4j
  43 +@Component
  44 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  45 +public class LwM2MTransportContextServer extends TransportContext {
  46 +
  47 + private LwM2MTransportConfigServer ctxServer;
  48 +
  49 + @Autowired
  50 + LwM2MTransportConfigServer lwM2MTransportConfigServer;
  51 +
  52 + @PostConstruct
  53 + public void init() {
  54 + this.ctxServer = lwM2MTransportConfigServer;
  55 + }
  56 +
  57 + public LwM2MTransportConfigServer getCtxServer () {
  58 + return this.ctxServer;
  59 + }
  60 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import com.google.gson.Gson;
  20 +import com.google.gson.JsonObject;
  21 +import com.google.gson.JsonParser;
  22 +import com.google.gson.JsonSyntaxException;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.apache.commons.lang3.StringUtils;
  25 +import org.eclipse.californium.core.network.config.NetworkConfig;
  26 +import org.eclipse.leshan.core.model.ResourceModel;
  27 +import org.eclipse.leshan.core.node.LwM2mNode;
  28 +import org.eclipse.leshan.core.node.LwM2mObject;
  29 +import org.eclipse.leshan.core.node.LwM2mObjectInstance;
  30 +import org.eclipse.leshan.core.node.LwM2mSingleResource;
  31 +import org.eclipse.leshan.core.node.LwM2mMultipleResource;
  32 +import org.eclipse.leshan.core.util.Hex;
  33 +import org.eclipse.leshan.server.californium.LeshanServer;
  34 +import org.eclipse.leshan.server.californium.LeshanServerBuilder;
  35 +import org.nustaq.serialization.FSTConfiguration;
  36 +import org.springframework.beans.factory.annotation.Autowired;
  37 +import org.springframework.beans.factory.annotation.Qualifier;
  38 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  39 +import org.springframework.stereotype.Component;
  40 +import org.thingsboard.server.common.data.DeviceProfile;
  41 +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
  42 +import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue;
  43 +
  44 +import javax.annotation.PostConstruct;
  45 +import java.io.File;
  46 +import java.io.IOException;
  47 +import java.text.DateFormat;
  48 +import java.text.SimpleDateFormat;
  49 +import java.util.Date;
  50 +import java.util.Optional;
  51 +import java.util.Map;
  52 +import java.util.Arrays;
  53 +import java.util.List;
  54 +import java.util.ArrayList;
  55 +import java.util.LinkedList;
  56 +import java.util.Collections;
  57 +
  58 +@Slf4j
  59 +@Component("LwM2MTransportHandler")
  60 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  61 +public class LwM2MTransportHandler{
  62 +
  63 + // We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to
  64 + // send a Confirmable message to the time when an acknowledgement is no longer expected.
  65 + public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000l; // 2min in ms
  66 + public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
  67 + public static final String KEYNAME = "keyName";
  68 + public static final String ATTRIBUTE = "attribute";
  69 + public static final String TELEMETRY = "telemetry";
  70 + public static final String OBSERVE = "observe";
  71 + public static final String BOOTSTRAP = "bootstrap";
  72 + public static final String SERVERS = "servers";
  73 + public static final String LWM2M_SERVER = "lwm2mServer";
  74 + public static final String BOOTSTRAP_SERVER = "bootstrapServer";
  75 + public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
  76 + public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes";
  77 + public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry";
  78 + public static final String LOG_LW2M_TELEMETRY = "logLwm2m";
  79 + public static final String LOG_LW2M_INFO = "info";
  80 + public static final String LOG_LW2M_ERROR = "error";
  81 + public static final String LOG_LW2M_WARN = "warn";
  82 +
  83 +
  84 + public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
  85 +
  86 + public static final String GET_TYPE_OPER_READ = "read";
  87 + public static final String GET_TYPE_OPER_DISCOVER = "discover";
  88 + public static final String GET_TYPE_OPER_OBSERVE = "observe";
  89 + public static final String POST_TYPE_OPER_OBSERVE_CANCEL = "observeCancel";
  90 + public static final String POST_TYPE_OPER_EXECUTE = "execute";
  91 + /**
  92 + * Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see
  93 + * section 5.3.3 of the LW M2M spec).
  94 + */
  95 + public static final String POST_TYPE_OPER_WRITE_REPLACE = "replace";
  96 + /**
  97 + * Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section
  98 + * 5.3.3 of the LW M2M spec).
  99 + */
  100 + public static final String PUT_TYPE_OPER_WRITE_UPDATE = "update";
  101 + public static final String PUT_TYPE_OPER_WRITE_ATTRIBUTES = "wright-attributes";
  102 +
  103 + public static final String EVENT_AWAKE = "AWAKE";
  104 +
  105 + private static Gson gson = null;
  106 +
  107 + @Autowired
  108 + @Qualifier("LeshanServerCert")
  109 + private LeshanServer lhServerCert;
  110 +
  111 + @Autowired
  112 + @Qualifier("leshanServerNoSecPskRpk")
  113 + private LeshanServer lhServerNoSecPskRpk;
  114 +
  115 + @Autowired
  116 + private LwM2MTransportService service;
  117 +
  118 +
  119 + @PostConstruct
  120 + public void init() {
  121 + LwM2mServerListener lwM2mServerListener = new LwM2mServerListener(lhServerCert, service);
  122 + this.lhServerCert.getRegistrationService().addListener(lwM2mServerListener.registrationListener);
  123 + this.lhServerCert.getPresenceService().addListener(lwM2mServerListener.presenceListener);
  124 + this.lhServerCert.getObservationService().addListener(lwM2mServerListener.observationListener);
  125 + lwM2mServerListener = new LwM2mServerListener(lhServerNoSecPskRpk, service);
  126 + this.lhServerNoSecPskRpk.getRegistrationService().addListener(lwM2mServerListener.registrationListener);
  127 + this.lhServerNoSecPskRpk.getPresenceService().addListener(lwM2mServerListener.presenceListener);
  128 + this.lhServerNoSecPskRpk.getObservationService().addListener(lwM2mServerListener.observationListener);
  129 + }
  130 +
  131 + public static NetworkConfig getCoapConfig() {
  132 + NetworkConfig coapConfig;
  133 + File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME);
  134 + if (configFile.isFile()) {
  135 + coapConfig = new NetworkConfig();
  136 + coapConfig.load(configFile);
  137 + } else {
  138 + coapConfig = LeshanServerBuilder.createDefaultNetworkConfig();
  139 + coapConfig.store(configFile);
  140 + }
  141 + return coapConfig;
  142 + }
  143 +
  144 + public static String getValueTypeToString (Object value, ResourceModel.Type type) {
  145 + switch (type) {
  146 + case STRING: // String
  147 + case OBJLNK: // ObjectLink
  148 + return value.toString();
  149 + case INTEGER: // Long
  150 + return Long.toString((long) value);
  151 + case BOOLEAN: // Boolean
  152 + return Boolean.toString((Boolean) value);
  153 + case FLOAT: // Double
  154 + return Double.toString((Float)value);
  155 + case TIME: // Date
  156 + String DATE_FORMAT = "MMM d, yyyy HH:mm a";
  157 + DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
  158 + return formatter.format(new Date((Long) Integer.toUnsignedLong(Integer.valueOf((Integer) value))));
  159 + case OPAQUE: // byte[] value, base64
  160 + return Hex.encodeHexString((byte[])value);
  161 + default:
  162 + return null;
  163 + }
  164 + }
  165 +
  166 + public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) {
  167 + if (content instanceof LwM2mObject) {
  168 + return (LwM2mObject) content;
  169 + } else if (content instanceof LwM2mObjectInstance) {
  170 + return (LwM2mObjectInstance) content;
  171 + } else if (content instanceof LwM2mSingleResource) {
  172 + return (LwM2mSingleResource) content;
  173 + } else if (content instanceof LwM2mMultipleResource) {
  174 + return (LwM2mMultipleResource) content;
  175 + }
  176 + return null;
  177 + }
  178 +
  179 + public static AttrTelemetryObserveValue getNewProfileParameters(JsonObject profilesConfigData) {
  180 + AttrTelemetryObserveValue attrTelemetryObserveValue = new AttrTelemetryObserveValue();
  181 + attrTelemetryObserveValue.setPostKeyNameProfile(profilesConfigData.get(KEYNAME).getAsJsonObject());
  182 + attrTelemetryObserveValue.setPostAttributeProfile(profilesConfigData.get(ATTRIBUTE).getAsJsonArray());
  183 + attrTelemetryObserveValue.setPostTelemetryProfile(profilesConfigData.get(TELEMETRY).getAsJsonArray());
  184 + attrTelemetryObserveValue.setPostObserveProfile(profilesConfigData.get(OBSERVE).getAsJsonArray());
  185 + return attrTelemetryObserveValue;
  186 + }
  187 +
  188 + /**
  189 +
  190 + * @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard
  191 + * Example: with pathResource (use only pathResource)
  192 + * property: "observeAttr"
  193 + * {"keyName": {
  194 + * "/3/0/1": "modelNumber",
  195 + * "/3/0/0": "manufacturer",
  196 + * "/3/0/2": "serialNumber"
  197 + * },
  198 + * "attribute":["/2/0/1","/3/0/9"],
  199 + * "telemetry":["/1/0/1","/2/0/1","/6/0/1"],
  200 + * "observe":["/2/0","/2/0/0","/4/0/2"]}
  201 + */
  202 + public static JsonObject getObserveAttrTelemetryFromThingsboard(DeviceProfile deviceProfile) {
  203 + if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
  204 + Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
  205 + Object observeAttr = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties();
  206 + try {
  207 + ObjectMapper mapper = new ObjectMapper();
  208 + String observeAttrStr = mapper.writeValueAsString(observeAttr);
  209 + JsonObject objectMsg = (observeAttrStr != null) ? validateJson(observeAttrStr) : null;
  210 + return (getValidateCredentialsBodyFromThingsboard(objectMsg)) ? objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject() : null;
  211 + } catch (IOException e) {
  212 + e.printStackTrace();
  213 + }
  214 + }
  215 + return null;
  216 + }
  217 +
  218 + public static JsonObject getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) {
  219 + if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
  220 + Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
  221 + Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties();
  222 + try {
  223 + ObjectMapper mapper = new ObjectMapper();
  224 + String bootstrapStr = mapper.writeValueAsString(bootstrap);
  225 + JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null;
  226 + return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null;
  227 + } catch (IOException e) {
  228 + e.printStackTrace();
  229 + }
  230 + }
  231 + return null;
  232 + }
  233 +
  234 + private static boolean getValidateCredentialsBodyFromThingsboard(JsonObject objectMsg) {
  235 + return (objectMsg != null &&
  236 + !objectMsg.isJsonNull() &&
  237 + objectMsg.has(OBSERVE_ATTRIBUTE_TELEMETRY) &&
  238 + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).isJsonNull() &&
  239 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).isJsonObject() &&
  240 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(KEYNAME) &&
  241 + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEYNAME).isJsonNull() &&
  242 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEYNAME).isJsonObject() &&
  243 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE) &&
  244 + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).isJsonNull() &&
  245 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).isJsonArray() &&
  246 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(TELEMETRY) &&
  247 + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonNull() &&
  248 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() &&
  249 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) &&
  250 + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() &&
  251 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray());
  252 + }
  253 +
  254 + private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) {
  255 + return (objectMsg != null &&
  256 + !objectMsg.isJsonNull() &&
  257 + objectMsg.has(BOOTSTRAP) &&
  258 + objectMsg.get(BOOTSTRAP).isJsonObject() &&
  259 + !objectMsg.get(BOOTSTRAP).isJsonNull() &&
  260 + objectMsg.get(BOOTSTRAP).getAsJsonObject().has(SERVERS) &&
  261 + !objectMsg.get(BOOTSTRAP).getAsJsonObject().get(SERVERS).isJsonNull() &&
  262 + objectMsg.get(BOOTSTRAP).getAsJsonObject().get(SERVERS).isJsonObject() &&
  263 + objectMsg.get(BOOTSTRAP).getAsJsonObject().has(BOOTSTRAP_SERVER) &&
  264 + !objectMsg.get(BOOTSTRAP).getAsJsonObject().get(BOOTSTRAP_SERVER).isJsonNull() &&
  265 + objectMsg.get(BOOTSTRAP).getAsJsonObject().get(BOOTSTRAP_SERVER).isJsonObject() &&
  266 + objectMsg.get(BOOTSTRAP).getAsJsonObject().has(LWM2M_SERVER) &&
  267 + !objectMsg.get(BOOTSTRAP).getAsJsonObject().get(LWM2M_SERVER).isJsonNull() &&
  268 + objectMsg.get(BOOTSTRAP).getAsJsonObject().get(LWM2M_SERVER).isJsonObject());
  269 + }
  270 +
  271 +
  272 + public static JsonObject validateJson(String jsonStr) {
  273 + JsonObject object = null;
  274 + if (jsonStr != null && !jsonStr.isEmpty()) {
  275 + String jsonValidFlesh = jsonStr.replaceAll("\\\\", "");
  276 + jsonValidFlesh = jsonValidFlesh.replaceAll("\n", "");
  277 + jsonValidFlesh = jsonValidFlesh.replaceAll("\t", "");
  278 + jsonValidFlesh = jsonValidFlesh.replaceAll(" ", "");
  279 + String jsonValid = (jsonValidFlesh.substring(0, 1).equals("\"") && jsonValidFlesh.substring(jsonValidFlesh.length() - 1).equals("\"")) ? jsonValidFlesh.substring(1, jsonValidFlesh.length() - 1) : jsonValidFlesh;
  280 + try {
  281 + object = new JsonParser().parse(jsonValid).getAsJsonObject();
  282 + } catch (JsonSyntaxException e) {
  283 + log.error("[{}] Fail validateJson [{}]", jsonStr, e.getMessage());
  284 + }
  285 + }
  286 + return object;
  287 + }
  288 +
  289 + public static <T> Optional<T> decode(byte[] byteArray) {
  290 + try {
  291 + FSTConfiguration config = FSTConfiguration.createDefaultConfiguration();;
  292 + T msg = (T) config.asObject(byteArray);
  293 + return Optional.ofNullable(msg);
  294 + } catch (IllegalArgumentException e) {
  295 + log.error("Error during deserialization message, [{}]", e.getMessage());
  296 + return Optional.empty();
  297 + }
  298 + }
  299 +
  300 + /**
  301 + * Equals to Map for values
  302 + * @param map1
  303 + * @param map2
  304 + * @param <V>
  305 + * @return
  306 + */
  307 + public static <V extends Comparable<V>> boolean mapsEquals(Map<?,V> map1, Map<?,V> map2) {
  308 + List<V> values1 = new ArrayList<V>(map1.values());
  309 + List<V> values2 = new ArrayList<V>(map2.values());
  310 + Collections.sort(values1);
  311 + Collections.sort(values2);
  312 + return values1.equals(values2);
  313 + }
  314 +
  315 + public static String convertCamelCase (String str) {
  316 + str = str.toLowerCase().replace("/[^a-z ]+/g", " ");
  317 + str = str.replace("/^(.)|\\s(.)/g", "$1");
  318 + str = str.replace("/[^a-zA-Z]+/g", "");
  319 + return str;
  320 + }
  321 +
  322 + public static String splitCamelCaseString(String s){
  323 + LinkedList<String> linkedListOut = new LinkedList<String>();
  324 + LinkedList<String> linkedList = new LinkedList<String>((Arrays.asList(s.split(" "))));
  325 + linkedList.stream().forEach(str-> {
  326 + String strOut = str.replaceAll("\\W", "").replaceAll("_", "").toUpperCase();
  327 + if (strOut.length()>1) linkedListOut.add(strOut.substring(0, 1) + strOut.substring(1).toLowerCase());
  328 + else linkedListOut.add(strOut);
  329 + });
  330 + linkedListOut.set(0, (linkedListOut.get(0).substring(0, 1).toLowerCase() + linkedListOut.get(0).substring(1)));
  331 + return StringUtils.join(linkedListOut, "");
  332 + }
  333 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.californium.core.coap.Response;
  20 +import org.eclipse.leshan.core.attributes.Attribute;
  21 +import org.eclipse.leshan.core.attributes.AttributeSet;
  22 +import org.eclipse.leshan.core.model.ResourceModel;
  23 +import org.eclipse.leshan.core.node.LwM2mSingleResource;
  24 +import org.eclipse.leshan.core.node.ObjectLink;
  25 +import org.eclipse.leshan.core.observation.Observation;
  26 +import org.eclipse.leshan.core.request.ContentFormat;
  27 +import org.eclipse.leshan.core.request.WriteRequest;
  28 +import org.eclipse.leshan.core.request.DiscoverRequest;
  29 +import org.eclipse.leshan.core.request.DownlinkRequest;
  30 +import org.eclipse.leshan.core.request.ObserveRequest;
  31 +import org.eclipse.leshan.core.request.CancelObservationRequest;
  32 +import org.eclipse.leshan.core.request.ReadRequest;
  33 +import org.eclipse.leshan.core.request.ExecuteRequest;
  34 +import org.eclipse.leshan.core.request.WriteAttributesRequest;
  35 +import org.eclipse.leshan.core.response.ResponseCallback;
  36 +import org.eclipse.leshan.core.response.LwM2mResponse;
  37 +import org.eclipse.leshan.core.response.ObserveResponse;
  38 +import org.eclipse.leshan.core.response.ReadResponse;
  39 +import org.eclipse.leshan.core.response.CancelObservationResponse;
  40 +import org.eclipse.leshan.core.response.DeleteResponse;
  41 +import org.eclipse.leshan.core.response.ExecuteResponse;
  42 +import org.eclipse.leshan.core.response.DiscoverResponse;
  43 +import org.eclipse.leshan.core.response.WriteAttributesResponse;
  44 +import org.eclipse.leshan.core.response.WriteResponse;
  45 +import org.eclipse.leshan.core.util.Hex;
  46 +import org.eclipse.leshan.core.util.NamedThreadFactory;
  47 +import org.eclipse.leshan.server.californium.LeshanServer;
  48 +import org.eclipse.leshan.server.registration.Registration;
  49 +import org.springframework.beans.factory.annotation.Autowired;
  50 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  51 +import org.springframework.stereotype.Service;
  52 +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient;
  53 +
  54 +import javax.annotation.PostConstruct;
  55 +import java.util.ArrayList;
  56 +import java.util.Collection;
  57 +import java.util.Date;
  58 +import java.util.Iterator;
  59 +import java.util.concurrent.ExecutorService;
  60 +import java.util.concurrent.Executors;
  61 +
  62 +import static org.eclipse.californium.core.coap.CoAP.ResponseCode.isSuccess;
  63 +import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
  64 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEFAULT_TIMEOUT;
  65 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_DISCOVER;
  66 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE;
  67 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ;
  68 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
  69 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE;
  70 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_EXECUTE;
  71 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_OBSERVE_CANCEL;
  72 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE;
  73 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR;
  74 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO;
  75 +
  76 +@Slf4j
  77 +@Service("LwM2MTransportRequest")
  78 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  79 +public class LwM2MTransportRequest {
  80 + private final ExecutorService executorService;
  81 + private static final String RESPONSE_CHANNEL = "THINGSBOARD_RESP";
  82 +
  83 + @Autowired
  84 + LwM2MTransportService service;
  85 +
  86 + public LwM2MTransportRequest() {
  87 + executorService = Executors.newCachedThreadPool(
  88 + new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL)));
  89 + }
  90 +
  91 +
  92 + @PostConstruct
  93 + public void init() {
  94 + }
  95 +
  96 + public Collection<Registration> doGetRegistrations(LeshanServer lwServer) {
  97 + Collection<Registration> registrations = new ArrayList<>();
  98 + for (Iterator<Registration> iterator = lwServer.getRegistrationService().getAllRegistrations(); iterator
  99 + .hasNext(); ) {
  100 + registrations.add(iterator.next());
  101 + }
  102 + return registrations;
  103 + }
  104 +
  105 + /**
  106 + * Device management and service enablement, including Read, Write, Execute, Discover, Create, Delete and Write-Attributes
  107 + *
  108 + * @param lwServer
  109 + * @param registration
  110 + * @param target
  111 + * @param typeOper
  112 + * @param contentFormatParam
  113 + * @param lwM2MClient
  114 + * @param observation
  115 + */
  116 + public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper,
  117 + String contentFormatParam, LwM2MClient lwM2MClient, Observation observation, Object params, long timeoutInMs) {
  118 + ResultIds resultIds = new ResultIds(target);
  119 + if (registration != null && resultIds.getObjectId() >= 0) {
  120 + DownlinkRequest request = null;
  121 + ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null;
  122 + ResourceModel resource = (resultIds.resourceId >= 0) ? (lwM2MClient != null) ?
  123 + lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getObjectModel().resources.get(resultIds.resourceId) : null : null;
  124 + ResourceModel.Type resType = (resource == null) ? null : resource.type;
  125 + boolean resMultiple = (resource == null) ? false : resource.multiple;
  126 + timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
  127 + switch (typeOper) {
  128 + case GET_TYPE_OPER_READ:
  129 + request = new ReadRequest(contentFormat, target);
  130 + break;
  131 + case GET_TYPE_OPER_DISCOVER:
  132 + request = new DiscoverRequest(target);
  133 + break;
  134 + case GET_TYPE_OPER_OBSERVE:
  135 + if (resultIds.getResourceId() >= 0) {
  136 + request = new ObserveRequest(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId());
  137 + } else if (resultIds.getInstanceId() >= 0) {
  138 + request = new ObserveRequest(resultIds.getObjectId(), resultIds.getInstanceId());
  139 + } else if (resultIds.getObjectId() >= 0) {
  140 + request = new ObserveRequest(resultIds.getObjectId());
  141 + }
  142 + break;
  143 + case POST_TYPE_OPER_OBSERVE_CANCEL:
  144 + request = new CancelObservationRequest(observation);
  145 + break;
  146 + case POST_TYPE_OPER_EXECUTE:
  147 + if (params != null && !resMultiple) {
  148 + request = new ExecuteRequest(target, LwM2MTransportHandler.getValueTypeToString(params, resType));
  149 + } else {
  150 + request = new ExecuteRequest(target);
  151 + }
  152 + break;
  153 + case POST_TYPE_OPER_WRITE_REPLACE:
  154 + // Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
  155 + if (contentFormat.equals(ContentFormat.TLV) && !resMultiple) {
  156 + request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, resType);
  157 + }
  158 + // Mode.REPLACE && Request to write a <b>String Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON)
  159 + else if (!contentFormat.equals(ContentFormat.TLV) && !resMultiple) {
  160 + request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, resType);
  161 + }
  162 + break;
  163 + case PUT_TYPE_OPER_WRITE_UPDATE:
  164 + if (resultIds.getResourceId() >= 0) {
  165 + ResourceModel resourceModel = lwServer.getModelProvider().getObjectModel(registration).getObjectModel(resultIds.getObjectId()).resources.get(resultIds.getResourceId());
  166 + ResourceModel.Type typeRes = resourceModel.type;
  167 +// request = getWriteRequestResource(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, typeRes);
  168 + }
  169 + break;
  170 + case PUT_TYPE_OPER_WRITE_ATTRIBUTES:
  171 + /**
  172 + * As example:
  173 + * a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
  174 + * to the Server with a minimum interval of 1sec;
  175 + * this value is set at theResource level.
  176 + * b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
  177 + * to the Server with a minimum value (pmin) given by the default one
  178 + * (resource 2 of Object Server ID=1),
  179 + * or with another value if this Attribute has been set at another level
  180 + * (Object or Object Instance: see section5.1.1).
  181 + * c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
  182 + * will be notified to the Server with a minimum interval of 10 sec;
  183 + * this value is set at the Object Instance level.
  184 + * d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
  185 + * when:
  186 + * a.old value is 20 and new value is 35 due to step condition
  187 + * b.old value is 45 and new value is 50 due to gt condition
  188 + * c.old value is 50 and new value is 40 due to both gt and step conditions
  189 + * d.old value is 35 and new value is 20 due to step conditione)
  190 + * Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
  191 + * when:
  192 + * a.old value is 17 and new value is 24 due to lt condition
  193 + * b.old value is 75 and new value is 90 due to both gt and step conditions
  194 + * String uriQueries = "pmin=10&pmax=60";
  195 + * AttributeSet attributes = AttributeSet.parse(uriQueries);
  196 + * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
  197 + * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
  198 + * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
  199 + * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
  200 + * Attribute [] attrs = {gt, st};
  201 + */
  202 + Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.valueOf("1")));
  203 + Attribute[] attrs = {pmin};
  204 + AttributeSet attrSet = new AttributeSet(attrs);
  205 + if (resultIds.getResourceId() >= 0) {
  206 + request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), attrSet);
  207 + } else if (resultIds.getInstanceId() >= 0) {
  208 + request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getInstanceId(), attrSet);
  209 + } else if (resultIds.getObjectId() >= 0) {
  210 + request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet);
  211 + }
  212 + break;
  213 + default:
  214 + }
  215 + if (request != null) sendRequest(lwServer, registration, request, lwM2MClient, timeoutInMs);
  216 + }
  217 + }
  218 +
  219 + /**
  220 + *
  221 + * @param lwServer
  222 + * @param registration
  223 + * @param request
  224 + * @param lwM2MClient
  225 + * @param timeoutInMs
  226 + */
  227 + private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, LwM2MClient lwM2MClient, long timeoutInMs) {
  228 + lwServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
  229 + if (isSuccess(((Response)response.getCoapResponse()).getCode())) {
  230 + this.handleResponse(registration, request.getPath().toString(), response, request, lwM2MClient);
  231 + if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) {
  232 + String msg = String.format(LOG_LW2M_INFO + " sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client",
  233 + ((Response)response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(),
  234 + ((LwM2mSingleResource)((WriteRequest) request).getNode()).getValue().toString());
  235 + service.sentLogsToThingsboard(msg, registration.getId());
  236 + log.info("[{}] - [{}] [{}] [{}] Update SendRequest", ((Response)response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), ((LwM2mSingleResource)((WriteRequest) request).getNode()).getValue());
  237 + }
  238 + }
  239 + else {
  240 + String msg = String.format(LOG_LW2M_ERROR + " sendRequest: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client",
  241 + ((Response)response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
  242 + service.sentLogsToThingsboard(msg, registration.getId());
  243 + log.error("[{}] - [{}] [{}] error SendRequest", ((Response)response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
  244 + }
  245 + }, e -> {
  246 + String msg = String.format(LOG_LW2M_ERROR + " sendRequest: Resource path - %s msg error - %s SendRequest to Client",
  247 + request.getPath().toString(), e.toString());
  248 + service.sentLogsToThingsboard(msg, registration.getId());
  249 + log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString());
  250 + });
  251 + }
  252 +
  253 + private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type) {
  254 + try {
  255 + switch (type) {
  256 + case STRING: // String
  257 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, value.toString()) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString());
  258 + case INTEGER: // Long
  259 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Integer.toUnsignedLong(Integer.valueOf(value.toString()))) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Integer.toUnsignedLong(Integer.valueOf(value.toString())));
  260 + case OBJLNK: // ObjectLink
  261 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString()));
  262 + case BOOLEAN: // Boolean
  263 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Boolean.valueOf(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Boolean.valueOf(value.toString()));
  264 + case FLOAT: // Double
  265 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.valueOf(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.valueOf(value.toString()));
  266 + case TIME: // Date
  267 + 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()))));
  268 + case OPAQUE: // byte[] value, base64
  269 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray()));
  270 + default:
  271 + }
  272 + return null;
  273 + } catch (NumberFormatException e) {
  274 + String patn = "/" + objectId + "/" + instanceId + "/" + resourceId;
  275 + log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString());
  276 + return null;
  277 + }
  278 + }
  279 +
  280 + private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient) {
  281 + executorService.submit(new Runnable() {
  282 + @Override
  283 + public void run() {
  284 +
  285 + try {
  286 + sendResponse(registration, path, response, request, lwM2MClient);
  287 + } catch (RuntimeException t) {
  288 + log.error("[{}] endpoint [{}] path [{}] error Unable to after send response.", registration.getEndpoint(), path, t.toString());
  289 + }
  290 + }
  291 + });
  292 + }
  293 +
  294 + /**
  295 + * processing a response from a client
  296 + * @param registration -
  297 + * @param path -
  298 + * @param response -
  299 + * @param lwM2MClient -
  300 + */
  301 + private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient) {
  302 + if (response instanceof ObserveResponse) {
  303 + service.onObservationResponse(registration, path, (ReadResponse) response);
  304 + } else if (response instanceof CancelObservationResponse) {
  305 + log.info("[{}] Path [{}] CancelObservationResponse 3_Send", path, response);
  306 + } else if (response instanceof ReadResponse) {
  307 + /**
  308 + * Use only at the first start after registration
  309 + * Fill with data -> Model client
  310 + */
  311 + if (lwM2MClient != null) {
  312 + if (lwM2MClient.getPendingRequests().size() > 0) {
  313 + lwM2MClient.onSuccessHandler(path, response);
  314 + }
  315 + }
  316 + /**
  317 + * Use after registration on request
  318 + */
  319 + else {
  320 + service.onObservationResponse(registration, path, (ReadResponse) response);
  321 + }
  322 + } else if (response instanceof DeleteResponse) {
  323 + log.info("[{}] Path [{}] DeleteResponse 5_Send", path, response);
  324 + } else if (response instanceof DiscoverResponse) {
  325 + log.info("[{}] Path [{}] DiscoverResponse 6_Send", path, response);
  326 + } else if (response instanceof ExecuteResponse) {
  327 + log.info("[{}] Path [{}] ExecuteResponse 7_Send", path, response);
  328 + } else if (response instanceof WriteAttributesResponse) {
  329 + log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", path, response);
  330 + } else if (response instanceof WriteResponse) {
  331 + log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", path, response);
  332 + service.onAttributeUpdateOk(registration, path, (WriteRequest) request);
  333 + }
  334 + }
  335 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  20 +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
  21 +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
  22 +import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
  23 +import org.eclipse.leshan.server.californium.LeshanServer;
  24 +import org.eclipse.leshan.server.californium.LeshanServerBuilder;
  25 +import org.eclipse.leshan.server.model.LwM2mModelProvider;
  26 +import org.eclipse.leshan.server.model.VersionedModelProvider;
  27 +import org.springframework.beans.factory.annotation.Autowired;
  28 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  29 +import org.springframework.context.annotation.Bean;
  30 +import org.springframework.context.annotation.ComponentScan;
  31 +import org.springframework.context.annotation.Configuration;
  32 +import org.springframework.context.annotation.Primary;
  33 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  34 +import org.thingsboard.server.transport.lwm2m.server.secure.LwM2MSetSecurityStoreServer;
  35 +import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore;
  36 +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
  37 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509;
  38 +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK;
  39 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig;
  40 +
  41 +
  42 +@Slf4j
  43 +@ComponentScan("org.thingsboard.server.transport.lwm2m.server")
  44 +@ComponentScan("org.thingsboard.server.transport.lwm2m.utils")
  45 +@Configuration("LwM2MTransportServerConfiguration")
  46 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  47 +public class LwM2MTransportServerConfiguration {
  48 +
  49 + @Autowired
  50 + private LwM2MTransportContextServer context;
  51 +
  52 + @Autowired
  53 + private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore;
  54 +
  55 + @Primary
  56 + @Bean(name = "LeshanServerCert")
  57 + public LeshanServer getLeshanServerCert() {
  58 + log.info("Starting LwM2M transport ServerCert... PostConstruct");
  59 + return getLeshanServer(this.context.getCtxServer().getServerPortCert(), this.context.getCtxServer().getServerSecurePortCert(), X509);
  60 + }
  61 +
  62 + @Bean(name = "leshanServerNoSecPskRpk")
  63 + public LeshanServer getLeshanServerNoSecPskRpk() {
  64 + log.info("Starting LwM2M transport ServerNoSecPskRpk... PostConstruct");
  65 + return getLeshanServer(this.context.getCtxServer().getServerPort(), this.context.getCtxServer().getServerSecurePort(), RPK);
  66 + }
  67 +
  68 + private LeshanServer getLeshanServer(Integer serverPort, Integer serverSecurePort, LwM2MSecurityMode dtlsMode) {
  69 +
  70 + LeshanServerBuilder builder = new LeshanServerBuilder();
  71 + builder.setLocalAddress(this.context.getCtxServer().getServerHost(), serverPort);
  72 + builder.setLocalSecureAddress(this.context.getCtxServer().getServerSecureHost(), serverSecurePort);
  73 + builder.setEncoder(new DefaultLwM2mNodeEncoder());
  74 + LwM2mNodeDecoder decoder = new DefaultLwM2mNodeDecoder();
  75 + builder.setDecoder(decoder);
  76 + builder.setEncoder(new DefaultLwM2mNodeEncoder(new LwM2mValueConverterImpl()));
  77 +
  78 + /** Create CoAP Config */
  79 + builder.setCoapConfig(getCoapConfig());
  80 +
  81 + /** Define model provider (Create Models )*/
  82 + LwM2mModelProvider modelProvider = new VersionedModelProvider(this.context.getCtxServer().getModelsValue());
  83 + builder.setObjectModelProvider(modelProvider);
  84 +
  85 + /** Create DTLS Config */
  86 + DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
  87 + dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isSupportDeprecatedCiphersEnable());
  88 + /** Set DTLS Config */
  89 + builder.setDtlsConfig(dtlsConfig);
  90 +
  91 + /** Use a magic converter to support bad type send by the UI. */
  92 + builder.setEncoder(new DefaultLwM2mNodeEncoder(new LwM2mValueConverterImpl()));
  93 +
  94 + /** Create DTLS security mode
  95 + * There can be only one DTLS security mode
  96 + */
  97 + new LwM2MSetSecurityStoreServer(builder, context, lwM2mInMemorySecurityStore, dtlsMode);
  98 +
  99 + /** Create LWM2M server */
  100 + return builder.build();
  101 + }
  102 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.server.californium.LeshanServer;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.beans.factory.annotation.Qualifier;
  22 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  23 +import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC;
  25 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  26 +import javax.annotation.PostConstruct;
  27 +import javax.annotation.PreDestroy;
  28 +
  29 +@Slf4j
  30 +@Service("LwM2MTransportServerInitializer")
  31 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  32 +public class LwM2MTransportServerInitializer {
  33 +
  34 + @Autowired
  35 + @Qualifier("LeshanServerCert")
  36 + private LeshanServer lhServerCert;
  37 +
  38 + @Autowired
  39 + @Qualifier("leshanServerNoSecPskRpk")
  40 + private LeshanServer lhServerNoSecPskRpk;
  41 +
  42 + @Autowired
  43 + private LwM2MTransportContextServer context;
  44 +
  45 + @PostConstruct
  46 + public void init() {
  47 + if (this.context.getCtxServer().getEnableGenPskRpk()) new LWM2MGenerationPSkRPkECC();
  48 + if (this.context.getCtxServer().isServerStartAll()) {
  49 + this.lhServerCert.start();
  50 + this.lhServerNoSecPskRpk.start();
  51 + }
  52 + else {
  53 + if (this.context.getCtxServer().getServerDtlsMode() == LwM2MSecurityMode.X509.code) {
  54 + this.lhServerCert.start();
  55 + }
  56 + else {
  57 + this.lhServerNoSecPskRpk.start();
  58 + }
  59 + }
  60 + }
  61 +
  62 + @PreDestroy
  63 + public void shutdown() {
  64 + log.info("Stopping LwM2M transport Server!");
  65 + try {
  66 + lhServerCert.destroy();
  67 + lhServerNoSecPskRpk.destroy();
  68 + } finally {
  69 + }
  70 + log.info("LwM2M transport Server stopped!");
  71 + }
  72 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import com.google.gson.*;
  19 +import lombok.SneakyThrows;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.leshan.core.model.ResourceModel;
  22 +import org.eclipse.leshan.core.node.LwM2mMultipleResource;
  23 +import org.eclipse.leshan.core.node.LwM2mObject;
  24 +import org.eclipse.leshan.core.node.LwM2mObjectInstance;
  25 +import org.eclipse.leshan.core.node.LwM2mSingleResource;
  26 +import org.eclipse.leshan.core.node.LwM2mResource;
  27 +import org.eclipse.leshan.core.node.LwM2mPath;
  28 +import org.eclipse.leshan.core.observation.Observation;
  29 +import org.eclipse.leshan.core.request.ContentFormat;
  30 +import org.eclipse.leshan.core.request.WriteRequest;
  31 +import org.eclipse.leshan.core.response.ReadResponse;
  32 +import org.eclipse.leshan.core.util.Hex;
  33 +import org.eclipse.leshan.server.californium.LeshanServer;
  34 +import org.eclipse.leshan.server.registration.Registration;
  35 +import org.springframework.beans.factory.annotation.Autowired;
  36 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  37 +import org.springframework.stereotype.Service;
  38 +import org.thingsboard.server.common.data.Device;
  39 +import org.thingsboard.server.common.data.DeviceProfile;
  40 +import org.thingsboard.server.common.transport.TransportService;
  41 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  42 +import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  43 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  44 +import org.thingsboard.server.common.transport.service.DefaultTransportService;
  45 +import org.thingsboard.server.gen.transport.TransportProtos;
  46 +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
  48 +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
  49 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
  50 +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  51 +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
  52 +import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
  53 +import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue;
  54 +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient;
  55 +import org.thingsboard.server.transport.lwm2m.server.client.ModelObject;
  56 +import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters;
  57 +import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
  58 +import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore;
  59 +
  60 +import javax.annotation.PostConstruct;
  61 +import java.util.Collection;
  62 +import java.util.UUID;
  63 +import java.util.Random;
  64 +import java.util.Map;
  65 +import java.util.HashMap;
  66 +import java.util.Arrays;
  67 +import java.util.Set;
  68 +import java.util.HashSet;
  69 +import java.util.NoSuchElementException;
  70 +import java.util.Optional;
  71 +import java.util.concurrent.ConcurrentHashMap;
  72 +import java.util.concurrent.ConcurrentMap;
  73 +import java.util.concurrent.CountDownLatch;
  74 +import java.util.concurrent.TimeUnit;
  75 +import java.util.concurrent.atomic.AtomicBoolean;
  76 +import java.util.function.Predicate;
  77 +import java.util.stream.Collectors;
  78 +import java.util.stream.Stream;
  79 +
  80 +import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
  81 +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.*;
  82 +
  83 +@Slf4j
  84 +@Service("LwM2MTransportService")
  85 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
  86 +public class LwM2MTransportService {
  87 +
  88 + @Autowired
  89 + private LwM2MJsonAdaptor adaptor;
  90 +
  91 + @Autowired
  92 + private TransportService transportService;
  93 +
  94 + @Autowired
  95 + public LwM2MTransportContextServer context;
  96 +
  97 + @Autowired
  98 + private LwM2MTransportRequest lwM2MTransportRequest;
  99 +
  100 + @Autowired
  101 + LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore;
  102 +
  103 +
  104 + @PostConstruct
  105 + public void init() {
  106 + context.getScheduler().scheduleAtFixedRate(() -> checkInactivityAndReportActivity(), new Random().nextInt((int) context.getCtxServer().getSessionReportTimeout()), context.getCtxServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS);
  107 + }
  108 +
  109 + /**
  110 + * Start registration device
  111 + * Create session: Map<String <registrationId >, LwM2MClient>
  112 + * 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint)
  113 + * 1.1 When we initialize the registration, we register the session by endpoint.
  114 + * 1.2 If the server has incomplete requests (canceling the registration of the previous session),
  115 + * delete the previous session only by the previous registration.getId
  116 + * 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId
  117 + * 1.2 Remove from sessions Model by enpPoint
  118 + * Next -> Create new LwM2MClient for current session -> setModelClient...
  119 + *
  120 + * @param lwServer - LeshanServer
  121 + * @param registration - Registration LwM2M Client
  122 + * @param previousObsersations - may be null
  123 + */
  124 + public void onRegistered(LeshanServer lwServer, Registration registration, Collection<Observation> previousObsersations) {
  125 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(lwServer, registration);
  126 + if (lwM2MClient != null) {
  127 + lwM2MClient.setLwM2MTransportService(this);
  128 + lwM2MClient.setLwM2MTransportService(this);
  129 + lwM2MClient.setSessionUuid(UUID.randomUUID());
  130 + this.setLwM2MClient(lwServer, registration, lwM2MClient);
  131 + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId());
  132 + if (sessionInfo != null) {
  133 + lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
  134 + lwM2MClient.setProfileUuid(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
  135 + lwM2MClient.setDeviceName(sessionInfo.getDeviceName());
  136 + lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType());
  137 + transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo));
  138 + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null);
  139 + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
  140 + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client registration", registration.getId());
  141 + } else {
  142 + log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo);
  143 + }
  144 + } else {
  145 + log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), lwM2MClient);
  146 + }
  147 + }
  148 +
  149 + /**
  150 + * @param lwServer - LeshanServer
  151 + * @param registration - Registration LwM2M Client
  152 + */
  153 + public void updatedReg(LeshanServer lwServer, Registration registration) {
  154 + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId());
  155 + if (sessionInfo != null) {
  156 + log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
  157 + } else {
  158 + log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo);
  159 + }
  160 + }
  161 +
  162 + /**
  163 + * @param registration - Registration LwM2M Client
  164 + * @param observations - All paths observations before unReg
  165 + * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect
  166 + */
  167 + public void unReg(Registration registration, Collection<Observation> observations) {
  168 + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId());
  169 + this.closeClientSession(registration);
  170 + }
  171 +
  172 + private void closeClientSession(Registration registration) {
  173 + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId());
  174 + if (sessionInfo != null) {
  175 + transportService.deregisterSession(sessionInfo);
  176 + this.doCloseSession(sessionInfo);
  177 + lwM2mInMemorySecurityStore.delRemoveSessionAndListener(registration.getId());
  178 + if (lwM2mInMemorySecurityStore.getProfiles().size() > 0) {
  179 + this.syncSessionsAndProfiles();
  180 + }
  181 + log.info("Client: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
  182 + } else {
  183 + log.error("Client: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo);
  184 + }
  185 + }
  186 +
  187 + public void onSleepingDev(Registration registration) {
  188 + log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
  189 + //TODO: associate endpointId with device information.
  190 + }
  191 +
  192 + /**
  193 + * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay,
  194 + * * if you need to do long time processing use a dedicated thread pool.
  195 + *
  196 + * @param registration
  197 + */
  198 +
  199 + public void onAwakeDev(Registration registration) {
  200 + log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
  201 + //TODO: associate endpointId with device information.
  202 + }
  203 +
  204 + /**
  205 + * This method is used to sync with sessions
  206 + * Removes a profile if not used in sessions
  207 + */
  208 + private void syncSessionsAndProfiles() {
  209 + Map<UUID, AttrTelemetryObserveValue> profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet()
  210 + .stream()
  211 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  212 + profilesClone.forEach((k, v) -> {
  213 + String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet()
  214 + .stream()
  215 + .filter(e -> e.getValue().getProfileUuid().equals(k))
  216 + .findFirst()
  217 + .map(Map.Entry::getKey) // return the key of the matching entry if found
  218 + .orElse("");
  219 + if (registrationId.isEmpty()) {
  220 + lwM2mInMemorySecurityStore.getProfiles().remove(k);
  221 + }
  222 + });
  223 + }
  224 +
  225 + /**
  226 + * Create new LwM2MClient for current session -> setModelClient...
  227 + * #1 Add all ObjectLinks (instance) to control the process of executing requests to the client
  228 + * to get the client model with current values
  229 + * #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse
  230 + *
  231 + * @param lwServer - LeshanServer
  232 + * @param registration - Registration LwM2M Client
  233 + * @param lwM2MClient - object with All parameters off client
  234 + */
  235 + private void setLwM2MClient(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) {
  236 + // #1
  237 + Arrays.stream(registration.getObjectLinks()).forEach(url -> {
  238 + ResultIds pathIds = new ResultIds(url.getUrl());
  239 + if (pathIds.instanceId > -1 && pathIds.resourceId == -1) {
  240 + lwM2MClient.getPendingRequests().add(url.getUrl());
  241 + }
  242 + });
  243 + // #2
  244 + Arrays.stream(registration.getObjectLinks()).forEach(url -> {
  245 + ResultIds pathIds = new ResultIds(url.getUrl());
  246 + if (pathIds.instanceId > -1 && pathIds.resourceId == -1) {
  247 + lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ,
  248 + ContentFormat.TLV.getName(), lwM2MClient, null, null, this.context.getCtxServer().getTimeout());
  249 + }
  250 + });
  251 + }
  252 +
  253 + /**
  254 + * @param registrationId - Id of Registration LwM2M Client
  255 + * @return - sessionInfo after access connect client
  256 + */
  257 + private SessionInfoProto getValidateSessionInfo(String registrationId) {
  258 + SessionInfoProto sessionInfo = null;
  259 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registrationId);
  260 + if (lwM2MClient != null) {
  261 + ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse();
  262 + if (msg == null || msg.getDeviceInfo() == null) {
  263 + log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED);
  264 + this.closeClientSession(lwM2MClient.getRegistration());
  265 + } else {
  266 + sessionInfo = SessionInfoProto.newBuilder()
  267 + .setNodeId(this.context.getNodeId())
  268 + .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits())
  269 + .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits())
  270 + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
  271 + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
  272 + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
  273 + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
  274 + .setDeviceName(msg.getDeviceInfo().getDeviceName())
  275 + .setDeviceType(msg.getDeviceInfo().getDeviceType())
  276 + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
  277 + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
  278 + .build();
  279 + }
  280 + }
  281 + return sessionInfo;
  282 + }
  283 +
  284 + /**
  285 + * Add attribute/telemetry information from Client and credentials/Profile to client model and start observe
  286 + * !!! if the resource has an observation, but no telemetry or attribute - the observation will not use
  287 + * #1 Client`s starting info to send to thingsboard
  288 + * #2 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection
  289 + * #3 Start observe
  290 + *
  291 + * @param lwServer - LeshanServer
  292 + * @param registration - Registration LwM2M Client
  293 + */
  294 +
  295 + public void updatesAndSentModelParameter(LeshanServer lwServer, Registration registration) {
  296 + // #1
  297 +// this.setParametersToModelClient(registration, modelClient, deviceProfile);
  298 + // #2
  299 + this.updateAttrTelemetry(registration, true, null);
  300 + // #3
  301 + this.onSentObserveToClient(lwServer, registration);
  302 + }
  303 +
  304 +
  305 + /**
  306 + * Sent Attribute and Telemetry to Thingsboard
  307 + * #1 - get AttrName/TelemetryName with value:
  308 + * #1.1 from Client
  309 + * #1.2 from LwM2MClient:
  310 + * -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile
  311 + * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId)
  312 + * #2 - set Attribute/Telemetry
  313 + *
  314 + * @param registration - Registration LwM2M Client
  315 + */
  316 + private void updateAttrTelemetry(Registration registration, boolean start, Set<String> paths) {
  317 + JsonObject attributes = new JsonObject();
  318 + JsonObject telemetrys = new JsonObject();
  319 + if (start) {
  320 + // #1.1
  321 + JsonObject attributeClient = this.getAttributeClient(registration);
  322 + if (attributeClient != null) {
  323 + attributeClient.entrySet().forEach(p -> {
  324 + attributes.add(p.getKey(), p.getValue());
  325 + });
  326 + }
  327 + }
  328 + // #1.2
  329 + CountDownLatch cancelLatch = new CountDownLatch(1);
  330 + this.getParametersFromProfile(attributes, telemetrys, registration, paths);
  331 + cancelLatch.countDown();
  332 + try {
  333 + cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
  334 + } catch (InterruptedException e) {
  335 + log.error("[{}] updateAttrTelemetry", e.toString());
  336 + }
  337 + if (attributes.getAsJsonObject().entrySet().size() > 0)
  338 + this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration.getId());
  339 + if (telemetrys.getAsJsonObject().entrySet().size() > 0)
  340 + this.updateParametersOnThingsboard(telemetrys, DEVICE_TELEMETRY_TOPIC, registration.getId());
  341 + }
  342 +
  343 + /**
  344 + * get AttrName/TelemetryName with value from Client
  345 + *
  346 + * @param registration -
  347 + * @return - JsonObject, format: {name: value}}
  348 + */
  349 + private JsonObject getAttributeClient(Registration registration) {
  350 + if (registration.getAdditionalRegistrationAttributes().size() > 0) {
  351 + JsonObject resNameValues = new JsonObject();
  352 + registration.getAdditionalRegistrationAttributes().entrySet().forEach(entry -> {
  353 + resNameValues.addProperty(entry.getKey(), entry.getValue());
  354 + });
  355 + return resNameValues;
  356 + }
  357 + return null;
  358 + }
  359 +
  360 + /**
  361 + * @param attributes - new JsonObject
  362 + * @param telemetry - new JsonObject
  363 + * @param registration - Registration LwM2M Client
  364 + * result: add to JsonObject those resources to which the user is subscribed and they have a value
  365 + * if path==null add All resources else only one
  366 + * (attributes/telemetry): new {name(Attr/Telemetry):value}
  367 + */
  368 + private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set<String> path) {
  369 + AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid());
  370 + attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> {
  371 + ResultIds pathIds = new ResultIds(p.getAsString().toString());
  372 + if (pathIds.getResourceId() > -1) {
  373 + if (path == null || path.contains(p.getAsString())) {
  374 + this.addParameters(pathIds, p.getAsString().toString(), attributes, registration);
  375 + }
  376 + }
  377 + });
  378 + attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> {
  379 + ResultIds pathIds = new ResultIds(p.getAsString().toString());
  380 + if (pathIds.getResourceId() > -1) {
  381 + if (path == null || path.contains(p.getAsString())) {
  382 + this.addParameters(pathIds, p.getAsString().toString(), telemetry, registration);
  383 + }
  384 + }
  385 + });
  386 + }
  387 +
  388 + /**
  389 + * @param pathIds - path resource
  390 + * @param parameters - JsonObject attributes/telemetry
  391 + * @param registration - Registration LwM2M Client
  392 + */
  393 + private void addParameters(ResultIds pathIds, String path, JsonObject parameters, Registration registration) {
  394 + ModelObject modelObject = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getModelObjects().get(pathIds.getObjectId());
  395 + JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()).getPostKeyNameProfile();
  396 + String resName = String.valueOf(names.get(path));
  397 + if (modelObject != null && resName != null && !resName.isEmpty()) {
  398 + String resValue = this.getResourceValue(modelObject, pathIds);
  399 + if (resValue != null) {
  400 + parameters.addProperty(resName, resValue);
  401 + }
  402 + }
  403 + }
  404 +
  405 + /**
  406 + * @param modelObject - ModelObject of Client
  407 + * @param pathIds - path resource
  408 + * @return - value of Resource or null
  409 + */
  410 + private String getResourceValue(ModelObject modelObject, ResultIds pathIds) {
  411 + String resValue = null;
  412 + if (modelObject.getInstances().get(pathIds.getInstanceId()) != null) {
  413 + LwM2mObjectInstance instance = modelObject.getInstances().get(pathIds.getInstanceId());
  414 + if (instance.getResource(pathIds.getResourceId()) != null) {
  415 + resValue = instance.getResource(pathIds.getResourceId()).getType() == OPAQUE ?
  416 + Hex.encodeHexString((byte[]) instance.getResource(pathIds.getResourceId()).getValue()).toLowerCase() :
  417 + (instance.getResource(pathIds.getResourceId()).isMultiInstances()) ?
  418 + instance.getResource(pathIds.getResourceId()).getValues().toString() :
  419 + instance.getResource(pathIds.getResourceId()).getValue().toString();
  420 + }
  421 + }
  422 + return resValue;
  423 + }
  424 +
  425 + /**
  426 + * Prepare Sent to Thigsboard callback - Attribute or Telemetry
  427 + *
  428 + * @param msg - JsonArray: [{name: value}]
  429 + * @param topicName - Api Attribute or Telemetry
  430 + * @param registrationId - Id of Registration LwM2M Client
  431 + */
  432 + public void updateParametersOnThingsboard(JsonElement msg, String topicName, String registrationId) {
  433 + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId);
  434 + if (sessionInfo != null) {
  435 + try {
  436 + if (topicName.equals(LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) {
  437 + PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
  438 + TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(-1, postAttributeMsg);
  439 + transportService.process(sessionInfo, postAttributeMsg, call);
  440 + } else if (topicName.equals(LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC)) {
  441 + PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
  442 + TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(-1, postTelemetryMsg);
  443 + transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(-1, call));
  444 + }
  445 + } catch (AdaptorException e) {
  446 + log.error("[{}] Failed to process publish msg [{}]", topicName, e);
  447 + log.info("[{}] Closing current session due to invalid publish", topicName);
  448 + }
  449 + } else {
  450 + log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registrationId, sessionInfo);
  451 + }
  452 + }
  453 +
  454 + /**
  455 + * Sent to Thingsboard Attribute || Telemetry
  456 + *
  457 + * @param msgId - always == -1
  458 + * @param msg - JsonObject: [{name: value}]
  459 + * @return - dummy
  460 + */
  461 + private <T> TransportServiceCallback<Void> getPubAckCallbackSentAttrTelemetry(final int msgId, final T msg) {
  462 + return new TransportServiceCallback<Void>() {
  463 + @Override
  464 + public void onSuccess(Void dummy) {
  465 + log.trace("Success to publish msg: {}, dummy: {}", msg, dummy);
  466 + }
  467 +
  468 + @Override
  469 + public void onError(Throwable e) {
  470 + log.trace("[{}] Failed to publish msg: {}", msg, e);
  471 + }
  472 + };
  473 + }
  474 +
  475 +
  476 + /**
  477 + * Start observe
  478 + * #1 - Analyze:
  479 + * #1.1 path in observe == (attribute or telemetry)
  480 + * #1.2 recourseValue notNull
  481 + * #2 Analyze after sent request (response):
  482 + * #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation
  483 + * #2.2 Next: ObservationListener.onResponse *
  484 + *
  485 + * @param lwServer - LeshanServer
  486 + * @param registration - Registration LwM2M Client
  487 + */
  488 + private void onSentObserveToClient(LeshanServer lwServer, Registration registration) {
  489 + if (lwServer.getObservationService().getObservations(registration).size() > 0) {
  490 + this.setCancelObservations(lwServer, registration);
  491 + }
  492 + UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid();
  493 + AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid);
  494 + attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> {
  495 + // #1.1
  496 + String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ?
  497 + p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ?
  498 + p.getAsString().toString() : null;
  499 + if (target != null) {
  500 + // #1.2
  501 + ResultIds pathIds = new ResultIds(target);
  502 + ModelObject modelObject = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getModelObjects().get(pathIds.getObjectId());
  503 + // #2
  504 + if (modelObject != null) {
  505 + if (getResourceValue(modelObject, pathIds) != null) {
  506 + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE,
  507 + null, null, null, null, this.context.getCtxServer().getTimeout());
  508 + }
  509 + }
  510 + }
  511 + });
  512 + }
  513 +
  514 + public void setCancelObservations(LeshanServer lwServer, Registration registration) {
  515 + if (registration != null) {
  516 + Set<Observation> observations = lwServer.getObservationService().getObservations(registration);
  517 + observations.forEach(observation -> {
  518 + this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString());
  519 + });
  520 + }
  521 + }
  522 +
  523 + /**
  524 + * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout());
  525 + * At server side this will not remove the observation from the observation store, to do it you need to use
  526 + * {@code ObservationService#cancelObservation()}
  527 + */
  528 + public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) {
  529 + CountDownLatch cancelLatch = new CountDownLatch(1);
  530 + lwServer.getObservationService().cancelObservations(registration, path);
  531 + cancelLatch.countDown();
  532 + try {
  533 + cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
  534 + } catch (InterruptedException e) {
  535 + }
  536 + }
  537 +
  538 + /**
  539 + * @param parameters - JsonArray postAttributeProfile/postTelemetryProfile
  540 + * @param path - recourse from postObserveProfile
  541 + * @return rez - true if path observe is in attribute/telemetry
  542 + */
  543 + private boolean getValidateObserve(JsonElement parameters, String path) {
  544 + AtomicBoolean rez = new AtomicBoolean(false);
  545 + if (parameters.isJsonArray()) {
  546 + parameters.getAsJsonArray().forEach(p -> {
  547 + if (p.getAsString().toString().equals(path)) rez.set(true);
  548 + }
  549 + );
  550 + } else if (parameters.isJsonObject()) {
  551 + rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString())
  552 + .filter(path::equals).findAny().orElse(null) != null);
  553 + }
  554 + return rez.get();
  555 + }
  556 +
  557 + /**
  558 + * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource
  559 + *
  560 + * @param registration - Registration LwM2M Client
  561 + * @param path - observe
  562 + * @param response - observe
  563 + */
  564 + @SneakyThrows
  565 + public void onObservationResponse(Registration registration, String path, ReadResponse response) {
  566 + if (response.getContent() != null) {
  567 + if (response.getContent() instanceof LwM2mObject) {
  568 + LwM2mObject content = (LwM2mObject) response.getContent();
  569 + String target = "/" + content.getId();
  570 + } else if (response.getContent() instanceof LwM2mObjectInstance) {
  571 + LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent();
  572 + } else if (response.getContent() instanceof LwM2mSingleResource) {
  573 + LwM2mSingleResource content = (LwM2mSingleResource) response.getContent();
  574 + this.onObservationSetResourcesValue(registration, content.getValue(), null, path);
  575 + } else if (response.getContent() instanceof LwM2mMultipleResource) {
  576 + LwM2mSingleResource content = (LwM2mSingleResource) response.getContent();
  577 + this.onObservationSetResourcesValue(registration, null, content.getValues(), path);
  578 + }
  579 + }
  580 + }
  581 +
  582 + /**
  583 + * Sending observe value of resources to thingsboard
  584 + * #1 Return old Resource from ModelObject
  585 + * #2 Create new Resource with value from observation
  586 + * #3 Create new Resources from old Resources
  587 + * #4 Update new Resources (replace old Resource on new Resource)
  588 + * #5 Remove old Instance from modelClient
  589 + * #6 Create new Instance with new Resources values
  590 + * #7 Update modelClient.getModelObjects(idObject) (replace old Instance on new Instance)
  591 + *
  592 + * @param registration - Registration LwM2M Client
  593 + * @param value - LwM2mSingleResource response.getContent()
  594 + * @param values - LwM2mSingleResource response.getContent()
  595 + * @param path - resource
  596 + */
  597 + private void onObservationSetResourcesValue(Registration registration, Object value, Map<Integer, ?> values, String path) {
  598 + ResultIds resultIds = new ResultIds(path);
  599 + // #1
  600 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId());
  601 + ModelObject modelObject = lwM2MClient.getModelObjects().get(resultIds.getObjectId());
  602 + Map<Integer, LwM2mObjectInstance> instancesModelObject = modelObject.getInstances();
  603 + LwM2mObjectInstance instanceOld = (instancesModelObject.get(resultIds.instanceId) != null) ? instancesModelObject.get(resultIds.instanceId) : null;
  604 + Map<Integer, LwM2mResource> resourcesOld = (instanceOld != null) ? instanceOld.getResources() : null;
  605 + LwM2mResource resourceOld = (resourcesOld != null && resourcesOld.get(resultIds.getResourceId()) != null) ? resourcesOld.get(resultIds.getResourceId()) : null;
  606 + // #2
  607 + LwM2mResource resourceNew;
  608 + if (resourceOld.isMultiInstances()) {
  609 + resourceNew = LwM2mMultipleResource.newResource(resultIds.getResourceId(), values, resourceOld.getType());
  610 + } else {
  611 + resourceNew = LwM2mSingleResource.newResource(resultIds.getResourceId(), value, resourceOld.getType());
  612 + }
  613 + //#3
  614 + Map<Integer, LwM2mResource> resourcesNew = new HashMap<>(resourcesOld);
  615 + // #4
  616 + resourcesNew.remove(resourceOld);
  617 + // #5
  618 + resourcesNew.put(resultIds.getResourceId(), resourceNew);
  619 + // #6
  620 + LwM2mObjectInstance instanceNew = new LwM2mObjectInstance(resultIds.instanceId, resourcesNew.values());
  621 + // #7
  622 + CountDownLatch respLatch = new CountDownLatch(1);
  623 + lwM2MClient.getModelObjects().get(resultIds.getObjectId()).removeInstance(resultIds.instanceId);
  624 + instancesModelObject.put(resultIds.instanceId, instanceNew);
  625 + respLatch.countDown();
  626 + try {
  627 + respLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
  628 + } catch (InterruptedException ex) {
  629 + }
  630 + Set<String> paths = new HashSet<String>();
  631 + paths.add(path);
  632 + this.updateAttrTelemetry(registration, false, paths);
  633 + }
  634 +
  635 + /**
  636 + * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...)
  637 + * config attr/telemetry... in profile
  638 + */
  639 + public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) {
  640 + log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList());
  641 + }
  642 +
  643 + /**
  644 + * Update - sent request in change value resources in Client (path to resources from profile by keyName)
  645 + * Only fo resources W
  646 + * Delete - nothing
  647 + *
  648 + * @param msg -
  649 + * @param sessionInfo -
  650 + */
  651 + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, SessionInfoProto sessionInfo) {
  652 + if (msg.getSharedUpdatedCount() > 0) {
  653 + JsonElement el = JsonConverter.toJson(msg);
  654 + el.getAsJsonObject().entrySet().forEach(de -> {
  655 + String profilePath = lwM2mInMemorySecurityStore.getProfiles().get(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()))
  656 + .getPostKeyNameProfile().getAsJsonObject().entrySet().stream()
  657 + .filter(e -> e.getValue().getAsString().equals(de.getKey())).findFirst().map(Map.Entry::getKey)
  658 + .orElse("");
  659 + String path = !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdate(sessionInfo, de.getKey());
  660 + if (path != null) {
  661 + ResultIds resultIds = new ResultIds(path);
  662 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue();
  663 + ResourceModel.Operations operations = lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getObjectModel().resources.get(resultIds.getResourceId()).operations;
  664 + String value = ((JsonPrimitive) de.getValue()).getAsString();
  665 + if (operations.isWritable()) {
  666 + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE,
  667 + ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout());
  668 + log.info("[{}] path onAttributeUpdate", path);
  669 + }
  670 + else {
  671 + log.error(LOG_LW2M_ERROR + ": Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value);
  672 + String logMsg = String.format(LOG_LW2M_ERROR + " attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value);
  673 + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId());
  674 + }
  675 + }
  676 + });
  677 + } else if (msg.getSharedDeletedCount() > 0) {
  678 + log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
  679 + }
  680 + }
  681 +
  682 + private String getPathAttributeUpdate(SessionInfoProto sessionInfo, String keyName) {
  683 + try {
  684 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue();
  685 + Predicate<Map.Entry<Integer, ResourceModel>> predicateRes = res -> keyName.equals(splitCamelCaseString(res.getValue().name));
  686 + Predicate<Map.Entry<Integer, ModelObject>> predicateObj = (obj -> {
  687 + return obj.getValue().getObjectModel().resources.entrySet().stream().filter(predicateRes).findFirst().isPresent();
  688 + });
  689 + Stream<Map.Entry<Integer, ModelObject>> objectStream = lwM2MClient.getModelObjects().entrySet().stream().filter(predicateObj);
  690 + Predicate<Map.Entry<Integer, ResourceModel>> predicateResFinal = (objectStream.count() > 0) ? predicateRes : res -> keyName.equals(res.getValue().name);
  691 + Predicate<Map.Entry<Integer, ModelObject>> predicateObjFinal = (obj -> {
  692 + return obj.getValue().getObjectModel().resources.entrySet().stream().filter(predicateResFinal).findFirst().isPresent();
  693 + });
  694 + Map.Entry<Integer, ModelObject> object = lwM2MClient.getModelObjects().entrySet().stream().filter(predicateObjFinal).findFirst().get();
  695 + ModelObject modelObject = object.getValue();
  696 + LwM2mObjectInstance instance = modelObject.getInstances().entrySet().stream().findFirst().get().getValue();
  697 + ResourceModel resource = modelObject.getObjectModel().resources.entrySet().stream().filter(predicateResFinal).findFirst().get().getValue();
  698 + return new LwM2mPath(object.getKey(), instance.getId(), resource.id).toString();
  699 + } catch (NoSuchElementException e) {
  700 + log.error("[{}] keyName [{}]", keyName, e.toString());
  701 + return null;
  702 + }
  703 + }
  704 +
  705 + public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request) {
  706 + ResultIds resultIds = new ResultIds(path);
  707 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId());
  708 + LwM2mResource resource = lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getInstances().get(resultIds.getInstanceId()).getResource(resultIds.getResourceId());
  709 + if (resource.isMultiInstances()) {
  710 + this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path);
  711 + } else {
  712 + this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path);
  713 + }
  714 + }
  715 +
  716 + /**
  717 + * @param sessionInfo -
  718 + * @param deviceProfile -
  719 + */
  720 + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  721 + String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet()
  722 + .stream()
  723 + .filter(e -> e.getValue().getDeviceUuid().equals(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())))
  724 + .findFirst()
  725 + .map(Map.Entry::getKey)
  726 + .orElse("");
  727 + if (!registrationId.isEmpty()) {
  728 + this.onDeviceUpdateChangeProfile(registrationId, deviceProfile);
  729 + }
  730 + }
  731 +
  732 + /**
  733 + * @param sessionInfo -
  734 + * @param device -
  735 + * @param deviceProfileOpt -
  736 + */
  737 + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
  738 + Optional<String> registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream()
  739 + .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid()))
  740 + .map(Map.Entry::getKey)
  741 + .findFirst();
  742 + registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt));
  743 + }
  744 +
  745 + /**
  746 + * Update parameters device in LwM2MClient
  747 + * If new deviceProfile != old deviceProfile => update deviceProfile
  748 + *
  749 + * @param registrationId -
  750 + * @param device -
  751 + */
  752 + private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional<DeviceProfile> deviceProfileOpt) {
  753 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId);
  754 + lwM2MClient.setDeviceName(device.getName());
  755 + if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) {
  756 + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationId, deviceProfile));
  757 + }
  758 + }
  759 +
  760 + /**
  761 + * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName)
  762 + * #2 Update in lwM2MClient: ...Profile
  763 + * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName)
  764 + * #3.1 Attribute isChange (add&del)
  765 + * #3.2 Telemetry isChange (add&del)
  766 + * #3.3 KeyName isChange (add)
  767 + * #4 update
  768 + * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and sent Value ti thingsboard
  769 + * #4.2 del
  770 + * -- if add attributes includes del telemetry - result del for observe
  771 + * #5
  772 + * #5.1 Observe isChange (add&del)
  773 + * #5.2 Observe.add
  774 + * -- path Attr/Telemetry includes newObserve and does not include oldObserve: sent Request observe to Client
  775 + * #5.3 Observe.del
  776 + * -- different between newObserve and oldObserve: sent Request cancel observe to client
  777 + *
  778 + * @param registrationId -
  779 + * @param deviceProfile -
  780 + */
  781 + public void onDeviceUpdateChangeProfile(String registrationId, DeviceProfile deviceProfile) {
  782 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registrationId);
  783 + AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid());
  784 + if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) {
  785 + LeshanServer lwServer = lwM2MClient.getLwServer();
  786 + Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId);
  787 + // #1
  788 + JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile();
  789 + Set<String> attributeSetOld = new Gson().fromJson(attributeOld, Set.class);
  790 + JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile();
  791 + Set<String> telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class);
  792 + JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile();
  793 + JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile();
  794 +
  795 + AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId());
  796 + JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile();
  797 + Set<String> attributeSetNew = new Gson().fromJson(attributeNew, Set.class);
  798 + JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile();
  799 + Set<String> telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class);
  800 + JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile();
  801 + JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile();
  802 + // #2
  803 + lwM2MClient.setDeviceProfileName(deviceProfile.getName());
  804 + lwM2MClient.setProfileUuid(deviceProfile.getUuidId());
  805 +
  806 + // #3
  807 + ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters();
  808 + // #3.1
  809 + if (!attributeOld.equals(attributeNew)) {
  810 + ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew);
  811 + sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
  812 + sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
  813 + }
  814 + // #3.2
  815 + if (!attributeOld.equals(attributeNew)) {
  816 + ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew);
  817 + sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
  818 + sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
  819 + }
  820 + // #3.3
  821 + if (!keyNameOld.equals(keyNameNew)) {
  822 + ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class),
  823 + new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class));
  824 + sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
  825 + }
  826 +
  827 + // #4.1 add
  828 + if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
  829 + // update value in Resources
  830 + this.updateResourceValueObserve(lwServer, registration, lwM2MClient, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ);
  831 + // sent attr/telemetry to tingsboard for new path
  832 + this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd());
  833 + }
  834 + // #4.2 del
  835 + if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) {
  836 + ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel());
  837 + sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel());
  838 + }
  839 +
  840 + // #5.1
  841 + if (!observeOld.equals(observeNew)) {
  842 + Set<String> observeSetOld = new Gson().fromJson(observeOld, Set.class);
  843 + Set<String> observeSetNew = new Gson().fromJson(observeNew, Set.class);
  844 + //#5.2 add
  845 + // path Attr/Telemetry includes newObserve
  846 + attributeSetOld.addAll(telemetrySetOld);
  847 + ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe
  848 + attributeSetNew.addAll(telemetrySetNew);
  849 + ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe
  850 + // does not include oldObserve
  851 + ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd());
  852 + // sent Request observe to Client
  853 + this.updateResourceValueObserve(lwServer, registration, lwM2MClient, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE);
  854 + // 5.3 del
  855 + // sent Request cancel observe to Client
  856 + this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel());
  857 + }
  858 + }
  859 + }
  860 +
  861 + /**
  862 + * Compare old list with new list after change AttrTelemetryObserve in config Profile
  863 + *
  864 + * @param parametersOld -
  865 + * @param parametersNew -
  866 + * @return ResultsAnalyzerParameters: add && new
  867 + */
  868 + private ResultsAnalyzerParameters getAnalyzerParameters(Set<String> parametersOld, Set<String> parametersNew) {
  869 + ResultsAnalyzerParameters analyzerParameters = null;
  870 + if (!parametersOld.equals(parametersNew)) {
  871 + analyzerParameters = new ResultsAnalyzerParameters();
  872 + analyzerParameters.setPathPostParametersAdd(parametersNew
  873 + .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet()));
  874 + analyzerParameters.setPathPostParametersDel(parametersOld
  875 + .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet()));
  876 + }
  877 + return analyzerParameters;
  878 + }
  879 +
  880 + private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap<String, String> keyNameOld, ConcurrentMap<String, String> keyNameNew) {
  881 + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
  882 + Set<String> paths = keyNameNew.entrySet()
  883 + .stream()
  884 + .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey())))
  885 + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())).keySet();
  886 + analyzerParameters.setPathPostParametersAdd(paths);
  887 + return analyzerParameters;
  888 + }
  889 +
  890 + private ResultsAnalyzerParameters getAnalyzerParametersIn(Set<String> parametersObserve, Set<String> parameters) {
  891 + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
  892 + analyzerParameters.setPathPostParametersAdd(parametersObserve
  893 + .stream().filter(p -> parameters.contains(p)).collect(Collectors.toSet()));
  894 + return analyzerParameters;
  895 + }
  896 +
  897 + /**
  898 + * Update Resource value after change RezAttrTelemetry in config Profile
  899 + * sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue()
  900 + *
  901 + * @param lwServer - LeshanServer
  902 + * @param registration - Registration LwM2M Client
  903 + * @param lwM2MClient - object with All parameters off client
  904 + * @param targets - path Resources == [ "/2/0/0", "/2/0/1"]
  905 + */
  906 + private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient, Set<String> targets, String typeOper) {
  907 + targets.stream().forEach(target -> {
  908 + ResultIds pathIds = new ResultIds(target);
  909 + if (pathIds.resourceId >= 0 && lwM2MClient.getModelObjects().get(pathIds.getObjectId())
  910 + .getInstances().get(pathIds.getInstanceId()).getResource(pathIds.getResourceId()).getValue() != null) {
  911 + if (GET_TYPE_OPER_READ.equals(typeOper)) {
  912 + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper,
  913 + ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout());
  914 + } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
  915 + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper,
  916 + null, null, null, null, this.context.getCtxServer().getTimeout());
  917 + }
  918 + }
  919 + });
  920 + }
  921 +
  922 + private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set<String> paramAnallyzer) {
  923 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId());
  924 + paramAnallyzer.forEach(p -> {
  925 + if (this.getResourceValue(lwM2MClient, p) != null) {
  926 + this.setCancelObservationRecourse(lwServer, registration, p);
  927 + }
  928 + }
  929 + );
  930 + }
  931 +
  932 + private ResourceValue getResourceValue(LwM2MClient lwM2MClient, String path) {
  933 + ResourceValue resourceValue = null;
  934 + ResultIds pathIds = new ResultIds(path);
  935 + if (pathIds.getResourceId() > -1) {
  936 + LwM2mResource resource = lwM2MClient.getModelObjects().get(pathIds.getObjectId()).getInstances().get(pathIds.getInstanceId()).getResource(pathIds.getResourceId());
  937 + if (resource.isMultiInstances()) {
  938 + Map<Integer, ?> values = resource.getValues();
  939 + if (resource.getValues().size() > 0) {
  940 + resourceValue = new ResourceValue();
  941 + resourceValue.setMultiInstances(resource.isMultiInstances());
  942 + resourceValue.setValues(resource.getValues());
  943 + }
  944 + } else {
  945 + if (resource.getValue() != null) {
  946 + resourceValue = new ResourceValue();
  947 + resourceValue.setMultiInstances(resource.isMultiInstances());
  948 + resourceValue.setValue(resource.getValue());
  949 + }
  950 + }
  951 + }
  952 + return resourceValue;
  953 + }
  954 +
  955 + /**
  956 + * Trigger Server path = "/1/0/8"
  957 + *
  958 + * Trigger bootStrap path = "/1/0/9" - have to implemented on client
  959 + */
  960 + public void doTrigger(LeshanServer lwServer, Registration registration, String path) {
  961 + lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE,
  962 + ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout());
  963 + }
  964 +
  965 + /**
  966 + * Session device in thingsboard is closed
  967 + *
  968 + * @param sessionInfo - lwm2m client
  969 + */
  970 + private void doCloseSession(SessionInfoProto sessionInfo) {
  971 + TransportProtos.SessionEvent event = SessionEvent.CLOSED;
  972 + TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder()
  973 + .setSessionType(TransportProtos.SessionType.ASYNC)
  974 + .setEvent(event).build();
  975 + transportService.process(sessionInfo, msg, null);
  976 + }
  977 +
  978 + /**
  979 + * Deregister session in transport
  980 + * @param sessionInfo - lwm2m client
  981 + */
  982 + private void doDisconnect(SessionInfoProto sessionInfo) {
  983 + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null);
  984 + transportService.deregisterSession(sessionInfo);
  985 + }
  986 +
  987 + private void checkInactivityAndReportActivity() {
  988 + lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> transportService.reportActivity(this.getValidateSessionInfo(key)));
  989 + }
  990 +
  991 + public void sentLogsToThingsboard(String msg, String registrationId) {
  992 + if (msg != null) {
  993 + JsonObject telemetrys = new JsonObject();
  994 + telemetrys.addProperty(LOG_LW2M_TELEMETRY, msg);
  995 + this.updateParametersOnThingsboard(telemetrys, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registrationId);
  996 + }
  997 + }
  998 +
  999 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.core.observation.Observation;
  20 +import org.eclipse.leshan.core.response.ObserveResponse;
  21 +import org.eclipse.leshan.server.californium.LeshanServer;
  22 +import org.eclipse.leshan.server.observation.ObservationListener;
  23 +import org.eclipse.leshan.server.queue.PresenceListener;
  24 +import org.eclipse.leshan.server.registration.Registration;
  25 +import org.eclipse.leshan.server.registration.RegistrationListener;
  26 +import org.eclipse.leshan.server.registration.RegistrationUpdate;
  27 +
  28 +import java.util.Collection;
  29 +
  30 +@Slf4j
  31 +public class LwM2mServerListener {
  32 + private LeshanServer lhServer;
  33 + private LwM2MTransportService service;
  34 +
  35 + public LwM2mServerListener(LeshanServer lhServer, LwM2MTransportService service) {
  36 + this.lhServer = lhServer;
  37 + this.service = service;
  38 + }
  39 +
  40 + public final RegistrationListener registrationListener = new RegistrationListener() {
  41 + /**
  42 + * Register – запрос, представленный в виде POST /rd?…
  43 + */
  44 + @Override
  45 + public void registered(Registration registration, Registration previousReg,
  46 + Collection<Observation> previousObsersations) {
  47 +
  48 + service.onRegistered(lhServer, registration, previousObsersations);
  49 + }
  50 +
  51 + /**
  52 + * Update – представляет из себя CoAP POST запрос на URL, полученный в ответ на Register.
  53 + */
  54 + @Override
  55 + public void updated(RegistrationUpdate update, Registration updatedRegistration,
  56 + Registration previousRegistration) {
  57 + service.updatedReg(lhServer, updatedRegistration);
  58 + }
  59 +
  60 + /**
  61 + * De-register (CoAP DELETE) – отправляется клиентом в случае инициирования процедуры выключения.
  62 + */
  63 + @Override
  64 + public void unregistered(Registration registration, Collection<Observation> observations, boolean expired,
  65 + Registration newReg) {
  66 + service.unReg(registration, observations);
  67 + }
  68 +
  69 + };
  70 +
  71 + public final PresenceListener presenceListener = new PresenceListener() {
  72 + @Override
  73 + public void onSleeping(Registration registration) {
  74 + service.onSleepingDev(registration);
  75 + }
  76 +
  77 + @Override
  78 + public void onAwake(Registration registration) {
  79 + service.onAwakeDev(registration);
  80 + }
  81 + };
  82 +
  83 + public final ObservationListener observationListener = new ObservationListener() {
  84 +
  85 + @Override
  86 + public void cancelled(Observation observation) {
  87 + log.info("Received notification cancelled from [{}] ", observation.getPath());
  88 + }
  89 +
  90 + @Override
  91 + public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
  92 + if (registration != null) {
  93 + try {
  94 + service.onObservationResponse(registration, observation.getPath().toString(), response);
  95 + } catch (java.lang.NullPointerException e) {
  96 + log.error(e.toString());
  97 + }
  98 + }
  99 + }
  100 +
  101 + @Override
  102 + public void onError(Observation observation, Registration registration, Exception error) {
  103 + log.error(String.format("Unable to handle notification of [%s:%s]", observation.getRegistrationId(), observation.getPath()), error);
  104 + }
  105 +
  106 + @Override
  107 + public void newObservation(Observation observation, Registration registration) {
  108 + log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint());
  109 + }
  110 + };
  111 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +
  21 +@Data
  22 +public class ResultIds {
  23 + @Builder.Default
  24 + int objectId = -1;
  25 + @Builder.Default
  26 + int instanceId = -1;
  27 + @Builder.Default
  28 + int resourceId = -1;
  29 +
  30 + public ResultIds (String path) {
  31 + String[] paths = path.split("/");
  32 + if (paths != null && paths.length > 1) {
  33 + this.objectId = (paths.length > 1) ? Integer.parseInt(paths[1]) : this.objectId;
  34 + this.instanceId = (paths.length > 2) ? Integer.parseInt(paths[2]) : this.instanceId;
  35 + this.resourceId = (paths.length > 3) ? Integer.parseInt(paths[3]) : this.resourceId;
  36 + }
  37 + }
  38 +}
... ...