Browse Source

Merge with master

pull/4524/head
Andrii Shvaika 5 years ago
parent
commit
00bd26f1f5
  1. 4
      application/pom.xml
  2. 7
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  3. 24
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  4. 5
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  7. 6
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  8. 2
      application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerService.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  12. 3
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
  15. 2
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  16. 10
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  18. 4
      application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java
  19. 19
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  20. 3
      application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java
  21. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  22. 3
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java
  23. 101
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  24. 3
      application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
  25. 1
      application/src/main/resources/logback.xml
  26. 11
      application/src/main/resources/thingsboard.yml
  27. 71
      application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java
  28. 3
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java
  29. 3
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java
  30. 9
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
  31. 26
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerComponent.java
  32. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  33. 4
      common/data/pom.xml
  34. 19
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  35. 3
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java
  36. 20
      common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java
  37. 11
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java
  38. 85
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java
  39. 13
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java
  40. 52
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java
  41. 45
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java
  42. 43
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java
  43. 25
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java
  44. 41
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java
  45. 32
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java
  46. 32
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java
  47. 36
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java
  48. 36
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java
  49. 56
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java
  50. 28
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java
  51. 34
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java
  52. 32
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java
  53. 26
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java
  54. 12
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java
  55. 3
      common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java
  56. 5
      common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java
  57. 19
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java
  58. 20
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  59. 4
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java
  60. 1
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java
  61. 5
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java
  62. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ClusterTopologyChangeEvent.java
  63. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java
  64. 35
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ServiceListChangedEvent.java
  65. 2
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/TbApplicationEvent.java
  66. 35
      common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java
  67. 35
      common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java
  68. 29
      common/queue/src/main/java/org/thingsboard/server/queue/util/TbSnmpTransportComponent.java
  69. 37
      common/queue/src/main/proto/queue.proto
  70. 11
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  71. 8
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  72. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java
  73. 100
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java
  74. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  75. 33
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java
  76. 123
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  77. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java
  78. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java
  79. 262
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java
  80. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java
  81. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java
  82. 18
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  83. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  84. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java
  85. 8
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
  86. 1
      common/transport/pom.xml
  87. 68
      common/transport/snmp/pom.xml
  88. 274
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java
  89. 35
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/ServiceListChangedEventListener.java
  90. 24
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEvent.java
  91. 34
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEventListener.java
  92. 171
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java
  93. 91
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java
  94. 121
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java
  95. 92
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportBalancingService.java
  96. 351
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java
  97. 146
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java
  98. 196
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java
  99. 745
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java
  100. 49
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java

4
application/pom.xml

@ -89,6 +89,10 @@
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>lwm2m</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>snmp</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>dao</artifactId>

7
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -22,10 +22,10 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.DefaultTbActorSystem;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbActorSystem;
import org.thingsboard.server.actors.TbActorSystemSettings;
@ -33,14 +33,13 @@ import org.thingsboard.server.actors.app.AppActor;
import org.thingsboard.server.actors.app.AppInitMsg;
import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Service
@Slf4j
@ -110,7 +109,7 @@ public class DefaultActorService extends TbApplicationEventListener<PartitionCha
if (poolSize == 1) {
return Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(dispatcherName));
} else {
return Executors.newWorkStealingPool(poolSize);
return ThingsBoardExecutors.newWorkStealingPool(poolSize, dispatcherName);
}
}

