Commit 7667afaeae02ab3dcc6a8bb389ad6f0ab14cb441
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,6 +86,10 @@ | ||
86 | <artifactId>coap</artifactId> | 86 | <artifactId>coap</artifactId> |
87 | </dependency> | 87 | </dependency> |
88 | <dependency> | 88 | <dependency> |
89 | + <groupId>org.thingsboard.common.transport</groupId> | ||
90 | + <artifactId>lwm2m</artifactId> | ||
91 | + </dependency> | ||
92 | + <dependency> | ||
89 | <groupId>org.thingsboard</groupId> | 93 | <groupId>org.thingsboard</groupId> |
90 | <artifactId>dao</artifactId> | 94 | <artifactId>dao</artifactId> |
91 | </dependency> | 95 | </dependency> |
@@ -62,7 +62,7 @@ public class DeviceActor extends ContextAwareActor { | @@ -62,7 +62,7 @@ public class DeviceActor extends ContextAwareActor { | ||
62 | processor.processAttributesUpdate(ctx, (DeviceAttributesEventNotificationMsg) msg); | 62 | processor.processAttributesUpdate(ctx, (DeviceAttributesEventNotificationMsg) msg); |
63 | break; | 63 | break; |
64 | case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: | 64 | case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: |
65 | - processor.processCredentialsUpdate(); | 65 | + processor.processCredentialsUpdate(msg); |
66 | break; | 66 | break; |
67 | case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: | 67 | case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: |
68 | processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg); | 68 | processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg); |
@@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; | ||
24 | import org.apache.commons.collections.CollectionUtils; | 24 | import org.apache.commons.collections.CollectionUtils; |
25 | import org.thingsboard.rule.engine.api.RpcError; | 25 | import org.thingsboard.rule.engine.api.RpcError; |
26 | import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; | 26 | import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; |
27 | +import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; | ||
27 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; | 28 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; |
28 | import org.thingsboard.server.actors.ActorSystemContext; | 29 | import org.thingsboard.server.actors.ActorSystemContext; |
29 | import org.thingsboard.server.actors.TbActorCtx; | 30 | import org.thingsboard.server.actors.TbActorCtx; |
@@ -36,6 +37,9 @@ import org.thingsboard.server.common.data.kv.AttributeKey; | @@ -36,6 +37,9 @@ import org.thingsboard.server.common.data.kv.AttributeKey; | ||
36 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; | 37 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
37 | import org.thingsboard.server.common.data.kv.KvEntry; | 38 | import org.thingsboard.server.common.data.kv.KvEntry; |
38 | import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; | 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 | import org.thingsboard.server.common.msg.TbMsgMetaData; | 43 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
40 | import org.thingsboard.server.common.msg.queue.TbCallback; | 44 | import org.thingsboard.server.common.msg.queue.TbCallback; |
41 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; | 45 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
@@ -61,6 +65,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM | @@ -61,6 +65,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM | ||
61 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; | 65 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; |
62 | import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; | 66 | import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; |
63 | import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; | 67 | import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; |
68 | +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; | ||
64 | import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; | 69 | import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; |
65 | import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; | 70 | import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
66 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; | 71 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; |
@@ -450,11 +455,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -450,11 +455,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
450 | dumpSessions(); | 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 | private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) { | 473 | private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) { |
@@ -465,6 +478,18 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -465,6 +478,18 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
465 | systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg); | 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 | void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { | 493 | void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { |
469 | this.deviceName = msg.getDeviceName(); | 494 | this.deviceName = msg.getDeviceName(); |
470 | this.deviceType = msg.getDeviceType(); | 495 | this.deviceType = msg.getDeviceType(); |
@@ -92,6 +92,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; | @@ -92,6 +92,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; | ||
92 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; | 92 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
93 | import org.thingsboard.server.queue.util.TbCoreComponent; | 93 | import org.thingsboard.server.queue.util.TbCoreComponent; |
94 | import org.thingsboard.server.service.component.ComponentDiscoveryService; | 94 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
95 | +import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; | ||
95 | import org.thingsboard.server.service.profile.TbDeviceProfileCache; | 96 | import org.thingsboard.server.service.profile.TbDeviceProfileCache; |
96 | import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | 97 | import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
97 | import org.thingsboard.server.service.queue.TbClusterService; | 98 | import org.thingsboard.server.service.queue.TbClusterService; |
@@ -211,6 +212,9 @@ public abstract class BaseController { | @@ -211,6 +212,9 @@ public abstract class BaseController { | ||
211 | @Autowired | 212 | @Autowired |
212 | protected TbDeviceProfileCache deviceProfileCache; | 213 | protected TbDeviceProfileCache deviceProfileCache; |
213 | 214 | ||
215 | + @Autowired | ||
216 | + protected LwM2MModelsRepository lwM2MModelsRepository; | ||
217 | + | ||
214 | @Value("${server.log_controller_error_stack_trace}") | 218 | @Value("${server.log_controller_error_stack_trace}") |
215 | @Getter | 219 | @Getter |
216 | private boolean logControllerErrorStackTrace; | 220 | private boolean logControllerErrorStackTrace; |
@@ -278,9 +278,8 @@ public class DeviceController extends BaseController { | @@ -278,9 +278,8 @@ public class DeviceController extends BaseController { | ||
278 | try { | 278 | try { |
279 | Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); | 279 | Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); |
280 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials)); | 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 | logEntityAction(device.getId(), device, | 283 | logEntityAction(device.getId(), device, |
285 | device.getCustomerId(), | 284 | device.getCustomerId(), |
286 | ActionType.CREDENTIALS_UPDATED, null, deviceCredentials); | 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 | +} |
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java
0 → 100644
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,6 +53,8 @@ public class DefaultDeviceAuthService implements DeviceAuthService { | ||
53 | return DeviceAuthResult.of(credentials.getDeviceId()); | 53 | return DeviceAuthResult.of(credentials.getDeviceId()); |
54 | case X509_CERTIFICATE: | 54 | case X509_CERTIFICATE: |
55 | return DeviceAuthResult.of(credentials.getDeviceId()); | 55 | return DeviceAuthResult.of(credentials.getDeviceId()); |
56 | + case LWM2M_CREDENTIALS: | ||
57 | + return DeviceAuthResult.of(credentials.getDeviceId()); | ||
56 | default: | 58 | default: |
57 | return DeviceAuthResult.of("Credentials Type is not supported yet!"); | 59 | return DeviceAuthResult.of("Credentials Type is not supported yet!"); |
58 | } | 60 | } |
@@ -65,4 +67,4 @@ public class DefaultDeviceAuthService implements DeviceAuthService { | @@ -65,4 +67,4 @@ public class DefaultDeviceAuthService implements DeviceAuthService { | ||
65 | } | 67 | } |
66 | } | 68 | } |
67 | 69 | ||
68 | -} | ||
70 | +} |
@@ -201,6 +201,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer | @@ -201,6 +201,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer | ||
201 | } | 201 | } |
202 | 202 | ||
203 | @Override | 203 | @Override |
204 | + | ||
204 | public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) { | 205 | public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) { |
205 | saveAndNotify(tenantId, entityId, scope, attributes, true, callback); | 206 | saveAndNotify(tenantId, entityId, scope, attributes, true, callback); |
206 | } | 207 | } |
@@ -69,6 +69,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponse | @@ -69,6 +69,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponse | ||
69 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; | 69 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; |
70 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; | 70 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; |
71 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; | 71 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; |
72 | +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; | ||
72 | import org.thingsboard.server.queue.common.TbProtoQueueMsg; | 73 | import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
73 | import org.thingsboard.server.queue.util.TbCoreComponent; | 74 | import org.thingsboard.server.queue.util.TbCoreComponent; |
74 | import org.thingsboard.server.dao.device.provision.ProvisionFailedException; | 75 | import org.thingsboard.server.dao.device.provision.ProvisionFailedException; |
@@ -149,6 +150,13 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -149,6 +150,13 @@ public class DefaultTransportApiService implements TransportApiService { | ||
149 | } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { | 150 | } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { |
150 | return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()), | 151 | return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()), |
151 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); | 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 | } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { | 160 | } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { |
153 | return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), | 161 | return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), |
154 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); | 162 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); |
@@ -396,4 +404,40 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -396,4 +404,40 @@ public class DefaultTransportApiService implements TransportApiService { | ||
396 | return TransportApiResponseMsg.newBuilder() | 404 | return TransportApiResponseMsg.newBuilder() |
397 | .setValidateCredResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build(); | 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 | :: ${application.title} :: ${application.formatted-version} | 9 | :: ${application.title} :: ${application.formatted-version} |
3 | =================================================== | 10 | =================================================== |
@@ -433,7 +433,7 @@ spring: | @@ -433,7 +433,7 @@ spring: | ||
433 | database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" | 433 | database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" |
434 | datasource: | 434 | datasource: |
435 | driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" | 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 | username: "${SPRING_DATASOURCE_USERNAME:postgres}" | 437 | username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
438 | password: "${SPRING_DATASOURCE_PASSWORD:postgres}" | 438 | password: "${SPRING_DATASOURCE_PASSWORD:postgres}" |
439 | hikari: | 439 | hikari: |
@@ -488,7 +488,7 @@ js: | @@ -488,7 +488,7 @@ js: | ||
488 | # Built-in JVM JavaScript environment properties | 488 | # Built-in JVM JavaScript environment properties |
489 | local: | 489 | local: |
490 | # Use Sandboxed (secured) JVM JavaScript environment | 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 | # Specify thread pool size for JavaScript sandbox resource monitor | 492 | # Specify thread pool size for JavaScript sandbox resource monitor |
493 | monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}" | 493 | monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}" |
494 | # Maximum CPU time in milliseconds allowed for script execution | 494 | # Maximum CPU time in milliseconds allowed for script execution |
@@ -565,6 +565,74 @@ transport: | @@ -565,6 +565,74 @@ transport: | ||
565 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" | 565 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" |
566 | bind_port: "${COAP_BIND_PORT:5683}" | 566 | bind_port: "${COAP_BIND_PORT:5683}" |
567 | timeout: "${COAP_TIMEOUT:10000}" | 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 | swagger: | 637 | swagger: |
570 | api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" | 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 | +} |
common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java
0 → 100644
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 | +} |
@@ -183,6 +183,47 @@ message GetEntityProfileRequestMsg { | @@ -183,6 +183,47 @@ message GetEntityProfileRequestMsg { | ||
183 | int64 entityIdLSB = 3; | 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 | message GetEntityProfileResponseMsg { | 227 | message GetEntityProfileResponseMsg { |
187 | string entityType = 1; | 228 | string entityType = 1; |
188 | bytes data = 2; | 229 | bytes data = 2; |
@@ -479,8 +520,10 @@ message TransportApiRequestMsg { | @@ -479,8 +520,10 @@ message TransportApiRequestMsg { | ||
479 | ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; | 520 | ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; |
480 | GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; | 521 | GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; |
481 | GetEntityProfileRequestMsg entityProfileRequestMsg = 4; | 522 | GetEntityProfileRequestMsg entityProfileRequestMsg = 4; |
523 | + LwM2MRequestMsg lwM2MRequestMsg = 5; | ||
482 | ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; | 524 | ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; |
483 | ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; | 525 | ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; |
526 | + ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; | ||
484 | } | 527 | } |
485 | 528 | ||
486 | /* Response from ThingsBoard Core Service to Transport Service */ | 529 | /* Response from ThingsBoard Core Service to Transport Service */ |
@@ -489,6 +532,7 @@ message TransportApiResponseMsg { | @@ -489,6 +532,7 @@ message TransportApiResponseMsg { | ||
489 | GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; | 532 | GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; |
490 | GetEntityProfileResponseMsg entityProfileResponseMsg = 3; | 533 | GetEntityProfileResponseMsg entityProfileResponseMsg = 3; |
491 | ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; | 534 | ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; |
535 | + LwM2MResponseMsg lwM2MResponseMsg = 6; | ||
492 | } | 536 | } |
493 | 537 | ||
494 | /* Messages that are handled by ThingsBoard Core Service */ | 538 | /* Messages that are handled by ThingsBoard Core Service */ |
@@ -529,10 +573,10 @@ message ToTransportMsg { | @@ -529,10 +573,10 @@ message ToTransportMsg { | ||
529 | AttributeUpdateNotificationMsg attributeUpdateNotification = 5; | 573 | AttributeUpdateNotificationMsg attributeUpdateNotification = 5; |
530 | ToDeviceRpcRequestMsg toDeviceRequest = 6; | 574 | ToDeviceRpcRequestMsg toDeviceRequest = 6; |
531 | ToServerRpcResponseMsg toServerResponse = 7; | 575 | ToServerRpcResponseMsg toServerResponse = 7; |
532 | - /* For Tenant, TenantProfile and DeviceProfile */ | ||
533 | EntityUpdateMsg entityUpdateMsg = 8; | 576 | EntityUpdateMsg entityUpdateMsg = 8; |
534 | EntityDeleteMsg entityDeleteMsg = 9; | 577 | EntityDeleteMsg entityDeleteMsg = 9; |
535 | ProvisionDeviceResponseMsg provisionResponse = 10; | 578 | ProvisionDeviceResponseMsg provisionResponse = 10; |
579 | + ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11; | ||
536 | } | 580 | } |
537 | 581 | ||
538 | message UsageStatsKVProto{ | 582 | message UsageStatsKVProto{ |
@@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; | @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; | ||
24 | import org.thingsboard.server.common.transport.TransportContext; | 24 | import org.thingsboard.server.common.transport.TransportContext; |
25 | import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; | 25 | import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; |
26 | 26 | ||
27 | + | ||
27 | /** | 28 | /** |
28 | * Created by ashvayka on 18.10.18. | 29 | * Created by ashvayka on 18.10.18. |
29 | */ | 30 | */ |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -16,14 +16,15 @@ | @@ -16,14 +16,15 @@ | ||
16 | package org.thingsboard.server.transport.coap; | 16 | package org.thingsboard.server.transport.coap; |
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | +import org.eclipse.californium.core.CoapObserveRelation; | ||
19 | import org.eclipse.californium.core.CoapResource; | 20 | import org.eclipse.californium.core.CoapResource; |
21 | +import org.eclipse.californium.core.coap.CoAP; | ||
20 | import org.eclipse.californium.core.coap.CoAP.ResponseCode; | 22 | import org.eclipse.californium.core.coap.CoAP.ResponseCode; |
21 | import org.eclipse.californium.core.coap.Request; | 23 | import org.eclipse.californium.core.coap.Request; |
24 | +import org.eclipse.californium.core.network.Endpoint; | ||
22 | import org.eclipse.californium.core.network.Exchange; | 25 | import org.eclipse.californium.core.network.Exchange; |
23 | -import org.eclipse.californium.core.network.ExchangeObserver; | ||
24 | import org.eclipse.californium.core.server.resources.CoapExchange; | 26 | import org.eclipse.californium.core.server.resources.CoapExchange; |
25 | import org.eclipse.californium.core.server.resources.Resource; | 27 | import org.eclipse.californium.core.server.resources.Resource; |
26 | -import org.springframework.util.ReflectionUtils; | ||
27 | import org.thingsboard.server.common.data.DataConstants; | 28 | import org.thingsboard.server.common.data.DataConstants; |
28 | import org.thingsboard.server.common.data.DeviceTransportType; | 29 | import org.thingsboard.server.common.data.DeviceTransportType; |
29 | import org.thingsboard.server.common.data.security.DeviceTokenCredentials; | 30 | import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
@@ -40,26 +41,26 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes | @@ -40,26 +41,26 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes | ||
40 | import org.thingsboard.server.gen.transport.TransportProtos; | 41 | import org.thingsboard.server.gen.transport.TransportProtos; |
41 | import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; | 42 | import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; |
42 | 43 | ||
43 | -import java.lang.reflect.Field; | ||
44 | import java.util.List; | 44 | import java.util.List; |
45 | import java.util.Optional; | 45 | import java.util.Optional; |
46 | import java.util.Set; | 46 | import java.util.Set; |
47 | import java.util.UUID; | 47 | import java.util.UUID; |
48 | +import java.util.Timer; | ||
49 | +import java.util.TimerTask; | ||
48 | import java.util.concurrent.ConcurrentHashMap; | 50 | import java.util.concurrent.ConcurrentHashMap; |
49 | import java.util.concurrent.ConcurrentMap; | 51 | import java.util.concurrent.ConcurrentMap; |
52 | +import java.util.concurrent.ScheduledThreadPoolExecutor; | ||
50 | import java.util.concurrent.atomic.AtomicInteger; | 53 | import java.util.concurrent.atomic.AtomicInteger; |
51 | import java.util.function.Consumer; | 54 | import java.util.function.Consumer; |
52 | 55 | ||
53 | @Slf4j | 56 | @Slf4j |
54 | public class CoapTransportResource extends CoapResource { | 57 | public class CoapTransportResource extends CoapResource { |
55 | - // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]] | ||
56 | private static final int ACCESS_TOKEN_POSITION = 3; | 58 | private static final int ACCESS_TOKEN_POSITION = 3; |
57 | private static final int FEATURE_TYPE_POSITION = 4; | 59 | private static final int FEATURE_TYPE_POSITION = 4; |
58 | private static final int REQUEST_ID_POSITION = 5; | 60 | private static final int REQUEST_ID_POSITION = 5; |
59 | 61 | ||
60 | private final CoapTransportContext transportContext; | 62 | private final CoapTransportContext transportContext; |
61 | private final TransportService transportService; | 63 | private final TransportService transportService; |
62 | - private final Field observerField; | ||
63 | private final long timeout; | 64 | private final long timeout; |
64 | private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); | 65 | private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); |
65 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); | 66 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); |
@@ -73,9 +74,20 @@ public class CoapTransportResource extends CoapResource { | @@ -73,9 +74,20 @@ public class CoapTransportResource extends CoapResource { | ||
73 | // This is important to turn off existing observable logic in | 74 | // This is important to turn off existing observable logic in |
74 | // CoapResource. We will have our own observe monitoring due to 1:1 | 75 | // CoapResource. We will have our own observe monitoring due to 1:1 |
75 | // observe relationship. | 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 | @Override | 93 | @Override |
@@ -187,9 +199,7 @@ public class CoapTransportResource extends CoapResource { | @@ -187,9 +199,7 @@ public class CoapTransportResource extends CoapResource { | ||
187 | new CoapOkCallback(exchange)); | 199 | new CoapOkCallback(exchange)); |
188 | break; | 200 | break; |
189 | case SUBSCRIBE_ATTRIBUTES_REQUEST: | 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 | transportService.process(sessionInfo, | 203 | transportService.process(sessionInfo, |
194 | TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), | 204 | TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), |
195 | new CoapNoOpCallback(exchange)); | 205 | new CoapNoOpCallback(exchange)); |
@@ -206,8 +216,6 @@ public class CoapTransportResource extends CoapResource { | @@ -206,8 +216,6 @@ public class CoapTransportResource extends CoapResource { | ||
206 | break; | 216 | break; |
207 | case SUBSCRIBE_RPC_COMMANDS_REQUEST: | 217 | case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
208 | rpcSubscriptions.add(sessionId); | 218 | rpcSubscriptions.add(sessionId); |
209 | - advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), | ||
210 | - registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); | ||
211 | transportService.process(sessionInfo, | 219 | transportService.process(sessionInfo, |
212 | TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), | 220 | TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), |
213 | new CoapNoOpCallback(exchange)); | 221 | new CoapNoOpCallback(exchange)); |
@@ -243,9 +251,6 @@ public class CoapTransportResource extends CoapResource { | @@ -243,9 +251,6 @@ public class CoapTransportResource extends CoapResource { | ||
243 | } catch (AdaptorException e) { | 251 | } catch (AdaptorException e) { |
244 | log.trace("[{}] Failed to decode message: ", sessionId, e); | 252 | log.trace("[{}] Failed to decode message: ", sessionId, e); |
245 | exchange.respond(ResponseCode.BAD_REQUEST); | 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,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,8 +18,9 @@ package org.thingsboard.server.transport.coap; | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.eclipse.californium.core.CoapResource; | 19 | import org.eclipse.californium.core.CoapResource; |
20 | import org.eclipse.californium.core.CoapServer; | 20 | import org.eclipse.californium.core.CoapServer; |
21 | + | ||
21 | import org.eclipse.californium.core.network.CoapEndpoint; | 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 | import org.springframework.beans.factory.annotation.Autowired; | 24 | import org.springframework.beans.factory.annotation.Autowired; |
24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
25 | import org.springframework.stereotype.Service; | 26 | import org.springframework.stereotype.Service; |
@@ -47,11 +48,15 @@ public class CoapTransportService { | @@ -47,11 +48,15 @@ public class CoapTransportService { | ||
47 | public void init() throws UnknownHostException { | 48 | public void init() throws UnknownHostException { |
48 | log.info("Starting CoAP transport..."); | 49 | log.info("Starting CoAP transport..."); |
49 | log.info("Starting CoAP transport server"); | 50 | log.info("Starting CoAP transport server"); |
50 | - this.server = new CoapServer(NetworkConfig.createStandardWithoutFile()); | 51 | + this.server = new CoapServer(); |
51 | createResources(); | 52 | createResources(); |
52 | InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); | 53 | InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); |
53 | InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); | 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 | server.start(); | 60 | server.start(); |
56 | log.info("CoAP transport started!"); | 61 | log.info("CoAP transport started!"); |
57 | } | 62 | } |
@@ -31,6 +31,7 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter; | @@ -31,6 +31,7 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter; | ||
31 | import org.thingsboard.server.gen.transport.TransportProtos; | 31 | import org.thingsboard.server.gen.transport.TransportProtos; |
32 | import org.thingsboard.server.transport.coap.CoapTransportResource; | 32 | import org.thingsboard.server.transport.coap.CoapTransportResource; |
33 | 33 | ||
34 | + | ||
34 | import java.util.Arrays; | 35 | import java.util.Arrays; |
35 | import java.util.HashSet; | 36 | import java.util.HashSet; |
36 | import java.util.List; | 37 | import java.util.List; |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java
@@ -28,6 +28,7 @@ import org.eclipse.californium.core.CoapClient; | @@ -28,6 +28,7 @@ import org.eclipse.californium.core.CoapClient; | ||
28 | import org.eclipse.californium.core.CoapHandler; | 28 | import org.eclipse.californium.core.CoapHandler; |
29 | import org.eclipse.californium.core.CoapResponse; | 29 | import org.eclipse.californium.core.CoapResponse; |
30 | import org.eclipse.californium.core.coap.MediaTypeRegistry; | 30 | import org.eclipse.californium.core.coap.MediaTypeRegistry; |
31 | +import org.eclipse.californium.elements.exception.ConnectorException; | ||
31 | import org.thingsboard.server.common.msg.session.FeatureType; | 32 | import org.thingsboard.server.common.msg.session.FeatureType; |
32 | import org.slf4j.Logger; | 33 | import org.slf4j.Logger; |
33 | import org.slf4j.LoggerFactory; | 34 | import org.slf4j.LoggerFactory; |
@@ -61,7 +62,7 @@ public class DeviceEmulator { | @@ -61,7 +62,7 @@ public class DeviceEmulator { | ||
61 | this.attributesClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.ATTRIBUTES)); | 62 | this.attributesClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.ATTRIBUTES)); |
62 | this.telemetryClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.TELEMETRY)); | 63 | this.telemetryClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.TELEMETRY)); |
63 | this.rpcClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.RPC)); | 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 | public void start() { | 68 | public void start() { |
@@ -72,11 +73,8 @@ public class DeviceEmulator { | @@ -72,11 +73,8 @@ public class DeviceEmulator { | ||
72 | try { | 73 | try { |
73 | sendObserveRequest(rpcClient); | 74 | sendObserveRequest(rpcClient); |
74 | while (!Thread.interrupted()) { | 75 | while (!Thread.interrupted()) { |
75 | - | ||
76 | - | ||
77 | sendRequest(attributesClient, createAttributesRequest()); | 76 | sendRequest(attributesClient, createAttributesRequest()); |
78 | sendRequest(telemetryClient, createTelemetryRequest()); | 77 | sendRequest(telemetryClient, createTelemetryRequest()); |
79 | - | ||
80 | Thread.sleep(1000); | 78 | Thread.sleep(1000); |
81 | } | 79 | } |
82 | } catch (Exception e) { | 80 | } catch (Exception e) { |
@@ -84,8 +82,8 @@ public class DeviceEmulator { | @@ -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 | MediaTypeRegistry.APPLICATION_JSON); | 87 | MediaTypeRegistry.APPLICATION_JSON); |
90 | log.info("Response: {}, {}", telemetryResponse.getCode(), telemetryResponse.getResponseText()); | 88 | log.info("Response: {}, {}", telemetryResponse.getCode(), telemetryResponse.getResponseText()); |
91 | } | 89 | } |
@@ -113,6 +111,7 @@ public class DeviceEmulator { | @@ -113,6 +111,7 @@ public class DeviceEmulator { | ||
113 | 111 | ||
114 | @Override | 112 | @Override |
115 | public void onError() { | 113 | public void onError() { |
114 | + log.info("Command Response Ack Error, No connect"); | ||
116 | //Do nothing | 115 | //Do nothing |
117 | } | 116 | } |
118 | }, mapper.writeValueAsString(response), MediaTypeRegistry.APPLICATION_JSON); | 117 | }, mapper.writeValueAsString(response), MediaTypeRegistry.APPLICATION_JSON); |
@@ -157,6 +156,15 @@ public class DeviceEmulator { | @@ -157,6 +156,15 @@ public class DeviceEmulator { | ||
157 | if (args.length != 4) { | 156 | if (args.length != 4) { |
158 | System.out.println("Usage: java -jar " + DeviceEmulator.class.getSimpleName() + ".jar host port device_token keys"); | 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 | final DeviceEmulator emulator = new DeviceEmulator(args[0], Integer.parseInt(args[1]), args[2], args[3]); | 168 | final DeviceEmulator emulator = new DeviceEmulator(args[0], Integer.parseInt(args[1]), args[2], args[3]); |
161 | emulator.start(); | 169 | emulator.start(); |
162 | Runtime.getRuntime().addShutdownHook(new Thread() { | 170 | Runtime.getRuntime().addShutdownHook(new Thread() { |
@@ -167,7 +175,6 @@ public class DeviceEmulator { | @@ -167,7 +175,6 @@ public class DeviceEmulator { | ||
167 | }); | 175 | }); |
168 | } | 176 | } |
169 | 177 | ||
170 | - | ||
171 | private String getFeatureTokenUrl(String host, int port, String token, FeatureType featureType) { | 178 | private String getFeatureTokenUrl(String host, int port, String token, FeatureType featureType) { |
172 | return getBaseUrl(host, port) + token + "/" + featureType.name().toLowerCase(); | 179 | return getBaseUrl(host, port) + token + "/" + featureType.name().toLowerCase(); |
173 | } | 180 | } |
@@ -327,6 +327,7 @@ public class DeviceApiController { | @@ -327,6 +327,7 @@ public class DeviceApiController { | ||
327 | public void onToServerRpcResponse(ToServerRpcResponseMsg msg) { | 327 | public void onToServerRpcResponse(ToServerRpcResponseMsg msg) { |
328 | responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); | 328 | responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); |
329 | } | 329 | } |
330 | + | ||
330 | } | 331 | } |
331 | 332 | ||
332 | private void reportActivity(SessionInfoProto sessionInfo) { | 333 | private void reportActivity(SessionInfoProto sessionInfo) { |
common/transport/lwm2m/pom.xml
0 → 100644
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>=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 | +} |
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ResultIds.java
0 → 100644
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 | +} |