24
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
@ -89,13 +90,14 @@ public class UserController extends BaseController {
try {
UserId userId = new UserId(toUUID(strUserId));
User user = checkUserId(userId, Operation.READ);
if(!user.getAdditionalInfo().isNull()) {
processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD);
processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD);
}
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
if(userCredentials.isEnabled()) {
addUserCredentialsEnabled((ObjectNode) user.getAdditionalInfo());
if(user.getAdditionalInfo().isObject()) {
ObjectNode additionalInfo = (ObjectNode) user.getAdditionalInfo();
processDashboardIdFromAdditionalInfo(additionalInfo, DEFAULT_DASHBOARD);
processDashboardIdFromAdditionalInfo(additionalInfo, HOME_DASHBOARD);
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
if(userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) {
additionalInfo.put("userCredentialsEnabled", true);
}
}
return user;
} catch (Exception e) {
@ -103,14 +105,6 @@ public class UserController extends BaseController {
}
}
private void addUserCredentialsEnabled(ObjectNode additionalInfo) {
if(!additionalInfo.isNull()) {
if(!additionalInfo.has("userCredentialsEnabled")) {
additionalInfo.put("userCredentialsEnabled", true);
}
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
@ResponseBody

5
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
@ -56,7 +57,7 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
@ -146,7 +147,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
this.scheduler = scheduler;
this.tenantProfileCache = tenantProfileCache;
this.mailService = mailService;
this.mailExecutor = Executors.newSingleThreadExecutor();
this.mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail"));
}
@PostConstruct

2
application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java

@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
public interface TbApiUsageStateService extends ApplicationListener<PartitionChangeEvent> {

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

@ -55,7 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;

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

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId;
@ -38,7 +39,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
@ -127,7 +128,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
consumers.computeIfAbsent(configuration.getName(), queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration));
consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName(), statsFactory));
}
submitExecutor = Executors.newSingleThreadExecutor();
submitExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-rule-engine-consumer-service-submit-executor"));
}
@PreDestroy
@ -160,6 +161,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
private void launchConsumer(TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer, TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats) {
consumersExecutor.execute(() -> {
Thread.currentThread().setName("" + Thread.currentThread().getName() + "-" + configuration.getName());
while (!stopped) {
try {
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = consumer.poll(pollDuration);

2
application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.queue;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
public interface TbCoreConsumerService extends ApplicationListener<PartitionChangeEvent> {

2
application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.queue;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
public interface TbRuleEngineConsumerService extends ApplicationListener<PartitionChangeEvent> {

2
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;

2
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -185,7 +185,7 @@ public class DefaultTbResourceService implements TbResourceService {
instance.setId(0);
List<LwM2mResourceObserve> resources = new ArrayList<>();
obj.resources.forEach((k, v) -> {
if (!v.operations.isExecutable()) {
if (v.operations.isReadable()) {
LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
resources.add(lwM2MResourceObserve);
}

3
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -25,6 +25,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
@ -93,7 +94,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
super.init(maxRequestsTimeout);
if (useJsSandbox()) {
sandbox = NashornSandboxes.create();
monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize());
monitorExecutorService = ThingsBoardExecutors.newWorkStealingPool(getMonitorThreadPoolSize(), "nashorn-js-monitor");
sandbox.setExecutor(monitorExecutorService);
sandbox.setMaxCPUTime(getMaxCpuTime());
sandbox.allowNoBraces(false);

2
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -54,7 +54,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.util.TbCoreComponent;

2
application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java

@ -18,7 +18,7 @@ package org.thingsboard.server.service.state;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.queue.TbCallback;

2
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdate
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;

10
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -20,10 +20,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -63,7 +63,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
private SubscriptionManagerService subscriptionManagerService;
private ExecutorService subscriptionUpdateExecutor;
private TbApplicationEventListener<PartitionChangeEvent> partitionChangeListener = new TbApplicationEventListener<>() {
@Override
protected void onTbApplicationEvent(PartitionChangeEvent event) {
@ -94,7 +94,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
@PostConstruct
public void initExecutor() {
subscriptionUpdateExecutor = Executors.newWorkStealingPool(20);
subscriptionUpdateExecutor = ThingsBoardExecutors.newWorkStealingPool(20, getClass());
}
@PreDestroy

2
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import java.util.List;

4
application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java

@ -15,8 +15,8 @@
*/
package org.thingsboard.server.service.subscription;
import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;

19
application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java

@ -22,35 +22,18 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

3
application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java

@ -17,8 +17,7 @@ package org.thingsboard.server.service.telemetry;
import org.springframework.context.ApplicationListener;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
/**
* Created by ashvayka on 27.03.18.

6
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -28,6 +28,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@ -157,9 +159,9 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
@PostConstruct
public void initExecutor() {
serviceId = serviceInfoProvider.getServiceId();
executor = Executors.newWorkStealingPool(50);
executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass());
pingExecutor = Executors.newSingleThreadScheduledExecutor();
pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping"));
pingExecutor.scheduleWithFixedDelay(this::sendPing, 10000, 10000, TimeUnit.MILLISECONDS);
}

3
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java

@ -16,8 +16,7 @@
package org.thingsboard.server.service.telemetry;
import org.springframework.context.ApplicationListener;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
/**
* Created by ashvayka on 27.03.18.

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

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
@ -47,6 +48,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@ -66,11 +69,15 @@ import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
@ -93,6 +100,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* Created by ashvayka on 05.10.18.
@ -146,43 +154,43 @@ public class DefaultTransportApiService implements TransportApiService {
@Override
public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue();
ListenableFuture<TransportApiResponseMsg> result = null;
if (transportApiRequestMsg.hasValidateTokenRequestMsg()) {
ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg();
return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN);
} else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) {
TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg();
return Futures.transform(validateCredentials(msg),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = validateCredentials(msg);
} else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg();
return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE);
} else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg());
} else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = handle(transportApiRequestMsg.getEntityProfileRequestMsg());
} else if (transportApiRequestMsg.hasLwM2MRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getLwM2MRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = handle(transportApiRequestMsg.getLwM2MRequestMsg());
} else if (transportApiRequestMsg.hasValidateDeviceLwM2MCredentialsRequestMsg()) {
ValidateDeviceLwM2MCredentialsRequestMsg msg = transportApiRequestMsg.getValidateDeviceLwM2MCredentialsRequestMsg();
return Futures.transform(validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS);
} else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = handle(transportApiRequestMsg.getProvisionDeviceRequestMsg());
} else if (transportApiRequestMsg.hasResourceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = handle(transportApiRequestMsg.getResourceRequestMsg());
} else if (transportApiRequestMsg.hasSnmpDevicesRequestMsg()) {
result = handle(transportApiRequestMsg.getSnmpDevicesRequestMsg());
} else if (transportApiRequestMsg.hasDeviceRequestMsg()) {
result = handle(transportApiRequestMsg.getDeviceRequestMsg());
} else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) {
result = handle(transportApiRequestMsg.getDeviceCredentialsRequestMsg());
} else if (transportApiRequestMsg.hasFirmwareRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getFirmwareRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
result = handle(transportApiRequestMsg.getFirmwareRequestMsg());
}
return Futures.transform(getEmptyTransportApiResponseFuture(),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()),
MoreExecutors.directExecutor());
}
private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) {
@ -376,6 +384,39 @@ public class DefaultTransportApiService implements TransportApiService {
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build());
}
private ListenableFuture<TransportApiResponseMsg> handle(GetDeviceRequestMsg requestMsg) {
DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId);
TransportApiResponseMsg responseMsg;
if (device != null) {
UUID deviceProfileId = device.getDeviceProfileId().getId();
responseMsg = TransportApiResponseMsg.newBuilder()
.setDeviceResponseMsg(TransportProtos.GetDeviceResponseMsg.newBuilder()
.setDeviceProfileIdMSB(deviceProfileId.getMostSignificantBits())
.setDeviceProfileIdLSB(deviceProfileId.getLeastSignificantBits())
.setDeviceTransportConfiguration(ByteString.copyFrom(
dataDecodingEncodingService.encode(device.getDeviceData().getTransportConfiguration())
)))
.build();
} else {
responseMsg = TransportApiResponseMsg.getDefaultInstance();
}
return Futures.immediateFuture(responseMsg);
}
private ListenableFuture<TransportApiResponseMsg> handle(GetDeviceCredentialsRequestMsg requestMsg) {
DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(TenantId.SYS_TENANT_ID, deviceId);
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder()
.setDeviceCredentialsResponseMsg(TransportProtos.GetDeviceCredentialsResponseMsg.newBuilder()
.setDeviceCredentialsData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceCredentials))))
.build());
}
private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
@ -394,6 +435,22 @@ public class DefaultTransportApiService implements TransportApiService {
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourceResponseMsg(builder).build());
}
private ListenableFuture<TransportApiResponseMsg> handle(GetSnmpDevicesRequestMsg requestMsg) {
PageLink pageLink = new PageLink(requestMsg.getPageSize(), requestMsg.getPage());
PageData<UUID> result = deviceService.findDevicesIdsByDeviceProfileTransportType(DeviceTransportType.SNMP, pageLink);
GetSnmpDevicesResponseMsg responseMsg = GetSnmpDevicesResponseMsg.newBuilder()
.addAllIds(result.getData().stream()
.map(UUID::toString)
.collect(Collectors.toList()))
.setHasNextPage(result.hasNext())
.build();
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder()
.setSnmpDevicesResponseMsg(responseMsg)
.build());
}
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
if (device == null) {

3
application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java

@ -21,6 +21,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.stats.MessagesStats;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
@ -70,7 +71,7 @@ public class TbCoreTransportApiService {
@PostConstruct
public void init() {
this.transportCallbackExecutor = Executors.newWorkStealingPool(maxCallbackThreads);
this.transportCallbackExecutor = ThingsBoardExecutors.newWorkStealingPool(maxCallbackThreads, getClass());
TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> producer = tbCoreQueueFactory.createTransportApiResponseProducer();
TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> consumer = tbCoreQueueFactory.createTransportApiRequestConsumer();

1
application/src/main/resources/logback.xml

@ -26,6 +26,7 @@
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<logger name="org.thingsboard.server.transport.snmp" level="TRACE" />
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->

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

@ -621,9 +621,9 @@ transport:
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
x509:
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
# Local LwM2M transport parameters
@ -686,6 +686,13 @@ transport:
alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
snmp:
enabled: "${SNMP_ENABLED:true}"
response_processing:
# parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices
parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}"
# to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
# Edges parameters
edges:

71
application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java

@ -16,10 +16,10 @@
package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@ -28,39 +28,74 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrateg
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class TbMsgPackProcessingContextTest {
public static final int TIMEOUT = 10;
ExecutorService executorService;
@After
public void tearDown() {
if (executorService != null) {
executorService.shutdownNow();
}
}
@Test
public void testHighConcurrencyCase() throws InterruptedException {
TbRuleEngineSubmitStrategy strategyMock = mock(TbRuleEngineSubmitStrategy.class);
//log.warn("preparing the test...");
int msgCount = 1000;
int parallelCount = 5;
ExecutorService executorService = Executors.newFixedThreadPool(parallelCount);
try {
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> messages = new ConcurrentHashMap<>();
for (int i = 0; i < msgCount; i++) {
messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null));
}
when(strategyMock.getPendingMap()).thenReturn(messages);
TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock);
for (UUID uuid : messages.keySet()) {
for (int i = 0; i < parallelCount; i++) {
executorService.submit(() -> context.onSuccess(uuid));
}
executorService = Executors.newFixedThreadPool(parallelCount);
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> messages = new ConcurrentHashMap<>(msgCount);
for (int i = 0; i < msgCount; i++) {
messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null));
}
TbRuleEngineSubmitStrategy strategyMock = mock(TbRuleEngineSubmitStrategy.class);
when(strategyMock.getPendingMap()).thenReturn(messages);
TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock);
for (UUID uuid : messages.keySet()) {
final CountDownLatch readyLatch = new CountDownLatch(parallelCount);
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(parallelCount);
for (int i = 0; i < parallelCount; i++) {
//final String taskName = "" + uuid + " " + i;
executorService.submit(() -> {
//log.warn("ready {}", taskName);
readyLatch.countDown();
try {
startLatch.await();
} catch (InterruptedException e) {
Assert.fail("failed to await");
}
//log.warn("go {}", taskName);
context.onSuccess(uuid);
finishLatch.countDown();
});
}
Assert.assertTrue(context.await(10, TimeUnit.SECONDS));
Mockito.verify(strategyMock, Mockito.times(msgCount)).onSuccess(Mockito.any(UUID.class));
} finally {
executorService.shutdownNow();
assertTrue(readyLatch.await(TIMEOUT, TimeUnit.SECONDS));
Thread.yield();
startLatch.countDown(); //run all-at-once submitted tasks
assertTrue(finishLatch.await(TIMEOUT, TimeUnit.SECONDS));
}
assertTrue(context.await(TIMEOUT, TimeUnit.SECONDS));
verify(strategyMock, times(msgCount)).onSuccess(any(UUID.class));
}
}

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

@ -19,11 +19,10 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
@Slf4j
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@TbCoapServerComponent
@Component
public class CoapServerContext {

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

@ -23,7 +23,6 @@ import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@ -39,7 +38,7 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@TbCoapServerComponent
public class DefaultCoapServerService implements CoapServerService {
@Autowired

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

@ -39,7 +39,6 @@ import java.util.Collections;
import java.util.Optional;
@Slf4j
@ConditionalOnExpression("'${transport.coap.enabled}'=='true'")
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
@Component
public class TbCoapDtlsSettings {
@ -50,7 +49,7 @@ public class TbCoapDtlsSettings {
@Value("${transport.coap.dtls.bind_port}")
private Integer port;
@Value("${transport.coap.dtls.mode}")
@Value("${transport.coap.dtls.mode:NO_AUTH}")
private String mode;
@Value("${transport.coap.dtls.key_store}")
@ -65,13 +64,13 @@ public class TbCoapDtlsSettings {
@Value("${transport.coap.dtls.key_alias}")
private String keyAlias;
@Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
@Value("${transport.coap.dtls.x509.skip_validity_check_for_client_cert:false}")
private boolean skipValidityCheckForClientCert;
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout:86400000}")
private long dtlsSessionInactivityTimeout;
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout:1800000}")
private long dtlsSessionReportTimeout;
@Autowired

26
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerComponent.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.coapserver;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
public @interface TbCoapServerComponent {
}

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

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import java.util.List;
import java.util.UUID;
public interface DeviceService {
@ -95,6 +97,8 @@ public interface DeviceService {
Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile);
PageData<UUID> findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink);
Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);

4
common/data/pom.xml

@ -87,6 +87,10 @@
<groupId>org.thingsboard</groupId>
<artifactId>protobuf-dynamic</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>

19
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -93,6 +93,25 @@ public class DataConstants {
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
//<<<<<<< HEAD
//=======
// //firmware
// //telemetry
// public static final String CURRENT_FIRMWARE_TITLE = "current_fw_title";
// public static final String CURRENT_FIRMWARE_VERSION = "current_fw_version";
// public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
// public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
// public static final String TARGET_FIRMWARE_TS = "target_fw_ts";
// public static final String FIRMWARE_STATE = "fw_state";
//
// //attributes
// //telemetry
// public static final String FIRMWARE_TITLE = "fw_title";
// public static final String FIRMWARE_VERSION = "fw_version";
// public static final String FIRMWARE_SIZE = "fw_size";
// public static final String FIRMWARE_CHECKSUM = "fw_checksum";
// public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm";
//>>>>>>> origin/master
public static final String EDGE_MSG_SOURCE = "edge";
public static final String MSG_SOURCE_KEY = "source";

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
public enum DeviceTransportType {
DEFAULT,
MQTT,
COAP,
LWM2M,
COAP
SNMP
}

20
common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java

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

11
common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java

@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
import java.io.Serializable;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
@ -29,11 +31,14 @@ import org.thingsboard.server.common.data.DeviceTransportType;
@JsonSubTypes({
@JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"),
@JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"),
@JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"),
@JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"),
@JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP")})
public interface DeviceTransportConfiguration {
@JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")})
public interface DeviceTransportConfiguration extends Serializable {
@JsonIgnore
DeviceTransportType getType();
default void validate() {
}
}

85
common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.ToString;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol;
import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import java.util.Objects;
@Data
@ToString(of = {"host", "port", "protocolVersion"})
public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration {
private String host;
private Integer port;
private SnmpProtocolVersion protocolVersion;
/*
* For SNMP v1 and v2c
* */
private String community;
/*
* For SNMP v3
* */
private String username;
private String securityName;
private String contextName;
private AuthenticationProtocol authenticationProtocol;
private String authenticationPassphrase;
private PrivacyProtocol privacyProtocol;
private String privacyPassphrase;
private String engineId;
@Override
public DeviceTransportType getType() {
return DeviceTransportType.SNMP;
}
@Override
public void validate() {
if (!isValid()) {
throw new IllegalArgumentException("Transport configuration is not valid");
}
}
@JsonIgnore
private boolean isValid() {
boolean isValid = StringUtils.isNotBlank(host) && port != null && protocolVersion != null;
if (isValid) {
switch (protocolVersion) {
case V1:
case V2C:
isValid = StringUtils.isNotEmpty(community);
break;
case V3:
isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName)
&& contextName != null && authenticationProtocol != null
&& StringUtils.isNotBlank(authenticationPassphrase)
&& privacyProtocol != null && privacyPassphrase != null && engineId != null;
break;
}
}
return isValid;
}
}

13
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java

@ -29,13 +29,18 @@ import java.io.Serializable;
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
@JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
@JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
@JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP")})
@JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
@JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
@JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
@JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"),
@JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")
})
public interface DeviceProfileTransportConfiguration extends Serializable {
@JsonIgnore
DeviceTransportType getType();
default void validate() {
}
}

52
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java

@ -0,0 +1,52 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import java.util.List;
@Data
public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
private Integer timeoutMs;
private Integer retries;
private List<SnmpCommunicationConfig> communicationConfigs;
@Override
public DeviceTransportType getType() {
return DeviceTransportType.SNMP;
}
@Override
public void validate() {
if (!isValid()) {
throw new IllegalArgumentException("SNMP transport configuration is not valid");
}
}
@JsonIgnore
private boolean isValid() {
return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0
&& communicationConfigs != null
&& communicationConfigs.stream().allMatch(config -> config != null && config.isValid())
&& communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid)
.distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum();
}
}

45
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp;
import java.util.Arrays;
import java.util.Optional;
public enum AuthenticationProtocol {
SHA_1("1.3.6.1.6.3.10.1.1.3"),
SHA_224("1.3.6.1.6.3.10.1.1.4"),
SHA_256("1.3.6.1.6.3.10.1.1.5"),
SHA_384("1.3.6.1.6.3.10.1.1.6"),
SHA_512("1.3.6.1.6.3.10.1.1.7"),
MD5("1.3.6.1.6.3.10.1.1.2");
// oids taken from org.snmp4j.security.SecurityProtocol implementations
private final String oid;
AuthenticationProtocol(String oid) {
this.oid = oid;
}
public String getOid() {
return oid;
}
public static Optional<AuthenticationProtocol> forName(String name) {
return Arrays.stream(values())
.filter(protocol -> protocol.name().equalsIgnoreCase(name))
.findFirst();
}
}

43
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp;
import java.util.Arrays;
import java.util.Optional;
public enum PrivacyProtocol {
DES("1.3.6.1.6.3.10.1.2.2"),
AES_128("1.3.6.1.6.3.10.1.2.4"),
AES_192("1.3.6.1.4.1.4976.2.2.1.1.1"),
AES_256("1.3.6.1.4.1.4976.2.2.1.1.2");
// oids taken from org.snmp4j.security.SecurityProtocol implementations
private final String oid;
PrivacyProtocol(String oid) {
this.oid = oid;
}
public String getOid() {
return oid;
}
public static Optional<PrivacyProtocol> forName(String name) {
return Arrays.stream(values())
.filter(protocol -> protocol.name().equalsIgnoreCase(name))
.findFirst();
}
}

25
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java

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

41
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.kv.DataType;
import java.util.regex.Pattern;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SnmpMapping {
private String oid;
private String key;
private DataType dataType;
private static final Pattern OID_PATTERN = Pattern.compile("^\\.?([0-2])((\\.0)|(\\.[1-9][0-9]*))*$");
@JsonIgnore
public boolean isValid() {
return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && StringUtils.isNotBlank(key);
}
}

32
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp;
public enum SnmpMethod {
GET(-96),
SET(-93);
// codes taken from org.snmp4j.PDU class
private final int code;
SnmpMethod(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

32
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp;
public enum SnmpProtocolVersion {
V1(0),
V2C(1),
V3(3);
private final int code;
SnmpProtocolVersion(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp.config;
import lombok.Data;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import java.util.List;
@Data
public abstract class MultipleMappingsSnmpCommunicationConfig implements SnmpCommunicationConfig {
protected List<SnmpMapping> mappings;
@Override
public boolean isValid() {
return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid());
}
@Override
public List<SnmpMapping> getAllMappings() {
return mappings;
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp.config;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class RepeatingQueryingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig {
private Long queryingFrequencyMs;
@Override
public SnmpMethod getMethod() {
return SnmpMethod.GET;
}
@Override
public boolean isValid() {
return queryingFrequencyMs != null && queryingFrequencyMs > 0 && super.isValid();
}
}

56
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.config.impl.ClientAttributesQueryingSnmpCommunicationConfig;
import org.thingsboard.server.common.data.transport.snmp.config.impl.SharedAttributesSettingSnmpCommunicationConfig;
import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig;
import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcRequestSnmpCommunicationConfig;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "spec")
@JsonSubTypes({
@Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"),
@Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"),
@Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"),
@Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST")
})
public interface SnmpCommunicationConfig {
SnmpCommunicationSpec getSpec();
@JsonIgnore
default SnmpMethod getMethod() {
return null;
}
@JsonIgnore
List<SnmpMapping> getAllMappings();
@JsonIgnore
boolean isValid();
}

28
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java

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

34
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java

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

32
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp.config.impl;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig;
@EqualsAndHashCode(callSuper = true)
@Data
public class TelemetryQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig {
@Override
public SnmpCommunicationSpec getSpec() {
return SnmpCommunicationSpec.TELEMETRY_QUERYING;
}
}

26
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java

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

12
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java

@ -38,6 +38,7 @@ import static java.util.Collections.emptyList;
@Slf4j
public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> implements TbQueueConsumer<T> {
public static final long ONE_MILLISECOND_IN_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
private volatile boolean subscribed;
protected volatile boolean stopped = false;
protected volatile Set<TopicPartitionInfo> partitions;
@ -83,7 +84,7 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
}
if (consumerLock.isLocked()) {
log.error("poll. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace"));
log.error("poll. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock topic " + topic, new RuntimeException("stacktrace"));
}
consumerLock.lock();
@ -131,9 +132,12 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
List<T> sleepAndReturnEmpty(final long startNanos, final long durationInMillis) {
long durationNanos = TimeUnit.MILLISECONDS.toNanos(durationInMillis);
long spentNanos = System.nanoTime() - startNanos;
if (spentNanos < durationNanos) {
long nanosLeft = durationNanos - spentNanos;
if (nanosLeft >= ONE_MILLISECOND_IN_NANOS) {
try {
Thread.sleep(Math.max(TimeUnit.NANOSECONDS.toMillis(durationNanos - spentNanos), 1));
long sleepMs = TimeUnit.NANOSECONDS.toMillis(nanosLeft);
log.trace("Going to sleep after poll: topic {} for {}ms", topic, sleepMs);
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
if (!stopped) {
log.error("Failed to wait", e);
@ -146,7 +150,7 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
@Override
public void commit() {
if (consumerLock.isLocked()) {
log.error("commit. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace"));
log.error("commit. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock topic " + topic, new RuntimeException("stacktrace"));
}
consumerLock.lock();
try {

3
common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueCallback;
@ -77,7 +78,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
this.executor = executor;
} else {
internalExecutor = true;
this.executor = Executors.newSingleThreadExecutor();
this.executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-queue-request-template-" + responseTemplate.getTopic()));
}
}

5
common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.common;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueHandler;
@ -70,8 +71,8 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
this.requestTimeout = requestTimeout;
this.callbackExecutor = executor;
this.stats = stats;
this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
this.loopExecutor = Executors.newSingleThreadExecutor();
this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("tb-queue-response-template-timeout-" + requestTemplate.getTopic()));
this.loopExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-queue-response-template-loop-" + requestTemplate.getTopic()));
}
@Override

19
common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java

@ -19,19 +19,23 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import org.thingsboard.server.queue.util.AfterContextReady;
import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -56,6 +60,8 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
@Autowired(required = false)
private TbQueueRuleEngineSettings ruleEngineSettings;
@Autowired
private ApplicationContext applicationContext;
private List<ServiceType> serviceTypes;
private ServiceInfo serviceInfo;
@ -102,6 +108,19 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
serviceInfo = builder.build();
}
@AfterContextReady
public void setTransports() {
serviceInfo = ServiceInfo.newBuilder(serviceInfo)
.addAllTransports(getTransportServices().stream()
.map(TbTransportService::getName)
.collect(Collectors.toSet()))
.build();
}
private Collection<TbTransportService> getTransportServices() {
return applicationContext.getBeansOfType(TbTransportService.class).values();
}
@Override
public ServiceInfo getServiceInfo() {
return serviceInfo;

20
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -15,26 +15,27 @@
*/
package org.thingsboard.server.queue.discovery;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -46,7 +47,6 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.stream.Collectors;
@Service
@ -186,6 +186,8 @@ public class HashPartitionService implements PartitionService {
applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes));
}
}
applicationEventPublisher.publishEvent(new ServiceListChangedEvent(otherServices, currentService));
}
@Override
@ -219,6 +221,14 @@ public class HashPartitionService implements PartitionService {
}
}
@Override
public int resolvePartitionIndex(UUID entityId, int partitions) {
int hash = hashFunction.newHasher()
.putLong(entityId.getMostSignificantBits())
.putLong(entityId.getLeastSignificantBits()).hash().asInt();
return Math.abs(hash % partitions);
}
private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) {
final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>();
services.forEach(serviceInfo -> {

4
common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java

@ -20,9 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* Once application is ready or cluster topology changes, this Service will produce {@link PartitionChangeEvent}
@ -55,4 +57,6 @@ public interface PartitionService {
* @return
*/
TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId);
int resolvePartitionIndex(UUID entityId, int partitions);
}

1
common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.discovery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.queue.discovery.event.TbApplicationEvent;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

5
common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java

@ -33,12 +33,14 @@ import org.apache.zookeeper.KeeperException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -77,7 +79,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
private volatile boolean stopped = true;
public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService) {
public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider,
PartitionService partitionService) {
this.serviceInfoProvider = serviceInfoProvider;
this.partitionService = partitionService;
}

3
common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java → common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ClusterTopologyChangeEvent.java

@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.discovery;
package org.thingsboard.server.queue.discovery.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
import java.util.Set;

3
common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java → common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java

@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.discovery;
package org.thingsboard.server.queue.discovery.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;

35
common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ServiceListChangedEvent.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.discovery.event;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import java.util.List;
@Getter
@ToString
public class ServiceListChangedEvent extends TbApplicationEvent {
private final List<ServiceInfo> otherServices;
private final ServiceInfo currentService;
public ServiceListChangedEvent(List<ServiceInfo> otherServices, ServiceInfo currentService) {
super(otherServices);
this.otherServices = otherServices;
this.currentService = currentService;
}
}

2
common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java → common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/TbApplicationEvent.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.discovery;
package org.thingsboard.server.queue.discovery.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;

35
common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.util;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventListener(ContextRefreshedEvent.class)
@Order
public @interface AfterContextReady {
@AliasFor(annotation = Order.class, attribute = "value")
int order() default Integer.MAX_VALUE;
}

35
common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.util;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventListener(ApplicationReadyEvent.class)
@Order
public @interface AfterStartUp {
@AliasFor(annotation = Order.class, attribute = "value")
int order() default Integer.MAX_VALUE;
}

29
common/queue/src/main/java/org/thingsboard/server/queue/util/TbSnmpTransportComponent.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface TbSnmpTransportComponent {
}

37
common/queue/src/main/proto/queue.proto

@ -34,6 +34,7 @@ message ServiceInfo {
int64 tenantIdMSB = 3;
int64 tenantIdLSB = 4;
repeated QueueInfo ruleEngineQueues = 5;
repeated string transports = 6;
}
/**
@ -246,6 +247,36 @@ message GetEntityProfileResponseMsg {
bytes apiState = 3;
}
message GetDeviceRequestMsg {
int64 deviceIdMSB = 1;
int64 deviceIdLSB = 2;
}
message GetDeviceResponseMsg {
int64 deviceProfileIdMSB = 1;
int64 deviceProfileIdLSB = 2;
bytes deviceTransportConfiguration = 3;
}
message GetDeviceCredentialsRequestMsg {
int64 deviceIdMSB = 1;
int64 deviceIdLSB = 2;
}
message GetDeviceCredentialsResponseMsg {
bytes deviceCredentialsData = 1;
}
message GetSnmpDevicesRequestMsg {
int32 page = 1;
int32 pageSize = 2;
}
message GetSnmpDevicesResponseMsg {
repeated string ids = 1;
bool hasNextPage = 2;
}
message EntityUpdateMsg {
string entityType = 1;
bytes data = 2;
@ -592,6 +623,9 @@ message TransportApiRequestMsg {
ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
GetResourceRequestMsg resourceRequestMsg = 9;
GetFirmwareRequestMsg firmwareRequestMsg = 10;
GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11;
GetDeviceRequestMsg deviceRequestMsg = 12;
GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13;
}
/* Response from ThingsBoard Core Service to Transport Service */
@ -600,9 +634,12 @@ message TransportApiResponseMsg {
GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5;
LwM2MResponseMsg lwM2MResponseMsg = 6;
GetResourceResponseMsg resourceResponseMsg = 7;
GetFirmwareResponseMsg firmwareResponseMsg = 8;
GetDeviceResponseMsg deviceResponseMsg = 9;
GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10;
}
/* Messages that are handled by ThingsBoard Core Service */

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

@ -21,7 +21,9 @@ import org.eclipse.californium.core.CoapServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.coapserver.CoapServerService;
import org.thingsboard.server.coapserver.TbCoapServerComponent;
import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
import javax.annotation.PostConstruct;
@ -29,9 +31,9 @@ import javax.annotation.PreDestroy;
import java.net.UnknownHostException;
@Service("CoapTransportService")
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@TbCoapServerComponent
@Slf4j
public class CoapTransportService {
public class CoapTransportService implements TbTransportService {
private static final String V1 = "v1";
private static final String API = "api";
@ -65,4 +67,9 @@ public class CoapTransportService {
public void shutdown() {
log.info("CoAP transport stopped!");
}
@Override
public String getName() {
return "COAP";
}
}

8
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -35,6 +35,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportContext;
@ -71,7 +72,7 @@ import java.util.function.Consumer;
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.http.enabled}'=='true')")
@RequestMapping("/api/v1")
@Slf4j
public class DeviceApiController {
public class DeviceApiController implements TbTransportService {
@Autowired
private HttpTransportContext transportContext;
@ -422,4 +423,9 @@ public class DeviceApiController {
}
}
@Override
public String getName() {
return "HTTP";
}
}

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

@ -55,7 +55,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getCoapConfig;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
@Slf4j
@Component
@ -93,7 +93,6 @@ public class LwM2MTransportBootstrapServerConfiguration {
builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort));
/** Define model provider (Create Models )*/
// builder.setModel(new StaticModel(contextS.getLwM2MTransportConfigServer().getModelsValueCommon()));
/** Create credentials */
this.setServerWithCredentials(builder);

100
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java

@ -0,0 +1,100 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server;
import org.eclipse.californium.core.network.config.NetworkConfig;
public class LwM2mNetworkConfig {
public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) {
NetworkConfig coapConfig = new NetworkConfig();
coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec);
coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort);
/**
* Example:Property for large packet:
* #NetworkConfig config = new NetworkConfig();
* #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32);
* #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32);
* #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048);
* #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3);
* #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000);
*/
/**
* Property to indicate if the response should always include the Block2 option \
* when client request early blockwise negociation but the response can be sent on one packet.
* - value of false indicate that the server will respond without block2 option if no further blocks are required.
* - value of true indicate that the server will response with block2 option event if no further blocks are required.
* CoAP client will try to use block mode
* or adapt the block size when receiving a 4.13 Entity too large response code
*/
coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
/***
* Property to indicate if the response should always include the Block2 option \
* when client request early blockwise negociation but the response can be sent on one packet.
* - value of false indicate that the server will respond without block2 option if no further blocks are required.
* - value of true indicate that the server will response with block2 option event if no further blocks are required.
*/
coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000);
/**
* !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13
* The maximum size of a resource body (in bytes) that will be accepted
* as the payload of a POST/PUT or the response to a GET request in a
* transparent> blockwise transfer.
* This option serves as a safeguard against excessive memory
* consumption when many resources contain large bodies that cannot be
* transferred in a single CoAP message. This option has no impact on
* *manually* managed blockwise transfers in which the blocks are handled individually.
* Note that this option does not prevent local clients or resource
* implementations from sending large bodies as part of a request or response to a peer.
* The default value of this property is DEFAULT_MAX_RESOURCE_BODY_SIZE = 8192
* A value of {@code 0} turns off transparent handling of blockwise transfers altogether.
*/
// coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 8192);
coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 16384);
/**
* The default DTLS response matcher.
* Supported values are STRICT, RELAXED, or PRINCIPAL.
* The default value is STRICT.
* Create new instance of udp endpoint context matcher.
* Params:
* checkAddress
* true with address check, (STRICT, UDP)
* - false, without
*/
coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT");
/**
* https://tools.ietf.org/html/rfc7959#section-2.9.3
* The block size (number of bytes) to use when doing a blockwise transfer. \
* This value serves as the upper limit for block size in blockwise transfers
*/
coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 512);
/**
* The maximum payload size (in bytes) that can be transferred in a
* single message, i.e. without requiring a blockwise transfer.
* NB: this value MUST be adapted to the maximum message size supported by the transport layer.
* In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol
* DEFAULT_VALUE = 1024
*/
coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 512);
coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4);
return coapConfig;
}
}

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

@ -26,6 +26,7 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate;
import java.util.Collection;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
@Slf4j
@ -85,17 +86,19 @@ public class LwM2mServerListener {
@Override
public void cancelled(Observation observation) {
log.info("Received notification cancelled from [{}] ", observation.getPath());
String msg = String.format("%s: Cancel Observation %s.", LOG_LW2M_INFO, observation.getPath());
service.sendLogsToThingsboard(msg, observation.getRegistrationId());
log.trace(msg);
}
@Override
public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
if (registration != null) {
try {
service.onObservationResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(),
service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(),
registration), response, null);
} catch (Exception e) {
log.error("[{}] onResponse", e.toString());
log.error("Observation/Read onResponse", e);
}
}
@ -108,7 +111,10 @@ public class LwM2mServerListener {
@Override
public void newObservation(Observation observation, Registration registration) {
log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint());
String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO,
observation.getPath());
service.sendLogsToThingsboard(msg, registration.getId());
log.trace(msg);
}
};

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

@ -25,7 +25,6 @@ import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ObjectModel;
@ -40,7 +39,6 @@ import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.registration.Registration;
import org.nustaq.serialization.FSTConfiguration;
import org.thingsboard.server.common.data.DeviceProfile;
@ -50,7 +48,6 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@ -72,7 +69,6 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
@Slf4j
public class LwM2mTransportHandler {
// public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
public static final String BOOTSTRAP = "bootstrap";
@ -85,19 +81,12 @@ public class LwM2mTransportHandler {
public static final String KEY_NAME = "keyName";
public static final String OBSERVE_LWM2M = "observe";
public static final String ATTRIBUTE_LWM2M = "attributeLwm2m";
// public static final String RESOURCE_VALUE = "resValue";
// public static final String RESOURCE_TYPE = "resType";
private static final String REQUEST = "/request";
// private static final String RESPONSE = "/response";
private static final String ATTRIBUTES = "/" + ATTRIBUTE;
public static final String TELEMETRIES = "/" + TELEMETRY;
// public static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
// public static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/";
public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
// public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES;
// public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES;
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
@ -112,6 +101,11 @@ public class LwM2mTransportHandler {
public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
public static final Integer FR_OBJECT_ID = 5;
public static final Integer FR_RESOURCE_VER_ID = 7;
public static final String FR_PATH_RESOURCE_VER_ID = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_PATH
+ "0" + LWM2M_SEPARATOR_PATH + FR_RESOURCE_VER_ID;
public enum LwM2mTypeServer {
BOOTSTRAP(0, "bootstrap"),
CLIENT(1, "client");
@ -168,6 +162,8 @@ public class LwM2mTransportHandler {
WRITE_ATTRIBUTES(8, "WriteAttributes"),
DELETE(9, "Delete");
// READ_INFO_FW(10, "ReadInfoFirmware");
public int code;
public String type;
@ -190,21 +186,6 @@ public class LwM2mTransportHandler {
public static final String SERVICE_CHANNEL = "SERVICE";
public static final String RESPONSE_CHANNEL = "RESP";
public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) {
NetworkConfig coapConfig;
File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME);
if (configFile.isFile()) {
coapConfig = new NetworkConfig();
coapConfig.load(configFile);
} else {
coapConfig = LeshanServerBuilder.createDefaultNetworkConfig();
coapConfig.store(configFile);
}
coapConfig.setString("COAP_PORT", Integer.toString(serverPortNoSec));
coapConfig.setString("COAP_SECURE_PORT", Integer.toString(serverSecurePort));
return coapConfig;
}
public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException {
switch (type) {
case BOOLEAN:

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

@ -67,6 +67,7 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT;
import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE;
@ -125,7 +126,6 @@ public class LwM2mTransportRequest {
public void sendAllRequest(Registration registration, String targetIdVer, LwM2mTypeOper typeOper,
String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) {
try {
String target = convertPathFromIdVerToObjectId(targetIdVer);
DownlinkRequest request = null;
ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT;
@ -145,11 +145,11 @@ public class LwM2mTransportRequest {
break;
case OBSERVE:
if (resultIds.isResource()) {
request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.isObjectInstance()) {
request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId());
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else if (resultIds.getObjectId() >= 0) {
request = new ObserveRequest(resultIds.getObjectId());
request = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
break;
case OBSERVE_CANCEL:
@ -171,8 +171,6 @@ public class LwM2mTransportRequest {
break;
case WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
// resource = lwM2MClient.getResourceModel(targetIdVer);
// if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
.getModelProvider());
if (contentFormat.equals(ContentFormat.TLV)) {
@ -181,7 +179,6 @@ public class LwM2mTransportRequest {
registration, rpcRequest);
}
// Mode.REPLACE && Request to write a <b>String Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON)
// else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
else if (!contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type,
@ -215,13 +212,16 @@ public class LwM2mTransportRequest {
long finalTimeoutInMs = timeoutInMs;
lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, rpcRequest));
} catch (Exception e) {
log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper, e);
log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e);
}
} else if (OBSERVE_CANCEL == typeOper) {
log.trace("[{}], [{}] - [{}] SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer);
if (rpcRequest != null) {
rpcRequest.setInfoMsg(null);
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, null);
}
} else if (OBSERVE_CANCEL == typeOper && rpcRequest != null) {
rpcRequest.setInfoMsg(null);
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, null);
} else {
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer);
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer);
if (rpcRequest != null) {
String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null";
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
@ -236,8 +236,8 @@ public class LwM2mTransportRequest {
Set<String> observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet());
String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO,
OBSERVE_READ_ALL.type, observationPaths);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.info("[{}], [{}]", registration.getEndpoint(), msg);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.trace("[{}] [{}], [{}]", typeOper.name(), registration.getEndpoint(), msg);
if (rpcRequest != null) {
String valueMsg = String.format("Observation paths - %s", observationPaths);
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE);
@ -246,7 +246,7 @@ public class LwM2mTransportRequest {
} catch (Exception e) {
String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR,
typeOper.name(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
throw new Exception(e);
}
}
@ -261,45 +261,53 @@ public class LwM2mTransportRequest {
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) {
leshanServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest);
if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) {
LwM2mNode node = ((WriteRequest) request).getNode();
Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(),
((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath());
String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client",
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(),
response.getCode().getName(), request.getPath().toString(), value);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(),
request.getPath().toString(), value);
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO);
}
} else {
String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR,
String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(),
((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
serviceImpl.sendLogsToThingsboard(msg, registration);
log.error("[{}], [{}] - [{}] [{}] error SendRequest", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR);
}
/** Not Found
* set setClient_fw_version = empty
**/
if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) {
lwM2MClient.setUpdateFw(false);
lwM2MClient.getFrUpdate().setClientFwVersion("");
log.warn("updateFirmwareClient1");
serviceImpl.updateFirmwareClient(lwM2MClient);
}
}
}, e -> {
/** version == null
* set setClient_fw_version = empty
**/
if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) {
lwM2MClient.setUpdateFw(false);
lwM2MClient.getFrUpdate().setClientFwVersion("");
log.warn("updateFirmwareClient2");
serviceImpl.updateFirmwareClient(lwM2MClient);
}
if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client",
LOG_LW2M_ERROR, request.getPath().toString(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration);
log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString());
String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s",
LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString());
if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR);
}
});
}
private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId,
@ -323,7 +331,9 @@ public class LwM2mTransportRequest {
Date date = new Date(Long.decode(value.toString()));
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date);
case OPAQUE: // byte[] value, base64
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray()));
byte[] valueRequest = value instanceof byte[] ? (byte[]) value : Hex.decodeHex(value.toString().toCharArray());
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, valueRequest) :
new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueRequest);
default:
}
}
@ -337,7 +347,7 @@ public class LwM2mTransportRequest {
String patn = "/" + objectId + "/" + instanceId + "/" + resourceId;
String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client",
patn, type, value, e.toString());
serviceImpl.sendLogsToThingsboard(msg, registration);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString());
if (rpcRequest != null) {
String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value);
@ -369,7 +379,7 @@ public class LwM2mTransportRequest {
DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) {
String pathIdVer = convertPathFromObjectIdToIdVer(path, registration);
if (response instanceof ReadResponse) {
serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest);
serviceImpl.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest);
} else if (response instanceof CancelObservationResponse) {
log.info("[{}] Path [{}] CancelObservationResponse 3_Send", pathIdVer, response);
@ -389,14 +399,33 @@ public class LwM2mTransportRequest {
} else if (response instanceof WriteAttributesResponse) {
log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", pathIdVer, response);
} else if (response instanceof WriteResponse) {
log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", pathIdVer, response);
log.info("[{}] Path [{}] WriteResponse 9_Send", pathIdVer, response);
this.infoWriteResponse(registration, response, request);
serviceImpl.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request);
}
if (rpcRequest != null && (response instanceof ExecuteResponse
|| response instanceof WriteAttributesResponse
|| response instanceof DeleteResponse)) {
rpcRequest.setInfoMsg(null);
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null);
if (rpcRequest != null) {
if (response instanceof ExecuteResponse
|| response instanceof WriteAttributesResponse
|| response instanceof DeleteResponse) {
rpcRequest.setInfoMsg(null);
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null);
} else if (response instanceof WriteResponse) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO);
}
}
}
private void infoWriteResponse(Registration registration, LwM2mResponse response,
DownlinkRequest request) {
LwM2mNode node = ((WriteRequest) request).getNode();
Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(),
((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath());
String msg = String.format("%s: Update finished successfully: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s",
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(),
response.getCode().getName(), request.getPath().toString(), value);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(),
request.getPath().toString(), value);
}
}

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java

@ -16,6 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.core.network.stack.BlockwiseLayer;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
@ -57,7 +59,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getCoapConfig;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig;
@Slf4j
@Component
@ -92,7 +94,10 @@ public class LwM2mTransportServerConfiguration {
/** Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/** Create CoAP Config */
NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort);
BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig);
builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort));
/** Define model provider (Create Models )*/
@ -110,6 +115,7 @@ public class LwM2mTransportServerConfiguration {
/** Create DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setServerOnly(true);
dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportConfigServer().isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportConfigServer().isRecommendedCiphers());
if (this.pskMode) {

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

@ -20,13 +20,14 @@ import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest;
import java.util.Collection;
import java.util.Optional;
public interface LwM2mTransportService {
public interface LwM2mTransportService extends TbTransportService {
void onRegistered(Registration registration, Collection<Observation> previousObsersations);
@ -38,7 +39,7 @@ public interface LwM2mTransportService {
void setCancelObservations(Registration registration);
void onObservationResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest);
void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest);
void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo);
@ -59,6 +60,4 @@ public interface LwM2mTransportService {
void doTrigger(Registration registration, String path);
void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);
}

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

@ -38,9 +38,15 @@ import org.eclipse.leshan.server.registration.Registration;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.firmware.FirmwareDataCache;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.firmware.FirmwareKey;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUtil;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -76,9 +82,11 @@ import java.util.stream.Collectors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE;
@ -109,6 +117,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private ExecutorService executorUpdateRegistered;
private ExecutorService executorUnRegistered;
private LwM2mValueConverterImpl converter;
private FirmwareDataCache firmwareDataCache;
private final TransportService transportService;
@ -120,12 +130,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private final LwM2mTransportRequest lwM2mTransportRequest;
public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer, LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, @Lazy LwM2mTransportRequest lwM2mTransportRequest) {
public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer,
LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer,
@Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache) {
this.transportService = transportService;
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
this.lwM2mClientContext = lwM2mClientContext;
this.leshanServer = leshanServer;
this.lwM2mTransportRequest = lwM2mTransportRequest;
this.firmwareDataCache = firmwareDataCache;
}
@PostConstruct
@ -167,8 +180,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
this.getInfoFirmwareUpdate(lwM2MClient);
this.initLwM2mFromClientValue(registration, lwM2MClient);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration.getId());
} else {
log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null);
}
@ -223,7 +237,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
executorUnRegistered.submit(() -> {
try {
this.setCancelObservations(registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId());
this.closeClientSession(registration);
} catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t);
@ -256,7 +270,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override
public void onSleepingDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration.getId());
//TODO: associate endpointId with device information.
}
@ -279,7 +293,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param response - observe
*/
@Override
public void onObservationResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) {
public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) {
if (response.getContent() != null) {
if (response.getContent() instanceof LwM2mObject) {
LwM2mObject lwM2mObject = (LwM2mObject) response.getContent();
@ -303,20 +317,26 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* Update - send request in change value resources in Client
* Path to resources from profile equal keyName or from ModelObject equal name
* Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase)
* Delete - nothing *
* 1. FirmwareUpdate:
* - If msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0
* 2. Shared Other AttributeUpdate
* -- Path to resources from profile equal keyName or from ModelObject equal name
* -- Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase)
* 3. Delete - nothing
*
* @param msg -
*/
@Override
public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));
if (msg.getSharedUpdatedCount() > 0) {
msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey();
String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName);
Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv());
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) {
this.getInfoFirmwareUpdate(lwM2MClient);
}
if (pathIdVer != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
.getModelProvider());
@ -326,19 +346,26 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, valueNew);
String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated",
LOG_LW2M_ERROR, pathIdVer, valueNew);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId());
}
} else {
log.error("Resource name name - [{}] value - [{}] is not present as attribute/telemetry in profile and cannot be updated", pathName, valueNew);
String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated",
LOG_LW2M_ERROR, pathName, valueNew);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId());
}
});
} else if (msg.getSharedDeletedCount() > 0) {
msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey();
Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv());
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew);
}
});
log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
}
}
/**
@ -454,23 +481,21 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonObject()) {
lwm2mClientRpcRequest.setParams(new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey)
.getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType()));
.getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType()));
}
lwm2mClientRpcRequest.setSessionInfo(sessionInfo);
if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) {
lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " +
lwm2mClientRpcRequest.keyNameKey + " is null or bad format");
}
else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper()
} else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper()
|| WRITE_REPLACE == lwm2mClientRpcRequest.getTypeOper())
&& lwm2mClientRpcRequest.getTargetIdVer() !=null
&& lwm2mClientRpcRequest.getTargetIdVer() != null
&& !(new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResource()
|| new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResourceInstance())) {
lwm2mClientRpcRequest.setErrorMsg("Invalid parameter " + lwm2mClientRpcRequest.targetIdVerKey
lwm2mClientRpcRequest.setErrorMsg("Invalid parameter " + lwm2mClientRpcRequest.targetIdVerKey
+ ". Only Resource or ResourceInstance can be this operation");
}
else if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()){
} else if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()) {
lwm2mClientRpcRequest.setErrorMsg("Procedures In Development...");
}
} else {
@ -482,23 +507,23 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
return lwm2mClientRpcRequest;
}
public void sentRpcRequest (Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) {
public void sentRpcRequest(Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) {
rpcRequest.setResponseCode(requestCode);
if (LOG_LW2M_ERROR.equals(typeMsg)) {
rpcRequest.setInfoMsg(null);
rpcRequest.setValueMsg(null);
if (rpcRequest.getErrorMsg() == null) {
msg = msg.isEmpty() ? null : msg;
rpcRequest.setErrorMsg(msg);
}
} else if (LOG_LW2M_INFO.equals(typeMsg)) {
if (rpcRequest.getInfoMsg() == null) {
rpcRequest.setInfoMsg(msg);
}
} else if (LOG_LW2M_VALUE.equals(typeMsg)) {
if (rpcRequest.getValueMsg() == null) {
rpcRequest.setValueMsg(msg);
}
if (LOG_LW2M_ERROR.equals(typeMsg)) {
rpcRequest.setInfoMsg(null);
rpcRequest.setValueMsg(null);
if (rpcRequest.getErrorMsg() == null) {
msg = msg.isEmpty() ? null : msg;
rpcRequest.setErrorMsg(msg);
}
} else if (LOG_LW2M_INFO.equals(typeMsg)) {
if (rpcRequest.getInfoMsg() == null) {
rpcRequest.setInfoMsg(msg);
}
} else if (LOG_LW2M_VALUE.equals(typeMsg)) {
if (rpcRequest.getValueMsg() == null) {
rpcRequest.setValueMsg(msg);
}
}
this.onToDeviceRpcResponse(rpcRequest.getDeviceRpcResponseResultMsg(), rpcRequest.getSessionInfo());
}
@ -555,7 +580,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
protected void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId());
//TODO: associate endpointId with device information.
}
@ -582,10 +607,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* @param logMsg - text msg
* @param registration - Id of Registration LwM2M Client
* @param registrationId - Id of Registration LwM2M Client
*/
public void sendLogsToThingsboard(String logMsg, Registration registration) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
public void sendLogsToThingsboard(String logMsg, String registrationId) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId);
if (logMsg != null && sessionInfo != null) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo);
}
@ -609,7 +634,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (clientObjects != null && clientObjects.size() > 0) {
if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
// #2
lwM2MClient.getPendingRequests().addAll(clientObjects);
lwM2MClient.getPendingReadRequests().addAll(clientObjects);
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(),
null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null));
}
@ -651,6 +676,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* Sending observe value of resources to thingsboard
* #1 Return old Value Resource from LwM2MClient
* #2 Update new Resources (replace old Resource Value on new Resource Value)
* #3 If fr_update -> UpdateFirmware
* #4 updateAttrTelemetry
*
* @param registration - Registration LwM2M Client
* @param lwM2mResource - LwM2mSingleResource response.getContent()
@ -660,6 +687,19 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
.getModelProvider())) {
if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) &&
lwM2MClient.getFrUpdate().getCurrentFwVersion() != null
&& !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion())
&& lwM2MClient.isUpdateFw()) {
/** version != null
* set setClient_fw_version = value
**/
lwM2MClient.setUpdateFw(false);
lwM2MClient.getFrUpdate().setClientFwVersion(lwM2mResource.getValue().toString());
log.warn("updateFirmwareClient3");
this.updateFirmwareClient(lwM2MClient);
}
Set<String> paths = new HashSet<>();
paths.add(path);
this.updateAttrTelemetry(registration, paths);
@ -668,6 +708,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
}
/**
* send Attribute and Telemetry to Thingsboard
* #1 - get AttrName/TelemetryName with value from LwM2MClient:
@ -722,22 +763,23 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile());
result = params.keySet();
}
if (!result.isEmpty()) {
if (result != null && !result.isEmpty()) {
// #1
Set<String> pathSend = result.stream().filter(target -> {
return target.split(LWM2M_SEPARATOR_PATH).length < 3 ?
clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) :
clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]);
}
)
.collect(Collectors.toUnmodifiableSet());
).collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
lwM2MClient.getPendingRequests().addAll(pathSend);
lwM2MClient.getPendingReadRequests().addAll(pathSend);
ConcurrentHashMap<String, Object> finalParams = params;
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null));
pathSend.forEach(target -> {
lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null);
});
if (OBSERVE.equals(typeOper)) {
lwM2MClient.initValue(this, null);
lwM2MClient.initReadValue(this, null);
}
}
}
@ -968,7 +1010,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
// update value in Resources
registrationIds.forEach(registrationId -> {
Registration registration = lwM2mClientContext.getRegistration(registrationId);
this.readResourceValueObserve(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ);
this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ);
// send attr/telemetry to tingsboard for new path
this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd());
});
@ -997,12 +1039,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
registrationIds.forEach(registrationId -> {
Registration registration = lwM2mClientContext.getRegistration(registrationId);
if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) {
this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE);
this.readObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE);
}
// 5.3 del
// send Request cancel observe to Client
if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) {
this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel());
this.cancelObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersDel());
}
});
}
@ -1042,7 +1084,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param registration - Registration LwM2M Client
* @param targets - path Resources == [ "/2/0/0", "/2/0/1"]
*/
private void readResourceValueObserve(Registration registration, Set<String> targets, LwM2mTypeOper typeOper) {
private void readObserveFromProfile(Registration registration, Set<String> targets, LwM2mTypeOper typeOper) {
targets.forEach(target -> {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target));
if (pathIds.isResource()) {
@ -1132,7 +1174,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
private void cancelObserveIsValue(Registration registration, Set<String> paramAnallyzer) {
private void cancelObserveFromProfile(Registration registration, Set<String> paramAnallyzer) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
paramAnallyzer.forEach(pathIdVer -> {
if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) {
@ -1152,7 +1194,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
log.error("Failed update resource [{}] [{}]", path, valueNew);
String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad",
LOG_LW2M_ERROR, path, valueNew);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId());
log.info("Failed update resource [{}] [{}]", path, valueNew);
}
}
@ -1181,8 +1223,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values
* #1 Get path resource by result attributesResponse
* 1. FirmwareUpdate:
* - msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0
* 2. Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values
* - Get path resource by result attributesResponse
*
* @param attributesResponse -
* @param sessionInfo -
@ -1190,6 +1234,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) {
try {
List<TransportProtos.TsKvProto> tsKvProtos = attributesResponse.getSharedAttributeListList();
this.updateAttriuteFromThingsboard(tsKvProtos, sessionInfo);
} catch (Exception e) {
log.error(String.valueOf(e));
@ -1273,7 +1318,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
private SessionInfoProto getValidateSessionInfo(String registrationId) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(null, registrationId);
return getNewSessionInfoProto(lwM2MClient);
return lwM2MClient != null ? this.getNewSessionInfoProto(lwM2MClient) : null;
}
/**
@ -1292,28 +1337,88 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* !!! sharedAttr === profileAttr !!!
* If there is a difference in values between the current resource values and the shared attribute values
* when the client connects to the server
* #1 get attributes name from profile include name resources in ModelObject if resource isWritable
* #2.1 #1 size > 0 => send Request getAttributes to thingsboard
* #1. !!! sharedAttr === profileAttr !!!
* - If there is a difference in values between the current resource values and the shared attribute values
* - when the client connects to the server
* #1.1 get attributes name from profile include name resources in ModelObject if resource isWritable
* #1.2 #1 size > 0 => send Request getAttributes to thingsboard
* #2. FirmwareAttribute subscribe:
*
* @param lwM2MClient - LwM2M Client
*/
public void putDelayedUpdateResourcesThingsboard(LwM2mClient lwM2MClient) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration());
if (sessionInfo != null) {
//#1.1 + #1.2
List<String> attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient);
if (attrSharedNames.size() > 0) {
//#2.1
//#1.1
ConcurrentMap<String, String> keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient);
if (keyNamesMap.values().size() > 0) {
try {
TransportProtos.GetAttributeRequestMsg getAttributeMsg = lwM2mTransportContextServer.getAdaptor().convertToGetAttributes(null, attrSharedNames);
//#1.2
TransportProtos.GetAttributeRequestMsg getAttributeMsg = lwM2mTransportContextServer.getAdaptor().convertToGetAttributes(null, keyNamesMap.values());
transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST));
} catch (AdaptorException e) {
log.warn("Failed to decode get attributes request", e);
}
}
}
}
public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration());
if (sessionInfo != null) {
TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.build();
transportService.process(sessionInfo, getFirmwareRequestMsg,
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion(response.getVersion());
lwM2MClient.getFrUpdate().setCurrentFwId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.setUpdateFw(true);
readRequestToClientFirmwareVer(lwM2MClient.getRegistration());
} else {
log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}
}
@Override
public void onError(Throwable e) {
log.trace("Failed to process credentials ", e);
}
});
}
}
/**
* @param registration
*/
public void readRequestToClientFirmwareVer(Registration registration) {
String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration);
lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(),
null, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null);
}
/**
*
* @param lwM2MClient -
*/
public void updateFirmwareClient(LwM2mClient lwM2MClient) {
if (!lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion())) {
int chunkSize = 0;
int chunk = 0;
byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk);
Integer objectId = 5;
String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId);
String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0;
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.TLV.getName(),
firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null);
log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion());
}
}
@ -1325,23 +1430,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param lwM2MClient -
* @return ArrayList keyNames from profile profileAttr && IsWritable
*/
private List<String> getNamesAttrFromProfileIsWritable(LwM2mClient lwM2MClient) {
private ConcurrentMap<String, String> getNamesFromProfileForSharedAttributes(LwM2mClient lwM2MClient) {
LwM2mClientProfile profile = lwM2mClientContext.getProfile(lwM2MClient.getProfileId());
Set<String> attrSet = new Gson().fromJson(profile.getPostAttributeProfile(),
new TypeToken<HashSet<String>>() {
}.getType());
ConcurrentMap<String, String> keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(),
return new Gson().fromJson(profile.getPostKeyNameProfile().toString(),
new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType());
ConcurrentMap<String, String> keyNamesIsWritable = keyNamesMap.entrySet()
.stream()
.filter(e -> (attrSet.contains(e.getKey()) && validateResourceInModel(lwM2MClient, e.getKey(), true)))
.collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue));
Set<String> namesIsWritable = ConcurrentHashMap.newKeySet();
namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values()));
return new ArrayList<>(namesIsWritable);
}
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
@ -1353,4 +1447,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
}
@Override
public String getName() {
return "LWM2M";
}
}

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

@ -24,11 +24,8 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Collection;
import java.util.Random;
import java.util.Set;
@Slf4j
@Component("LwM2MJsonAdaptor")
@ -54,11 +51,7 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor {
}
@Override
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List<String> clientKeys, List<String> sharedKeys) throws AdaptorException {
return processGetAttributeRequestMsg(clientKeys, sharedKeys);
}
protected TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(List<String> clientKeys, List<String> sharedKeys) throws AdaptorException {
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(Collection<String> clientKeys, Collection<String> sharedKeys) throws AdaptorException {
try {
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
Random random = new Random();
@ -75,14 +68,4 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor {
throw new AdaptorException(e);
}
}
private Set<String> toStringSet(JsonElement requestBody, String name) {
JsonElement element = requestBody.getAsJsonObject().get(name);
if (element != null) {
return new HashSet<>(Arrays.asList(element.getAsString().split(",")));
} else {
return null;
}
}
}

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

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

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

@ -57,13 +57,15 @@ public class LwM2mClient implements Cloneable {
private UUID deviceId;
private UUID sessionId;
private UUID profileId;
private volatile LwM2mFirmwareUpdate frUpdate;
private Registration registration;
private ValidateDeviceCredentialsResponseMsg credentialsResponse;
private final Map<String, ResourceValue> resources;
private final Map<String, TransportProtos.TsKvProto> delayedRequests;
private final List<String> pendingRequests;
private final List<String> pendingReadRequests;
private final Queue<LwM2mQueuedRequest> queuedRequests;
private boolean init;
private volatile boolean updateFw;
public Object clone() throws CloneNotSupportedException {
return super.clone();
@ -75,12 +77,14 @@ public class LwM2mClient implements Cloneable {
this.securityInfo = securityInfo;
this.credentialsResponse = credentialsResponse;
this.delayedRequests = new ConcurrentHashMap<>();
this.pendingRequests = new CopyOnWriteArrayList<>();
this.pendingReadRequests = new CopyOnWriteArrayList<>();
this.resources = new ConcurrentHashMap<>();
this.profileId = profileId;
this.sessionId = sessionId;
this.init = false;
this.updateFw = false;
this.queuedRequests = new ConcurrentLinkedQueue<>();
this.frUpdate = new LwM2mFirmwareUpdate();
}
public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) {
@ -103,15 +107,13 @@ public class LwM2mClient implements Cloneable {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
String verRez = getVerFromPathIdVerOrId(pathRez);
return (verRez == null || verSupportedObject.equals(verRez)) ? modelProvider.getObjectModel(registration)
return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration)
.getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null;
}
public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, LwM2mModelProvider modelProvider,
LwM2mValueConverterImpl converter) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
String verRez = getVerFromPathIdVerOrId(pathRezIdVer);
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()).resources;
@ -170,11 +172,11 @@ public class LwM2mClient implements Cloneable {
.collect(Collectors.toSet());
}
public void initValue(LwM2mTransportServiceImpl serviceImpl, String path) {
public void initReadValue(LwM2mTransportServiceImpl serviceImpl, String path) {
if (path != null) {
this.pendingRequests.remove(path);
this.pendingReadRequests.remove(path);
}
if (this.pendingRequests.size() == 0) {
if (this.pendingReadRequests.size() == 0) {
this.init = true;
serviceImpl.putDelayedUpdateResourcesThingsboard(this);
}

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

@ -82,12 +82,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override
public LwM2mClient getLwM2mClientWithReg(Registration registration, String registrationId) {
LwM2mClient client = registrationId != null ?
LwM2mClient client = registrationId != null && this.lwM2mClients.containsKey(registrationId) ?
this.lwM2mClients.get(registrationId) :
this.lwM2mClients.containsKey(registration.getId()) ?
this.lwM2mClients.get(registration.getId()) :
this.lwM2mClients.get(registration.getEndpoint());
return client != null ? client : updateInSessionsLwM2MClient(registration);
registration !=null && this.lwM2mClients.containsKey(registration.getId()) ?
this.lwM2mClients.get(registration.getId()) : registration !=null && this.lwM2mClients.containsKey(registration) ?
this.lwM2mClients.get(registration.getEndpoint()) : null;
return client != null ? client : registration!= null ? updateInSessionsLwM2MClient(registration) : null;
}
@Override

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java

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

8
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java

@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.TbTransportService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -38,7 +39,7 @@ import javax.annotation.PreDestroy;
@Service("MqttTransportService")
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.mqtt.enabled}'=='true')")
@Slf4j
public class MqttTransportService {
public class MqttTransportService implements TbTransportService {
@Value("${transport.mqtt.bind_address}")
private String host;
@ -90,4 +91,9 @@ public class MqttTransportService {
}
log.info("MQTT transport stopped!");
}
@Override
public String getName() {
return "MQTT";
}
}

1
common/transport/pom.xml

@ -40,6 +40,7 @@
<module>http</module>
<module>coap</module>
<module>lwm2m</module>
<module>snmp</module>
</modules>
</project>

68
common/transport/snmp/pom.xml

@ -0,0 +1,68 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>snmp</artifactId>
<packaging>jar</packaging>
<name>Thingsboard SNMP Transport Common</name>
<url>https://thingsboard.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../../..</main.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>transport-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.snmp4j</groupId>
<artifactId>snmp4j</artifactId>
</dependency>
<dependency>
<groupId>org.snmp4j</groupId>
<artifactId>snmp4j-agent</artifactId>
<version>3.3.6</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

274
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java

@ -0,0 +1,274 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.transport.DeviceUpdatedEvent;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportDeviceProfileCache;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.service.ProtoTransportEntityService;
import org.thingsboard.server.transport.snmp.service.SnmpAuthService;
import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService;
import org.thingsboard.server.transport.snmp.service.SnmpTransportService;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
@TbSnmpTransportComponent
@Component
@Slf4j
@RequiredArgsConstructor
public class SnmpTransportContext extends TransportContext {
@Getter
private final SnmpTransportService snmpTransportService;
private final TransportDeviceProfileCache deviceProfileCache;
private final TransportService transportService;
private final ProtoTransportEntityService protoEntityService;
private final SnmpTransportBalancingService balancingService;
@Getter
private final SnmpAuthService snmpAuthService;
private final Map<DeviceId, DeviceSessionContext> sessions = new ConcurrentHashMap<>();
private final Collection<DeviceId> allSnmpDevicesIds = new ConcurrentLinkedDeque<>();
@AfterStartUp(order = 2)
public void fetchDevicesAndEstablishSessions() {
log.info("Initializing SNMP devices sessions");
int batchIndex = 0;
int batchSize = 512;
boolean nextBatchExists = true;
while (nextBatchExists) {
TransportProtos.GetSnmpDevicesResponseMsg snmpDevicesResponse = protoEntityService.getSnmpDevicesIds(batchIndex, batchSize);
snmpDevicesResponse.getIdsList().stream()
.map(id -> new DeviceId(UUID.fromString(id)))
.peek(allSnmpDevicesIds::add)
.filter(deviceId -> balancingService.isManagedByCurrentTransport(deviceId.getId()))
.map(protoEntityService::getDeviceById)
.forEach(device -> getExecutor().execute(() -> establishDeviceSession(device)));
nextBatchExists = snmpDevicesResponse.getHasNextPage();
batchIndex++;
}
log.debug("Found all SNMP devices ids: {}", allSnmpDevicesIds);
}
private void establishDeviceSession(Device device) {
if (device == null) return;
log.info("Establishing SNMP session for device {}", device.getId());
DeviceProfileId deviceProfileId = device.getDeviceProfileId();
DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId);
DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId());
if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) {
log.warn("[{}] Expected credentials type is {} but found {}", device.getId(), DeviceCredentialsType.ACCESS_TOKEN, credentials.getCredentialsType());
return;
}
SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
DeviceSessionContext deviceSessionContext;
try {
deviceSessionContext = new DeviceSessionContext(
device, deviceProfile, credentials.getCredentialsId(),
profileTransportConfiguration, deviceTransportConfiguration, this
);
registerSessionMsgListener(deviceSessionContext);
} catch (Exception e) {
log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString());
return;
}
sessions.put(device.getId(), deviceSessionContext);
snmpTransportService.createQueryingTasks(deviceSessionContext);
log.info("Established SNMP device session for device {}", device.getId());
}
private void updateDeviceSession(DeviceSessionContext sessionContext, Device device, DeviceProfile deviceProfile) {
log.info("Updating SNMP session for device {}", device.getId());
DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId());
if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) {
log.warn("[{}] Expected credentials type is {} but found {}", device.getId(), DeviceCredentialsType.ACCESS_TOKEN, credentials.getCredentialsType());
destroyDeviceSession(sessionContext);
return;
}
SnmpDeviceProfileTransportConfiguration newProfileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
SnmpDeviceTransportConfiguration newDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
try {
if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) {
sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration);
sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration);
snmpTransportService.cancelQueryingTasks(sessionContext);
snmpTransportService.createQueryingTasks(sessionContext);
} else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) {
sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration);
sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration);
} else {
log.trace("Configuration of the device {} was not updated", device);
}
} catch (Exception e) {
log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage());
destroyDeviceSession(sessionContext);
}
}
private void destroyDeviceSession(DeviceSessionContext sessionContext) {
if (sessionContext == null) return;
log.info("Destroying SNMP device session for device {}", sessionContext.getDevice().getId());
sessionContext.close();
snmpAuthService.cleanUpSnmpAuthInfo(sessionContext);
transportService.deregisterSession(sessionContext.getSessionInfo());
snmpTransportService.cancelQueryingTasks(sessionContext);
sessions.remove(sessionContext.getDeviceId());
log.trace("Unregistered and removed session");
}
private void registerSessionMsgListener(DeviceSessionContext deviceSessionContext) {
transportService.process(DeviceTransportType.SNMP,
TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceSessionContext.getToken()).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (msg.hasDeviceInfo()) {
SessionInfoProto sessionInfo = SessionInfoCreator.create(
msg, SnmpTransportContext.this, UUID.randomUUID()
);
transportService.registerAsyncSession(sessionInfo, deviceSessionContext);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), TransportServiceCallback.EMPTY);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), TransportServiceCallback.EMPTY);
deviceSessionContext.setSessionInfo(sessionInfo);
deviceSessionContext.setDeviceInfo(msg.getDeviceInfo());
} else {
log.warn("[{}] Failed to process device auth", deviceSessionContext.getDeviceId());
}
}
@Override
public void onError(Throwable e) {
log.warn("[{}] Failed to process device auth: {}", deviceSessionContext.getDeviceId(), e);
}
});
}
@EventListener(DeviceUpdatedEvent.class)
public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) {
Device device = deviceUpdatedEvent.getDevice();
log.trace("Got creating or updating device event for device {}", device);
DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration())
.map(DeviceTransportConfiguration::getType)
.orElse(null);
if (!allSnmpDevicesIds.contains(device.getId())) {
if (transportType != DeviceTransportType.SNMP) {
return;
}
allSnmpDevicesIds.add(device.getId());
if (balancingService.isManagedByCurrentTransport(device.getId().getId())) {
establishDeviceSession(device);
}
} else {
if (balancingService.isManagedByCurrentTransport(device.getId().getId())) {
DeviceSessionContext sessionContext = sessions.get(device.getId());
if (transportType == DeviceTransportType.SNMP) {
if (sessionContext != null) {
updateDeviceSession(sessionContext, device, deviceProfileCache.get(device.getDeviceProfileId()));
} else {
establishDeviceSession(device);
}
} else {
log.trace("Transport type was changed to {}", transportType);
destroyDeviceSession(sessionContext);
}
}
}
}
public void onDeviceDeleted(DeviceSessionContext sessionContext) {
destroyDeviceSession(sessionContext);
}
public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) {
updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile);
}
public void onSnmpTransportListChanged() {
log.trace("SNMP transport list changed. Updating sessions");
List<DeviceId> deleted = new LinkedList<>();
for (DeviceId deviceId : allSnmpDevicesIds) {
if (balancingService.isManagedByCurrentTransport(deviceId.getId())) {
if (!sessions.containsKey(deviceId)) {
Device device = protoEntityService.getDeviceById(deviceId);
if (device != null) {
log.info("SNMP device {} is now managed by current transport node", deviceId);
establishDeviceSession(device);
} else {
deleted.add(deviceId);
}
}
} else {
Optional.ofNullable(sessions.get(deviceId))
.ifPresent(sessionContext -> {
log.info("SNMP session for device {} is not managed by current transport node anymore", deviceId);
destroyDeviceSession(sessionContext);
});
}
}
log.trace("Removing deleted SNMP devices: {}", deleted);
allSnmpDevicesIds.removeAll(deleted);
}
public Collection<DeviceSessionContext> getSessions() {
return sessions.values();
}
}

35
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/ServiceListChangedEventListener.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.event;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService;
@TbSnmpTransportComponent
@Component
@RequiredArgsConstructor
public class ServiceListChangedEventListener extends TbApplicationEventListener<ServiceListChangedEvent> {
private final SnmpTransportBalancingService snmpTransportBalancingService;
@Override
protected void onTbApplicationEvent(ServiceListChangedEvent event) {
snmpTransportBalancingService.onServiceListChanged(event);
}
}

24
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEvent.java

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

34
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEventListener.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.event;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.SnmpTransportContext;
@TbSnmpTransportComponent
@Component
@RequiredArgsConstructor
public class SnmpTransportListChangedEventListener extends TbApplicationEventListener<SnmpTransportListChangedEvent> {
private final SnmpTransportContext snmpTransportContext;
@Override
protected void onTbApplicationEvent(SnmpTransportListChangedEvent event) {
snmpTransportContext.onSnmpTransportListChanged();
}
}

171
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java

@ -0,0 +1,171 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.service;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@TbSnmpTransportComponent
@Service
@Slf4j
public class PduService {
public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
PDU pdu = setUpPdu(sessionContext);
pdu.setType(communicationConfig.getMethod().getCode());
pdu.addAll(communicationConfig.getAllMappings().stream()
.filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey()))
.map(mapping -> Optional.ofNullable(values.get(mapping.getKey()))
.map(value -> {
Variable variable = toSnmpVariable(value, mapping.getDataType());
return new VariableBinding(new OID(mapping.getOid()), variable);
})
.orElseGet(() -> new VariableBinding(new OID(mapping.getOid()))))
.collect(Collectors.toList()));
return pdu;
}
public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) {
PDU pdu = setUpPdu(sessionContext);
pdu.setType(snmpMethod.getCode());
Variable variable = value == null ? Null.instance : toSnmpVariable(value, dataType);
pdu.add(new VariableBinding(new OID(oid), variable));
return pdu;
}
private Variable toSnmpVariable(String value, DataType dataType) {
dataType = dataType == null ? DataType.STRING : dataType;
Variable variable;
switch (dataType) {
case LONG:
try {
variable = new Integer32(Integer.parseInt(value));
break;
} catch (NumberFormatException ignored) {
}
case DOUBLE:
case BOOLEAN:
case STRING:
case JSON:
default:
variable = new OctetString(value);
}
return variable;
}
private PDU setUpPdu(DeviceSessionContext sessionContext) {
PDU pdu;
SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration();
SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion();
switch (snmpVersion) {
case V1:
case V2C:
pdu = new PDU();
break;
case V3:
ScopedPDU scopedPdu = new ScopedPDU();
scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName()));
scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId()));
pdu = scopedPdu;
break;
default:
throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported");
}
return pdu;
}
public JsonObject processPdu(PDU pdu, List<SnmpMapping> responseMappings) {
Map<OID, String> values = processPdu(pdu);
Map<OID, SnmpMapping> mappings = new HashMap<>();
if (responseMappings != null) {
for (SnmpMapping mapping : responseMappings) {
OID oid = new OID(mapping.getOid());
mappings.put(oid, mapping);
}
}
JsonObject data = new JsonObject();
values.forEach((oid, value) -> {
log.trace("Processing variable binding: {} - {}", oid, value);
SnmpMapping mapping = mappings.get(oid);
if (mapping == null) {
log.debug("No SNMP mapping for oid {}", oid);
return;
}
processValue(mapping.getKey(), mapping.getDataType(), value, data);
});
return data;
}
public Map<OID, String> processPdu(PDU pdu) {
return IntStream.range(0, pdu.size())
.mapToObj(pdu::get)
.filter(Objects::nonNull)
.filter(variableBinding -> !(variableBinding.getVariable() instanceof Null))
.collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString));
}
public void processValue(String key, DataType dataType, String value, JsonObject result) {
switch (dataType) {
case LONG:
result.addProperty(key, Long.parseLong(value));
break;
case BOOLEAN:
result.addProperty(key, Boolean.parseBoolean(value));
break;
case DOUBLE:
result.addProperty(key, Double.parseDouble(value));
break;
case STRING:
case JSON:
default:
result.addProperty(key, value);
}
}
}

91
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java

@ -0,0 +1,91 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@TbSnmpTransportComponent
@Service
@RequiredArgsConstructor
public class ProtoTransportEntityService {
private final TransportService transportService;
private final DataDecodingEncodingService dataDecodingEncodingService;
public Device getDeviceById(DeviceId id) {
TransportProtos.GetDeviceResponseMsg deviceProto = transportService.getDevice(TransportProtos.GetDeviceRequestMsg.newBuilder()
.setDeviceIdMSB(id.getId().getMostSignificantBits())
.setDeviceIdLSB(id.getId().getLeastSignificantBits())
.build());
if (deviceProto == null) {
return null;
}
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(
deviceProto.getDeviceProfileIdMSB(), deviceProto.getDeviceProfileIdLSB())
);
Device device = new Device();
device.setId(id);
device.setDeviceProfileId(deviceProfileId);
DeviceTransportConfiguration deviceTransportConfiguration = (DeviceTransportConfiguration) dataDecodingEncodingService.decode(
deviceProto.getDeviceTransportConfiguration().toByteArray()
).orElseThrow(() -> new IllegalStateException("Can't find device transport configuration"));
DeviceData deviceData = new DeviceData();
deviceData.setTransportConfiguration(deviceTransportConfiguration);
device.setDeviceData(deviceData);
return device;
}
public DeviceCredentials getDeviceCredentialsByDeviceId(DeviceId deviceId) {
TransportProtos.GetDeviceCredentialsResponseMsg deviceCredentialsResponse = transportService.getDeviceCredentials(
TransportProtos.GetDeviceCredentialsRequestMsg.newBuilder()
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.build()
);
return (DeviceCredentials) dataDecodingEncodingService.decode(deviceCredentialsResponse.getDeviceCredentialsData().toByteArray())
.orElseThrow(() -> new IllegalArgumentException("Device credentials not found"));
}
public TransportProtos.GetSnmpDevicesResponseMsg getSnmpDevicesIds(int page, int pageSize) {
TransportProtos.GetSnmpDevicesRequestMsg requestMsg = TransportProtos.GetSnmpDevicesRequestMsg.newBuilder()
.setPage(page)
.setPageSize(pageSize)
.build();
return transportService.getSnmpDevicesIds(requestMsg);
}
}

121
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java

@ -0,0 +1,121 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.service;
import lombok.RequiredArgsConstructor;
import org.snmp4j.AbstractTarget;
import org.snmp4j.CommunityTarget;
import org.snmp4j.Target;
import org.snmp4j.UserTarget;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.service.SnmpTransportService;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import java.util.Optional;
@Service
@TbSnmpTransportComponent
@RequiredArgsConstructor
public class SnmpAuthService {
private final SnmpTransportService snmpTransportService;
@Value("${transport.snmp.underlying_protocol}")
private String snmpUnderlyingProtocol;
public Target setUpSnmpTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) {
AbstractTarget target;
SnmpProtocolVersion protocolVersion = deviceTransportConfig.getProtocolVersion();
switch (protocolVersion) {
case V1:
CommunityTarget communityTargetV1 = new CommunityTarget();
communityTargetV1.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1);
communityTargetV1.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
communityTargetV1.setCommunity(new OctetString(deviceTransportConfig.getCommunity()));
target = communityTargetV1;
break;
case V2C:
CommunityTarget communityTargetV2 = new CommunityTarget();
communityTargetV2.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c);
communityTargetV2.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
communityTargetV2.setCommunity(new OctetString(deviceTransportConfig.getCommunity()));
target = communityTargetV2;
break;
case V3:
OctetString username = new OctetString(deviceTransportConfig.getUsername());
OctetString securityName = new OctetString(deviceTransportConfig.getSecurityName());
OctetString engineId = new OctetString(deviceTransportConfig.getEngineId());
OID authenticationProtocol = new OID(deviceTransportConfig.getAuthenticationProtocol().getOid());
OID privacyProtocol = new OID(deviceTransportConfig.getPrivacyProtocol().getOid());
OctetString authenticationPassphrase = new OctetString(deviceTransportConfig.getAuthenticationPassphrase());
authenticationPassphrase = new OctetString(SecurityProtocols.getInstance().passwordToKey(authenticationProtocol, authenticationPassphrase, engineId.getValue()));
OctetString privacyPassphrase = new OctetString(deviceTransportConfig.getPrivacyPassphrase());
privacyPassphrase = new OctetString(SecurityProtocols.getInstance().passwordToKey(privacyProtocol, authenticationProtocol, privacyPassphrase, engineId.getValue()));
USM usm = snmpTransportService.getSnmp().getUSM();
if (usm.hasUser(engineId, securityName)) {
usm.removeAllUsers(username, engineId);
}
usm.addLocalizedUser(
engineId.getValue(), username,
authenticationProtocol, authenticationPassphrase.getValue(),
privacyProtocol, privacyPassphrase.getValue()
);
UserTarget userTarget = new UserTarget();
userTarget.setSecurityName(securityName);
userTarget.setAuthoritativeEngineID(engineId.getValue());
userTarget.setSecurityModel(SecurityModel.SECURITY_MODEL_USM);
userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV);
target = userTarget;
break;
default:
throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported");
}
Address address = GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort());
target.setAddress(Optional.ofNullable(address).orElseThrow(() -> new IllegalArgumentException("Address of the SNMP device is invalid")));
target.setTimeout(profileTransportConfig.getTimeoutMs());
target.setRetries(profileTransportConfig.getRetries());
target.setVersion(protocolVersion.getCode());
return target;
}
public void cleanUpSnmpAuthInfo(DeviceSessionContext sessionContext) {
SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration();
if (deviceTransportConfiguration.getProtocolVersion() == SnmpProtocolVersion.V3) {
OctetString username = new OctetString(deviceTransportConfiguration.getUsername());
OctetString engineId = new OctetString(deviceTransportConfiguration.getEngineId());
snmpTransportService.getSnmp().getUSM().removeAllUsers(username, engineId);
}
}
}

92
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportBalancingService.java

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.event.SnmpTransportListChangedEvent;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@TbSnmpTransportComponent
@Service
@RequiredArgsConstructor
@Slf4j
public class SnmpTransportBalancingService {
private final PartitionService partitionService;
private final ApplicationEventPublisher eventPublisher;
private final SnmpTransportService snmpTransportService;
private int snmpTransportsCount = 1;
private Integer currentTransportPartitionIndex = 0;
public void onServiceListChanged(ServiceListChangedEvent event) {
log.trace("Got service list changed event: {}", event);
recalculatePartitions(event.getOtherServices(), event.getCurrentService());
}
public boolean isManagedByCurrentTransport(UUID entityId) {
boolean isManaged = resolvePartitionIndexForEntity(entityId) == currentTransportPartitionIndex;
if (!isManaged) {
log.trace("Entity {} is not managed by current SNMP transport node", entityId);
}
return isManaged;
}
private int resolvePartitionIndexForEntity(UUID entityId) {
return partitionService.resolvePartitionIndex(entityId, snmpTransportsCount);
}
private void recalculatePartitions(List<ServiceInfo> otherServices, ServiceInfo currentService) {
log.info("Recalculating partitions for SNMP transports");
List<ServiceInfo> snmpTransports = Stream.concat(otherServices.stream(), Stream.of(currentService))
.filter(service -> service.getTransportsList().contains(snmpTransportService.getName()))
.sorted(Comparator.comparing(ServiceInfo::getServiceId))
.collect(Collectors.toList());
log.trace("Found SNMP transports: {}", snmpTransports);
int previousCurrentTransportPartitionIndex = currentTransportPartitionIndex;
int previousSnmpTransportsCount = snmpTransportsCount;
if (!snmpTransports.isEmpty()) {
for (int i = 0; i < snmpTransports.size(); i++) {
if (snmpTransports.get(i).equals(currentService)) {
currentTransportPartitionIndex = i;
break;
}
}
snmpTransportsCount = snmpTransports.size();
}
if (snmpTransportsCount != previousSnmpTransportsCount || currentTransportPartitionIndex != previousCurrentTransportPartitionIndex) {
log.info("SNMP transports partitions have changed: transports count = {}, current transport partition index = {}", snmpTransportsCount, currentTransportPartitionIndex);
eventPublisher.publishEvent(new SnmpTransportListChangedEvent());
} else {
log.info("SNMP transports partitions have not changed");
}
}
}

351
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java

@ -0,0 +1,351 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.service;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.OctetString;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@TbSnmpTransportComponent
@Service
@Slf4j
@RequiredArgsConstructor
public class SnmpTransportService implements TbTransportService {
private final TransportService transportService;
private final PduService pduService;
@Getter
private Snmp snmp;
private ScheduledExecutorService queryingExecutor;
private ExecutorService responseProcessingExecutor;
private final Map<SnmpCommunicationSpec, ResponseDataMapper> responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class);
private final Map<SnmpCommunicationSpec, ResponseProcessor> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class);
@Value("${transport.snmp.response_processing.parallelism_level}")
private Integer responseProcessingParallelismLevel;
@Value("${transport.snmp.underlying_protocol}")
private String snmpUnderlyingProtocol;
@PostConstruct
private void init() throws IOException {
queryingExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("snmp-querying"));
responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel);
initializeSnmp();
configureResponseDataMappers();
configureResponseProcessors();
log.info("SNMP transport service initialized");
}
private void initializeSnmp() throws IOException {
TransportMapping<?> transportMapping;
switch (snmpUnderlyingProtocol) {
case "udp":
transportMapping = new DefaultUdpTransportMapping();
break;
case "tcp":
transportMapping = new DefaultTcpTransportMapping();
break;
default:
throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported");
}
snmp = new Snmp(transportMapping);
snmp.listen();
USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
SecurityModels.getInstance().addSecurityModel(usm);
}
public void createQueryingTasks(DeviceSessionContext sessionContext) {
List<ScheduledFuture<?>> queryingTasks = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream()
.filter(communicationConfig -> communicationConfig instanceof RepeatingQueryingSnmpCommunicationConfig)
.map(config -> {
RepeatingQueryingSnmpCommunicationConfig repeatingCommunicationConfig = (RepeatingQueryingSnmpCommunicationConfig) config;
Long queryingFrequency = repeatingCommunicationConfig.getQueryingFrequencyMs();
return queryingExecutor.scheduleWithFixedDelay(() -> {
try {
if (sessionContext.isActive()) {
sendRequest(sessionContext, repeatingCommunicationConfig);
}
} catch (Exception e) {
log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString());
}
}, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS);
})
.collect(Collectors.toList());
sessionContext.getQueryingTasks().addAll(queryingTasks);
}
public void cancelQueryingTasks(DeviceSessionContext sessionContext) {
sessionContext.getQueryingTasks().forEach(task -> task.cancel(true));
sessionContext.getQueryingTasks().clear();
}
private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) {
sendRequest(sessionContext, communicationConfig, Collections.emptyMap());
}
private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
PDU request = pduService.createPdu(sessionContext, communicationConfig, values);
RequestInfo requestInfo = new RequestInfo(communicationConfig.getSpec(), communicationConfig.getAllMappings());
sendRequest(sessionContext, request, requestInfo);
}
private void sendRequest(DeviceSessionContext sessionContext, PDU request, RequestInfo requestInfo) {
if (request.size() > 0) {
log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings());
try {
snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext);
} catch (IOException e) {
log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString());
}
}
}
public void onAttributeUpdate(DeviceSessionContext sessionContext, TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification) {
sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream()
.filter(config -> config.getSpec() == SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING)
.findFirst()
.ifPresent(communicationConfig -> {
Map<String, String> sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString()
));
sendRequest(sessionContext, communicationConfig, sharedAttributes);
});
}
public void onToDeviceRpcRequest(DeviceSessionContext sessionContext, TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
SnmpMethod snmpMethod = SnmpMethod.valueOf(toDeviceRpcRequestMsg.getMethodName());
JsonObject params = JsonConverter.parse(toDeviceRpcRequestMsg.getParams()).getAsJsonObject();
String key = Optional.ofNullable(params.get("key")).map(JsonElement::getAsString).orElse(null);
String value = Optional.ofNullable(params.get("value")).map(JsonElement::getAsString).orElse(null);
if (value == null && snmpMethod == SnmpMethod.SET) {
throw new IllegalArgumentException("Value must be specified for SNMP method 'SET'");
}
SnmpCommunicationConfig communicationConfig = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream()
.filter(config -> config.getSpec() == SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No communication config found with RPC spec"));
SnmpMapping snmpMapping = communicationConfig.getAllMappings().stream()
.filter(mapping -> mapping.getKey().equals(key))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No SNMP mapping found in the config for specified key"));
String oid = snmpMapping.getOid();
DataType dataType = snmpMapping.getDataType();
PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType);
RequestInfo requestInfo = new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), communicationConfig.getSpec(), communicationConfig.getAllMappings());
sendRequest(sessionContext, request, requestInfo);
}
public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) {
((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext);
if (event.getError() != null) {
log.warn("SNMP response error: {}", event.getError().toString());
return;
}
PDU response = event.getResponse();
if (response == null) {
log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID());
return;
}
RequestInfo requestInfo = (RequestInfo) event.getUserObject();
responseProcessingExecutor.execute(() -> {
processResponse(sessionContext, response, requestInfo);
});
}
private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) {
ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationSpec());
if (responseProcessor == null) return;
JsonObject responseData = responseDataMappers.get(requestInfo.getCommunicationSpec()).map(response, requestInfo);
if (responseData.entrySet().isEmpty()) {
log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID());
return;
}
responseProcessor.process(responseData, requestInfo, sessionContext);
reportActivity(sessionContext.getSessionInfo());
}
private void configureResponseDataMappers() {
responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> {
JsonObject responseData = new JsonObject();
pduService.processPdu(pdu).forEach((oid, value) -> {
requestInfo.getResponseMappings().stream()
.filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString()))
.findFirst()
.ifPresent(snmpMapping -> {
pduService.processValue(snmpMapping.getKey(), snmpMapping.getDataType(), value, responseData);
});
});
return responseData;
});
ResponseDataMapper defaultResponseDataMapper = (pdu, requestInfo) -> {
return pduService.processPdu(pdu, requestInfo.getResponseMappings());
};
Arrays.stream(SnmpCommunicationSpec.values())
.forEach(communicationSpec -> {
responseDataMappers.putIfAbsent(communicationSpec, defaultResponseDataMapper);
});
}
private void configureResponseProcessors() {
responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestInfo, sessionContext) -> {
TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData);
transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null);
log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData);
});
responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestInfo, sessionContext) -> {
TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData);
transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null);
log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData);
});
responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestInfo, sessionContext) -> {
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(requestInfo.getRequestId())
.setPayload(JsonConverter.toJson(responseData))
.build();
transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null);
log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId());
});
}
private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder()
.setAttributeSubscription(true)
.setRpcSubscription(true)
.setLastActivityTime(System.currentTimeMillis())
.build(), TransportServiceCallback.EMPTY);
}
@Override
public String getName() {
return "SNMP";
}
@PreDestroy
public void shutdown() {
log.info("Stopping SNMP transport!");
if (queryingExecutor != null) {
queryingExecutor.shutdownNow();
}
if (responseProcessingExecutor != null) {
responseProcessingExecutor.shutdownNow();
}
if (snmp != null) {
try {
snmp.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
log.info("SNMP transport stopped!");
}
@Data
private static class RequestInfo {
private Integer requestId;
private SnmpCommunicationSpec communicationSpec;
private List<SnmpMapping> responseMappings;
public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec, List<SnmpMapping> responseMappings) {
this.requestId = requestId;
this.communicationSpec = communicationSpec;
this.responseMappings = responseMappings;
}
public RequestInfo(SnmpCommunicationSpec communicationSpec, List<SnmpMapping> responseMappings) {
this.communicationSpec = communicationSpec;
this.responseMappings = responseMappings;
}
}
private interface ResponseDataMapper {
JsonObject map(PDU pdu, RequestInfo requestInfo);
}
private interface ResponseProcessor {
void process(JsonObject responseData, RequestInfo requestInfo, DeviceSessionContext sessionContext);
}
}

146
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java

@ -0,0 +1,146 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp.session;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.transport.snmp.SnmpTransportContext;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class DeviceSessionContext extends DeviceAwareSessionContext implements SessionMsgListener, ResponseListener {
@Getter
private Target target;
private final String token;
@Getter
@Setter
private SnmpDeviceProfileTransportConfiguration profileTransportConfiguration;
@Getter
@Setter
private SnmpDeviceTransportConfiguration deviceTransportConfiguration;
@Getter
private final Device device;
private final SnmpTransportContext snmpTransportContext;
private final AtomicInteger msgIdSeq = new AtomicInteger(0);
@Getter
private boolean isActive = true;
@Getter
private final List<ScheduledFuture<?>> queryingTasks = new LinkedList<>();
public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token,
SnmpDeviceProfileTransportConfiguration profileTransportConfiguration,
SnmpDeviceTransportConfiguration deviceTransportConfiguration,
SnmpTransportContext snmpTransportContext) throws Exception {
super(UUID.randomUUID());
super.setDeviceId(device.getId());
super.setDeviceProfile(deviceProfile);
this.device = device;
this.token = token;
this.snmpTransportContext = snmpTransportContext;
this.profileTransportConfiguration = profileTransportConfiguration;
this.deviceTransportConfiguration = deviceTransportConfiguration;
initializeTarget(profileTransportConfiguration, deviceTransportConfiguration);
}
@Override
public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {
super.onDeviceProfileUpdate(newSessionInfo, deviceProfile);
if (isActive) {
snmpTransportContext.onDeviceProfileUpdated(deviceProfile, this);
}
}
@Override
public void onDeviceDeleted(DeviceId deviceId) {
snmpTransportContext.onDeviceDeleted(this);
}
@Override
public void onResponse(ResponseEvent event) {
if (isActive) {
snmpTransportContext.getSnmpTransportService().processResponseEvent(this, event);
}
}
public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) throws Exception {
log.trace("Initializing target for SNMP session of device {}", device);
this.target = snmpTransportContext.getSnmpAuthService().setUpSnmpTarget(profileTransportConfig, deviceTransportConfig);
log.debug("SNMP target initialized: {}", target);
}
public void close() {
isActive = false;
}
public String getToken() {
return token;
}
@Override
public int nextMsgId() {
return msgIdSeq.incrementAndGet();
}
@Override
public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) {
}
@Override
public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) {
snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification);
}
@Override
public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) {
}
@Override
public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest);
}
@Override
public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) {
}
}

196
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java

@ -0,0 +1,196 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.TransportMapping;
import org.snmp4j.agent.BaseAgent;
import org.snmp4j.agent.CommandProcessor;
import org.snmp4j.agent.DuplicateRegistrationException;
import org.snmp4j.agent.MOGroup;
import org.snmp4j.agent.ManagedObject;
import org.snmp4j.agent.mo.MOAccessImpl;
import org.snmp4j.agent.mo.MOScalar;
import org.snmp4j.agent.mo.snmp.RowStatus;
import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB;
import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB;
import org.snmp4j.agent.mo.snmp.SnmpTargetMIB;
import org.snmp4j.agent.mo.snmp.StorageType;
import org.snmp4j.agent.mo.snmp.VacmMIB;
import org.snmp4j.agent.security.MutableVACM;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.TransportMappings;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Scanner;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class SnmpDeviceSimulatorV2 extends BaseAgent {
public static class RequestProcessor extends CommandProcessor {
private final Consumer<CommandResponderEvent> processor;
public RequestProcessor(Consumer<CommandResponderEvent> processor) {
super(new OctetString(MPv3.createLocalEngineID()));
this.processor = processor;
}
@Override
public void processPdu(CommandResponderEvent event) {
processor.accept(event);
}
}
private final Target target;
private final Address address;
private Snmp snmp;
private final String password;
public SnmpDeviceSimulatorV2(int port, String password) throws IOException {
super(new File("conf.agent"), new File("bootCounter.agent"), new RequestProcessor(event -> {
System.out.println("aboba");
((Snmp) event.getSource()).cancel(event.getPDU(), event1 -> System.out.println("canceled"));
}));
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(password));
this.address = GenericAddress.parse("udp:0.0.0.0/" + port);
target.setAddress(address);
target.setRetries(2);
target.setTimeout(1500);
target.setVersion(SnmpConstants.version2c);
this.target = target;
this.password = password;
}
public void start() throws IOException {
init();
addShutdownHook();
getServer().addContext(new OctetString("public"));
finishInit();
run();
sendColdStartNotification();
snmp = new Snmp(transportMappings[0]);
}
public void setUpMappings(Map<String, String> oidToResponseMappings) {
unregisterManagedObject(getSnmpv2MIB());
oidToResponseMappings.forEach((oid, response) -> {
registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response)));
});
}
public void sendTrap(String host, int port, Map<String, String> values) throws IOException {
PDU pdu = new PDU();
pdu.addAll(values.entrySet().stream()
.map(entry -> new VariableBinding(new OID(entry.getKey()), new OctetString(entry.getValue())))
.collect(Collectors.toList()));
pdu.setType(PDU.TRAP);
CommunityTarget remoteTarget = (CommunityTarget) getTarget().clone();
remoteTarget.setAddress(new UdpAddress(host + "/" + port));
snmp.send(pdu, remoteTarget);
}
@Override
protected void registerManagedObjects() {
}
protected void registerManagedObject(ManagedObject mo) {
try {
server.register(mo, null);
} catch (DuplicateRegistrationException ex) {
throw new RuntimeException(ex);
}
}
protected void unregisterManagedObject(MOGroup moGroup) {
moGroup.unregisterMOs(server, getContext(moGroup));
}
@Override
protected void addNotificationTargets(SnmpTargetMIB targetMIB,
SnmpNotificationMIB notificationMIB) {
}
@Override
protected void addViews(VacmMIB vacm) {
vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString(
"cpublic"), new OctetString("v1v2group"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"),
SecurityModel.SECURITY_MODEL_ANY, SecurityLevel.NOAUTH_NOPRIV,
MutableVACM.VACM_MATCH_EXACT, new OctetString("fullReadView"),
new OctetString("fullWriteView"), new OctetString(
"fullNotifyView"), StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
}
protected void addUsmUser(USM usm) {
}
protected void initTransportMappings() {
transportMappings = new TransportMapping[]{TransportMappings.getInstance().createTransportMapping(address)};
}
protected void unregisterManagedObjects() {
}
protected void addCommunities(SnmpCommunityMIB communityMIB) {
Variable[] com2sec = new Variable[]{
new OctetString("public"),
new OctetString("cpublic"),
getAgent().getContextEngineID(),
new OctetString("public"),
new OctetString(),
new Integer32(StorageType.nonVolatile),
new Integer32(RowStatus.active)
};
SnmpCommunityMIB.SnmpCommunityEntryRow row = communityMIB.getSnmpCommunityEntry().createRow(
new OctetString("public2public").toSubIndex(true), com2sec);
communityMIB.getSnmpCommunityEntry().addRow(row);
}
public Target getTarget() {
return target;
}
}

745
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java

@ -0,0 +1,745 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.TransportMapping;
import org.snmp4j.agent.BaseAgent;
import org.snmp4j.agent.CommandProcessor;
import org.snmp4j.agent.DuplicateRegistrationException;
import org.snmp4j.agent.MOGroup;
import org.snmp4j.agent.ManagedObject;
import org.snmp4j.agent.mo.DefaultMOMutableRow2PC;
import org.snmp4j.agent.mo.DefaultMOTable;
import org.snmp4j.agent.mo.MOAccessImpl;
import org.snmp4j.agent.mo.MOColumn;
import org.snmp4j.agent.mo.MOMutableColumn;
import org.snmp4j.agent.mo.MOMutableTableModel;
import org.snmp4j.agent.mo.MOScalar;
import org.snmp4j.agent.mo.MOTableIndex;
import org.snmp4j.agent.mo.MOTableRow;
import org.snmp4j.agent.mo.MOTableSubIndex;
import org.snmp4j.agent.mo.ext.AgentppSimulationMib;
import org.snmp4j.agent.mo.snmp.RowStatus;
import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB;
import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB;
import org.snmp4j.agent.mo.snmp.SnmpTargetMIB;
import org.snmp4j.agent.mo.snmp.StorageType;
import org.snmp4j.agent.mo.snmp.TransportDomains;
import org.snmp4j.agent.mo.snmp.VacmMIB;
import org.snmp4j.agent.mo.snmp4j.example.Snmp4jHeartbeatMib;
import org.snmp4j.agent.security.MutableVACM;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.MessageProcessingModel;
import org.snmp4j.security.AuthHMAC192SHA256;
import org.snmp4j.security.AuthMD5;
import org.snmp4j.security.AuthSHA;
import org.snmp4j.security.PrivAES128;
import org.snmp4j.security.PrivAES192;
import org.snmp4j.security.PrivAES256;
import org.snmp4j.security.PrivDES;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.Gauge32;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.SMIConstants;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.TimeTicks;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.Variable;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.TransportMappings;
import org.snmp4j.util.ThreadPool;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* The TestAgent is a sample SNMP agent implementation of all
* features (MIB implementations) provided by the SNMP4J-Agent framework.
*
* Note, for snmp4s, this code is mostly a copy from snmp4j.
* And don't remove snmp users
*
*/
public class SnmpDeviceSimulatorV3 extends BaseAgent {
protected String address;
private Snmp4jHeartbeatMib heartbeatMIB;
private AgentppSimulationMib agentppSimulationMIB;
public SnmpDeviceSimulatorV3(CommandProcessor processor) throws IOException {
super(new File("SNMP4JTestAgentBC.cfg"), new File("SNMP4JTestAgentConfig.cfg"),
processor);
agent.setWorkerPool(ThreadPool.create("RequestPool", 4));
}
public void setUpMappings(Map<String, String> oidToResponseMappings) {
unregisterManagedObject(getSnmpv2MIB());
oidToResponseMappings.forEach((oid, response) -> {
registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response)));
});
}
protected void registerManagedObject(ManagedObject mo) {
try {
server.register(mo, null);
} catch (DuplicateRegistrationException ex) {
throw new RuntimeException(ex);
}
}
protected void unregisterManagedObject(MOGroup moGroup) {
moGroup.unregisterMOs(server, getContext(moGroup));
}
protected void registerManagedObjects() {
try {
server.register(createStaticIfTable(), null);
server.register(createStaticIfXTable(), null);
agentppSimulationMIB.registerMOs(server, null);
heartbeatMIB.registerMOs(server, null);
} catch (DuplicateRegistrationException ex) {
ex.printStackTrace();
}
}
protected void addNotificationTargets(SnmpTargetMIB targetMIB,
SnmpNotificationMIB notificationMIB) {
targetMIB.addDefaultTDomains();
targetMIB.addTargetAddress(new OctetString("notificationV2c"),
TransportDomains.transportDomainUdpIpv4,
new OctetString(new UdpAddress("127.0.0.1/162").getValue()),
200, 1,
new OctetString("notify"),
new OctetString("v2c"),
StorageType.permanent);
targetMIB.addTargetAddress(new OctetString("notificationV3"),
TransportDomains.transportDomainUdpIpv4,
new OctetString(new UdpAddress("127.0.0.1/1162").getValue()),
200, 1,
new OctetString("notify"),
new OctetString("v3notify"),
StorageType.permanent);
targetMIB.addTargetParams(new OctetString("v2c"),
MessageProcessingModel.MPv2c,
SecurityModel.SECURITY_MODEL_SNMPv2c,
new OctetString("cpublic"),
SecurityLevel.AUTH_PRIV,
StorageType.permanent);
targetMIB.addTargetParams(new OctetString("v3notify"),
MessageProcessingModel.MPv3,
SecurityModel.SECURITY_MODEL_USM,
new OctetString("v3notify"),
SecurityLevel.NOAUTH_NOPRIV,
StorageType.permanent);
notificationMIB.addNotifyEntry(new OctetString("default"),
new OctetString("notify"),
SnmpNotificationMIB.SnmpNotifyTypeEnum.inform,
StorageType.permanent);
}
protected void addViews(VacmMIB vacm) {
vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv1,
new OctetString("cpublic"),
new OctetString("v1v2group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c,
new OctetString("cpublic"),
new OctetString("v1v2group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("SHADES"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("MD5DES"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("TEST"),
new OctetString("v3test"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("SHA"),
new OctetString("v3restricted"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("SHAAES128"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("SHAAES192"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("SHAAES256"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("MD5AES128"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("MD5AES192"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("MD5AES256"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("aboba"),
new OctetString("v3group"),
StorageType.nonVolatile);
//============================================//
// agent5-auth-priv
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("agent5"),
new OctetString("v3group"),
StorageType.nonVolatile);
//===========================================//
// agent002
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("agent002"),
new OctetString("v3group"),
StorageType.nonVolatile);
//===========================================//
// user001-auth-no-priv
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("user001"),
new OctetString("group001"),
StorageType.nonVolatile);
//===========================================//
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("v3notify"),
new OctetString("v3group"),
StorageType.nonVolatile);
//===========================================//
// group auth no priv
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("v3notify-auth"),
new OctetString("group001"),
StorageType.nonVolatile);
//===========================================//
// my conf
vacm.addAccess(new OctetString("group001"), new OctetString("public"),
SecurityModel.SECURITY_MODEL_USM,
SecurityLevel.AUTH_NOPRIV,
MutableVACM.VACM_MATCH_EXACT,
new OctetString("fullReadView"),
new OctetString("fullWriteView"),
new OctetString("fullNotifyView"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"),
SecurityModel.SECURITY_MODEL_ANY,
SecurityLevel.NOAUTH_NOPRIV,
MutableVACM.VACM_MATCH_EXACT,
new OctetString("fullReadView"),
new OctetString("fullWriteView"),
new OctetString("fullNotifyView"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v3group"), new OctetString(),
SecurityModel.SECURITY_MODEL_USM,
SecurityLevel.AUTH_PRIV,
MutableVACM.VACM_MATCH_EXACT,
new OctetString("fullReadView"),
new OctetString("fullWriteView"),
new OctetString("fullNotifyView"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v3restricted"), new OctetString(),
SecurityModel.SECURITY_MODEL_USM,
SecurityLevel.NOAUTH_NOPRIV,
MutableVACM.VACM_MATCH_EXACT,
new OctetString("restrictedReadView"),
new OctetString("restrictedWriteView"),
new OctetString("restrictedNotifyView"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v3test"), new OctetString(),
SecurityModel.SECURITY_MODEL_USM,
SecurityLevel.AUTH_PRIV,
MutableVACM.VACM_MATCH_EXACT,
new OctetString("testReadView"),
new OctetString("testWriteView"),
new OctetString("testNotifyView"),
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("fullWriteView"), new OID("1.3"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("fullNotifyView"), new OID("1.3"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("restrictedReadView"),
new OID("1.3.6.1.2"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("restrictedWriteView"),
new OID("1.3.6.1.2.1"),
new OctetString(),
VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"),
new OID("1.3.6.1.2"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"),
new OID("1.3.6.1.6.3.1"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("testReadView"),
new OID("1.3.6.1.2"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("testReadView"),
new OID("1.3.6.1.2.1.1"),
new OctetString(), VacmMIB.vacmViewExcluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("testWriteView"),
new OID("1.3.6.1.2.1"),
new OctetString(),
VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
vacm.addViewTreeFamily(new OctetString("testNotifyView"),
new OID("1.3.6.1.2"),
new OctetString(), VacmMIB.vacmViewIncluded,
StorageType.nonVolatile);
}
protected void addUsmUser(USM usm) {
UsmUser user = new UsmUser(new OctetString("SHADES"),
AuthSHA.ID,
new OctetString("SHADESAuthPassword"),
PrivDES.ID,
new OctetString("SHADESPrivPassword"));
// usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
usm.addUser(user.getSecurityName(), null, user);
user = new UsmUser(new OctetString("TEST"),
AuthSHA.ID,
new OctetString("maplesyrup"),
PrivDES.ID,
new OctetString("maplesyrup"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("SHA"),
AuthSHA.ID,
new OctetString("SHAAuthPassword"),
null,
null);
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("SHADES"),
AuthSHA.ID,
new OctetString("SHADESAuthPassword"),
PrivDES.ID,
new OctetString("SHADESPrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("MD5DES"),
AuthMD5.ID,
new OctetString("MD5DESAuthPassword"),
PrivDES.ID,
new OctetString("MD5DESPrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("SHAAES128"),
AuthSHA.ID,
new OctetString("SHAAES128AuthPassword"),
PrivAES128.ID,
new OctetString("SHAAES128PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("SHAAES192"),
AuthSHA.ID,
new OctetString("SHAAES192AuthPassword"),
PrivAES192.ID,
new OctetString("SHAAES192PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("SHAAES256"),
AuthSHA.ID,
new OctetString("SHAAES256AuthPassword"),
PrivAES256.ID,
new OctetString("SHAAES256PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("MD5AES128"),
AuthMD5.ID,
new OctetString("MD5AES128AuthPassword"),
PrivAES128.ID,
new OctetString("MD5AES128PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("MD5AES192"),
AuthHMAC192SHA256.ID,
new OctetString("MD5AES192AuthPassword"),
PrivAES192.ID,
new OctetString("MD5AES192PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
//==============================================================
user = new UsmUser(new OctetString("MD5AES256"),
AuthMD5.ID,
new OctetString("MD5AES256AuthPassword"),
PrivAES256.ID,
new OctetString("MD5AES256PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
user = new UsmUser(new OctetString("MD5AES256"),
AuthMD5.ID,
new OctetString("MD5AES256AuthPassword"),
PrivAES256.ID,
new OctetString("MD5AES256PrivPassword"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
OctetString securityName = new OctetString("aboba");
OctetString authenticationPassphrase = new OctetString("abobaaboba");
OctetString privacyPassphrase = new OctetString("abobaaboba");
OID authenticationProtocol = AuthSHA.ID;
OID privacyProtocol = PrivDES.ID; // FIXME: to config
user = new UsmUser(securityName, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase);
usm.addUser(user);
//===============================================================//
user = new UsmUser(new OctetString("agent5"),
AuthSHA.ID,
new OctetString("authpass"),
PrivDES.ID,
new OctetString("privpass"));
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
//===============================================================//
// user001
user = new UsmUser(new OctetString("user001"),
AuthSHA.ID,
new OctetString("authpass"),
null, null);
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
//===============================================================//
// user002
user = new UsmUser(new OctetString("user001"),
null,
null,
null, null);
usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user);
//===============================================================//
user = new UsmUser(new OctetString("v3notify"),
null,
null,
null,
null);
usm.addUser(user.getSecurityName(), null, user);
this.usm = usm;
}
private static DefaultMOTable createStaticIfXTable() {
MOTableSubIndex[] subIndexes =
new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) };
MOTableIndex indexDef = new MOTableIndex(subIndexes, false);
MOColumn[] columns = new MOColumn[19];
int c = 0;
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING,
MOAccessImpl.ACCESS_READ_ONLY); // ifName
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifInMulticastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifInBroadcastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifOutMulticastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifOutBroadcastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCInOctets
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCInUcastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCInMulticastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCInBroadcastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutOctets
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutUcastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutMulticastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_COUNTER32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutBroadcastPkts
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_WRITE); // ifLinkUpDownTrapEnable
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_GAUGE32,
MOAccessImpl.ACCESS_READ_ONLY); // ifHighSpeed
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_WRITE); // ifPromiscuousMode
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_ONLY); // ifConnectorPresent
columns[c++] =
new MOMutableColumn(c, SMIConstants.SYNTAX_OCTET_STRING, // ifAlias
MOAccessImpl.ACCESS_READ_WRITE, null);
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_TIMETICKS,
MOAccessImpl.ACCESS_READ_ONLY); // ifCounterDiscontinuityTime
DefaultMOTable ifXTable =
new DefaultMOTable(new OID("1.3.6.1.2.1.31.1.1.1"), indexDef, columns);
MOMutableTableModel model = (MOMutableTableModel) ifXTable.getModel();
Variable[] rowValues1 = new Variable[] {
new OctetString("Ethernet-0"),
new Integer32(1),
new Integer32(2),
new Integer32(3),
new Integer32(4),
new Integer32(5),
new Integer32(6),
new Integer32(7),
new Integer32(8),
new Integer32(9),
new Integer32(10),
new Integer32(11),
new Integer32(12),
new Integer32(13),
new Integer32(14),
new Integer32(15),
new Integer32(16),
new OctetString("My eth"),
new TimeTicks(1000)
};
Variable[] rowValues2 = new Variable[] {
new OctetString("Loopback"),
new Integer32(21),
new Integer32(22),
new Integer32(23),
new Integer32(24),
new Integer32(25),
new Integer32(26),
new Integer32(27),
new Integer32(28),
new Integer32(29),
new Integer32(30),
new Integer32(31),
new Integer32(32),
new Integer32(33),
new Integer32(34),
new Integer32(35),
new Integer32(36),
new OctetString("My loop"),
new TimeTicks(2000)
};
model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1));
model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2));
ifXTable.setVolatile(true);
return ifXTable;
}
private static DefaultMOTable createStaticIfTable() {
MOTableSubIndex[] subIndexes =
new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) };
MOTableIndex indexDef = new MOTableIndex(subIndexes, false);
MOColumn[] columns = new MOColumn[8];
int c = 0;
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_ONLY); // ifIndex
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING,
MOAccessImpl.ACCESS_READ_ONLY); // ifDescr
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_ONLY); // ifType
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_ONLY); // ifMtu
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_GAUGE32,
MOAccessImpl.ACCESS_READ_ONLY); // ifSpeed
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING,
MOAccessImpl.ACCESS_READ_ONLY); // ifPhysAddress
columns[c++] =
new MOMutableColumn(c, SMIConstants.SYNTAX_INTEGER, // ifAdminStatus
MOAccessImpl.ACCESS_READ_WRITE, null);
columns[c++] =
new MOColumn(c, SMIConstants.SYNTAX_INTEGER,
MOAccessImpl.ACCESS_READ_ONLY); // ifOperStatus
DefaultMOTable ifTable =
new DefaultMOTable(new OID("1.3.6.1.2.1.2.2.1"), indexDef, columns);
MOMutableTableModel model = (MOMutableTableModel) ifTable.getModel();
Variable[] rowValues1 = new Variable[] {
new Integer32(1),
new OctetString("eth0"),
new Integer32(6),
new Integer32(1500),
new Gauge32(100000000),
new OctetString("00:00:00:00:01"),
new Integer32(1),
new Integer32(1)
};
Variable[] rowValues2 = new Variable[] {
new Integer32(2),
new OctetString("loopback"),
new Integer32(24),
new Integer32(1500),
new Gauge32(10000000),
new OctetString("00:00:00:00:02"),
new Integer32(1),
new Integer32(1)
};
model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1));
model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2));
ifTable.setVolatile(true);
return ifTable;
}
private static DefaultMOTable createStaticSnmp4sTable() {
MOTableSubIndex[] subIndexes =
new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) };
MOTableIndex indexDef = new MOTableIndex(subIndexes, false);
MOColumn[] columns = new MOColumn[8];
int c = 0;
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_NULL, MOAccessImpl.ACCESS_READ_ONLY); // testNull
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // testBoolean
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // ifType
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // ifMtu
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, MOAccessImpl.ACCESS_READ_ONLY); // ifSpeed
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, MOAccessImpl.ACCESS_READ_ONLY); //ifPhysAddress
columns[c++] = new MOMutableColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_WRITE,
null);
// ifAdminStatus
columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY);
// ifOperStatus
DefaultMOTable ifTable =
new DefaultMOTable(new OID("1.3.6.1.4.1.50000.1.1"), indexDef, columns);
MOMutableTableModel model = (MOMutableTableModel) ifTable.getModel();
Variable[] rowValues1 = new Variable[] {
new Integer32(1),
new OctetString("eth0"),
new Integer32(6),
new Integer32(1500),
new Gauge32(100000000),
new OctetString("00:00:00:00:01"),
new Integer32(1),
new Integer32(1)
};
Variable[] rowValues2 = new Variable[] {
new Integer32(2),
new OctetString("loopback"),
new Integer32(24),
new Integer32(1500),
new Gauge32(10000000),
new OctetString("00:00:00:00:02"),
new Integer32(1),
new Integer32(1)
};
model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1));
model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2));
ifTable.setVolatile(true);
return ifTable;
}
protected void initTransportMappings() throws IOException {
transportMappings = new TransportMapping[2];
Address addr = GenericAddress.parse(address);
TransportMapping tm =
TransportMappings.getInstance().createTransportMapping(addr);
transportMappings[0] = tm;
transportMappings[1] = new DefaultTcpTransportMapping(new TcpAddress(address));
}
public void start(String ip, String port) throws IOException {
address = ip + "/" + port;
//BasicConfigurator.configure();
init();
addShutdownHook();
// loadConfig(ImportModes.REPLACE_CREATE);
getServer().addContext(new OctetString("public"));
finishInit();
run();
sendColdStartNotification();
}
protected void unregisterManagedObjects() {
// here we should unregister those objects previously registered...
}
protected void addCommunities(SnmpCommunityMIB communityMIB) {
Variable[] com2sec = new Variable[] {
new OctetString("public"), // community name
new OctetString("cpublic"), // security name
getAgent().getContextEngineID(), // local engine ID
new OctetString("public"), // default context name
new OctetString(), // transport tag
new Integer32(StorageType.nonVolatile), // storage type
new Integer32(RowStatus.active) // row status
};
MOTableRow row =
communityMIB.getSnmpCommunityEntry().createRow(
new OctetString("public2public").toSubIndex(true), com2sec);
communityMIB.getSnmpCommunityEntry().addRow((SnmpCommunityMIB.SnmpCommunityEntryRow) row);
// snmpCommunityMIB.setSourceAddressFiltering(true);
}
protected void registerSnmpMIBs() {
heartbeatMIB = new Snmp4jHeartbeatMib(super.getNotificationOriginator(),
new OctetString(),
super.snmpv2MIB.getSysUpTime());
agentppSimulationMIB = new AgentppSimulationMib();
super.registerSnmpMIBs();
}
protected void initMessageDispatcher() {
this.dispatcher = new MessageDispatcherImpl();
this.mpv3 = new MPv3(this.agent.getContextEngineID().getValue());
this.usm = new USM(SecurityProtocols.getInstance(), this.agent.getContextEngineID(), this.updateEngineBoots());
SecurityModels.getInstance().addSecurityModel(this.usm);
SecurityProtocols.getInstance().addDefaultProtocols();
this.dispatcher.addMessageProcessingModel(new MPv1());
this.dispatcher.addMessageProcessingModel(new MPv2c());
this.dispatcher.addMessageProcessingModel(this.mpv3);
this.initSnmpSession();
}
}

49
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.snmp;
import java.io.IOException;
import java.util.Map;
import java.util.Scanner;
public class SnmpTestV2 {
public static void main(String[] args) throws IOException {
SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public");
device.start();
device.setUpMappings(Map.of(
".1.3.6.1.2.1.1.1.50", "12",
".1.3.6.1.2.1.2.1.52", "56",
".1.3.6.1.2.1.3.1.54", "yes",
".1.3.6.1.2.1.7.1.58", ""
));
// while (true) {
// new Scanner(System.in).nextLine();
// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12"));
// System.out.println("sent");
// }
// Snmp snmp = new Snmp(device.transportMappings[0]);
// device.snmp.addCommandResponder(event -> {
// System.out.println(event);
// });
new Scanner(System.in).nextLine();
}
}

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

Loading…
Cancel
Save