Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/power-mode

pull/4746/head
YevhenBondarenko 5 years ago
parent
commit
ea3d273625
  1. 24
      application/src/main/data/json/system/oauth2_config_templates/apple_config.json
  2. 2
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  3. 10
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 4
      application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
  5. 14
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  6. 14
      application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  8. 19
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  9. 158
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  10. 101
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  13. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java
  14. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java
  15. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java
  16. 1
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  18. 8
      application/src/main/resources/thingsboard.yml
  19. 19
      application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java
  20. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  21. 81
      application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java
  22. 8
      application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java
  23. 2
      application/src/test/resources/logback.xml
  24. 37
      common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java
  25. 15
      common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java
  26. 13
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java
  27. 9
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java
  28. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java
  29. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java
  30. 2
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java
  31. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java
  32. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java
  33. 2
      common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java
  34. 242
      common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java
  35. 12
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  36. 211
      common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java
  37. 72
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  38. 16
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  39. 150
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java
  40. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  41. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  42. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  43. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java
  44. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  45. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java
  46. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  47. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  48. 30
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java
  49. 32
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  50. 561
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java
  51. 177
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java
  52. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java
  53. 37
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  54. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  55. 41
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  56. 483
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java
  57. 41
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java
  58. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java
  59. 62
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  60. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java
  61. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java
  62. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java
  63. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java
  64. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java
  65. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java
  66. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java
  67. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java
  68. 35
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MUplinkTargetedCallback.java
  69. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java
  70. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java
  71. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java
  72. 56
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java
  73. 26
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/LwM2MTelemetryLogService.java
  74. 440
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  75. 111
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java
  76. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaState.java
  77. 24
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java
  78. 48
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareDeliveryMethod.java
  79. 75
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateResult.java
  80. 56
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java
  81. 48
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MFirmwareUpdateStrategy.java
  82. 48
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MSoftwareUpdateStrategy.java
  83. 84
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java
  84. 62
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java
  85. 26
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  86. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java
  87. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java
  88. 387
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  89. 39
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mTypeServer.java
  90. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
  91. 4
      common/util/pom.xml
  92. 57
      common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java
  93. 16
      dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
  94. 33
      dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java
  95. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java
  96. 3
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java
  97. 2
      dao/src/main/resources/sql/schema-entities-hsql.sql
  98. 2
      dao/src/main/resources/sql/schema-entities.sql
  99. 1
      dao/src/test/resources/application-test.properties
  100. 30
      msa/js-executor/api/httpServer.js

24
application/src/main/data/json/system/oauth2_config_templates/apple_config.json

@ -0,0 +1,24 @@
{
"providerId": "Apple",
"additionalInfo": null,
"accessTokenUri": "https://appleid.apple.com/auth/token",
"authorizationUri": "https://appleid.apple.com/auth/authorize?response_mode=form_post",
"scope": ["email","openid","name"],
"jwkSetUri": "https://appleid.apple.com/auth/keys",
"userInfoUri": null,
"clientAuthenticationMethod": "POST",
"userNameAttributeName": "email",
"mapperConfig": {
"type": "APPLE",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "firstName",
"lastNameAttributeKey": "lastName",
"tenantNameStrategy": "DOMAIN"
}
},
"comment": null,
"loginButtonIcon": "apple-logo",
"loginButtonLabel": "Apple",
"helpLink": "https://developer.apple.com/sign-in-with-apple/get-started/"
}

2
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -92,7 +92,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
client_secret varchar(2048),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),

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

@ -782,15 +782,17 @@ public class DeviceController extends BaseController {
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET)
@RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType,
@RequestParam String deviceProfileId) throws ThingsboardException {
public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType,
@PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException {
checkParameter("OtaPackageType", otaPackageType);
checkParameter("DeviceProfileId", deviceProfileId);
try {
return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(
getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType));
getTenantId(),
new DeviceProfileId(UUID.fromString(deviceProfileId)),
OtaPackageType.valueOf(otaPackageType));
} catch (Exception e) {
throw handleException(e);
}

4
application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java

@ -128,7 +128,7 @@ public class OtaPackageController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST)
@ResponseBody
public OtaPackage saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId,
public OtaPackageInfo saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId,
@RequestParam(required = false) String checksum,
@RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr,
@RequestBody MultipartFile file) throws ThingsboardException {
@ -160,7 +160,7 @@ public class OtaPackageController extends BaseController {
otaPackage.setContentType(file.getContentType());
otaPackage.setData(ByteBuffer.wrap(bytes));
otaPackage.setDataSize((long) bytes.length);
OtaPackage savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage);
OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage);
logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null);
return savedOtaPackage;
} catch (Exception e) {

14
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -74,6 +74,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@ -86,6 +87,7 @@ public class RuleChainController extends BaseController {
public static final String RULE_NODE_ID = "ruleNodeId";
private static final ObjectMapper objectMapper = new ObjectMapper();
public static final int TIMEOUT = 20;
@Autowired
private InstallScripts installScripts;
@ -388,25 +390,25 @@ public class RuleChainController extends BaseController {
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
switch (scriptType) {
case "update":
output = msgToOutput(engine.executeUpdate(inMsg));
output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
break;
case "generate":
output = msgToOutput(engine.executeGenerate(inMsg));
output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
break;
case "filter":
boolean result = engine.executeFilter(inMsg);
boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
output = Boolean.toString(result);
break;
case "switch":
Set<String> states = engine.executeSwitch(inMsg);
Set<String> states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
output = objectMapper.writeValueAsString(states);
break;
case "json":
JsonNode json = engine.executeJson(inMsg);
JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
output = objectMapper.writeValueAsString(json);
break;
case "string":
output = engine.executeToString(inMsg);
output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
break;
default:
throw new IllegalArgumentException("Unsupported script type: " + scriptType);

14
application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java

@ -30,6 +30,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@ -84,8 +85,10 @@ public abstract class AbstractJsInvokeService implements JsInvokeService {
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1);
return doInvokeFunction(scriptId, functionName, args);
} else {
return Futures.immediateFailedFuture(
new RuntimeException("Script invocation is blocked due to maximum error count " + getMaxErrors() + "!"));
String message = "Script invocation is blocked due to maximum error count "
+ getMaxErrors() + ", scriptId " + scriptId + "!";
log.warn(message);
return Futures.immediateFailedFuture(new RuntimeException(message));
}
} else {
return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!"));
@ -117,8 +120,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService {
protected abstract long getMaxBlacklistDuration();
protected void onScriptExecutionError(UUID scriptId) {
disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet();
protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) {
DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo());
log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}",
disableListInfo.get(), scriptId, t, t.getCause(), scriptBody);
disableListInfo.incrementAndGet();
}
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {

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

@ -160,7 +160,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
return ((Invocable) engine).invokeFunction(functionName, args);
}
} catch (Exception e) {
onScriptExecutionError(scriptId);
onScriptExecutionError(scriptId, e, functionName);
throw new ExecutionException(e);
}
});

19
application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java

@ -18,7 +18,6 @@ package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -26,6 +25,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
@ -161,7 +161,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Override
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
String scriptBody = scriptIdToBodysMap.get(scriptId);
log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout);
final String scriptBody = scriptIdToBodysMap.get(scriptId);
if (scriptBody == null) {
return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!"));
}
@ -170,7 +171,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
.setScriptIdLSB(scriptId.getLeastSignificantBits())
.setFunctionName(functionName)
.setTimeout((int) maxRequestsTimeout)
.setScriptBody(scriptIdToBodysMap.get(scriptId));
.setScriptBody(scriptBody);
for (Object arg : args) {
jsRequestBuilder.addArgs(arg.toString());
@ -180,6 +181,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
.setInvokeRequest(jsRequestBuilder.build())
.build();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
if (maxRequestsTimeout > 0) {
future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
@ -193,7 +197,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Override
public void onFailure(Throwable t) {
onScriptExecutionError(scriptId);
onScriptExecutionError(scriptId, t, scriptBody);
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
queueTimeoutMsgs.incrementAndGet();
}
@ -201,13 +205,16 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
}
}, callbackExecutor);
return Futures.transform(future, response -> {
stopWatch.stop();
log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey());
JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse();
if (invokeResult.getSuccess()) {
return invokeResult.getResult();
} else {
onScriptExecutionError(scriptId);
final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails());
onScriptExecutionError(scriptId, e, scriptBody);
log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
throw new RuntimeException(invokeResult.getErrorDetails());
throw e;
}
}, callbackExecutor);
}

158
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -18,12 +18,12 @@ package org.thingsboard.server.service.script;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
@ -32,6 +32,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -102,140 +103,115 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData);
} catch (Throwable th) {
th.printStackTrace();
throw new RuntimeException("Failed to unbind message data from javascript result", th);
}
}
@Override
public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (result.isObject()) {
return Collections.singletonList(unbindMsg(result, msg));
} else if (result.isArray()){
List<TbMsg> res = new ArrayList<>(result.size());
result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
return res;
} else {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
return Futures.transformAsync(result,
json -> executeUpdateTransform(msg, json),
MoreExecutors.directExecutor());
}
ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) {
if (json.isObject()) {
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
} else if (json.isArray()) {
List<TbMsg> res = new ArrayList<>(json.size());
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
return Futures.immediateFuture(res);
}
log.warn("Wrong result type: {}", json.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
}
@Override
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
return Futures.transformAsync(result, json -> {
if (json.isObject()) {
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
} else if (json.isArray()){
List<TbMsg> res = new ArrayList<>(json.size());
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
return Futures.immediateFuture(res);
}
else{
log.warn("Wrong result type: {}", json.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
}
}, MoreExecutors.directExecutor());
public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) {
return Futures.transformAsync(executeScriptAsync(prevMsg),
result -> executeGenerateTransform(prevMsg, result),
MoreExecutors.directExecutor());
}
@Override
public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException {
JsonNode result = executeScript(prevMsg);
ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) {
if (!result.isObject()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
}
return unbindMsg(result, prevMsg);
return Futures.immediateFuture(unbindMsg(result, prevMsg));
}
@Override
public JsonNode executeJson(TbMsg msg) throws ScriptException {
return executeScript(msg);
}
@Override
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException {
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
return executeScriptAsync(msg);
}
@Override
public String executeToString(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (!result.isTextual()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
}
return result.asText();
public ListenableFuture<String> executeToStringAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg),
this::executeToStringTransform,
MoreExecutors.directExecutor());
}
@Override
public boolean executeFilter(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (!result.isBoolean()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
ListenableFuture<String> executeToStringTransform(JsonNode result) {
if (result.isTextual()) {
return Futures.immediateFuture(result.asText());
}
return result.asBoolean();
log.warn("Wrong result type: {}", result.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
}
@Override
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
return Futures.transformAsync(result, json -> {
if (!json.isBoolean()) {
log.warn("Wrong result type: {}", json.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
} else {
return Futures.immediateFuture(json.asBoolean());
}
}, MoreExecutors.directExecutor());
return Futures.transformAsync(executeScriptAsync(msg),
this::executeFilterTransform,
MoreExecutors.directExecutor());
}
@Override
public Set<String> executeSwitch(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
ListenableFuture<Boolean> executeFilterTransform(JsonNode json) {
if (json.isBoolean()) {
return Futures.immediateFuture(json.asBoolean());
}
log.warn("Wrong result type: {}", json.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
}
ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) {
if (result.isTextual()) {
return Collections.singleton(result.asText());
} else if (result.isArray()) {
Set<String> nextStates = Sets.newHashSet();
return Futures.immediateFuture(Collections.singleton(result.asText()));
}
if (result.isArray()) {
Set<String> nextStates = new HashSet<>();
for (JsonNode val : result) {
if (!val.isTextual()) {
log.warn("Wrong result type: {}", val.getNodeType());
throw new ScriptException("Wrong result type: " + val.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + val.getNodeType()));
} else {
nextStates.add(val.asText());
}
}
return nextStates;
} else {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
return Futures.immediateFuture(nextStates);
}
log.warn("Wrong result type: {}", result.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
}
private JsonNode executeScript(TbMsg msg) throws ScriptException {
try {
String[] inArgs = prepareArgs(msg);
String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
return mapper.readTree(eval);
} catch (ExecutionException e) {
if (e.getCause() instanceof ScriptException) {
throw (ScriptException) e.getCause();
} else if (e.getCause() instanceof RuntimeException) {
throw new ScriptException(e.getCause().getMessage());
} else {
throw new ScriptException(e);
}
} catch (Exception e) {
throw new ScriptException(e);
}
@Override
public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg),
this::executeSwitchTransform,
MoreExecutors.directExecutor()); //usually runs in a callbackExecutor
}
private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) {
ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) {
log.trace("execute script async, msg {}", msg);
String[] inArgs = prepareArgs(msg);
return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]),
return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]);
}
ListenableFuture<JsonNode> executeScriptAsync(CustomerId customerId, Object... args) {
return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args),
o -> {
try {
return Futures.immediateFuture(mapper.readTree(o.toString()));

101
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java

@ -0,0 +1,101 @@
/**
* 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.service.security.auth.oauth2;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Service(value = "appleOAuth2ClientMapper")
@Slf4j
public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
private static final String USER = "user";
private static final String NAME = "name";
private static final String FIRST_NAME = "firstName";
private static final String LAST_NAME = "lastName";
private static final String EMAIL = "email";
@Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
Map<String, Object> attributes = updateAttributesFromRequestParams(request, token.getPrincipal().getAttributes());
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
}
private static Map<String, Object> updateAttributesFromRequestParams(HttpServletRequest request, Map<String, Object> attributes) {
Map<String, Object> updated = attributes;
MultiValueMap<String, String> params = toMultiMap(request.getParameterMap());
String userValue = params.getFirst(USER);
if (StringUtils.hasText(userValue)) {
JsonNode user = null;
try {
user = JacksonUtil.toJsonNode(userValue);
} catch (Exception e) {}
if (user != null) {
updated = new HashMap<>(attributes);
if (user.has(NAME)) {
JsonNode name = user.get(NAME);
if (name.isObject()) {
JsonNode firstName = name.get(FIRST_NAME);
if (firstName != null && firstName.isTextual()) {
updated.put(FIRST_NAME, firstName.asText());
}
JsonNode lastName = name.get(LAST_NAME);
if (lastName != null && lastName.isTextual()) {
updated.put(LAST_NAME, lastName.asText());
}
}
}
if (user.has(EMAIL)) {
JsonNode email = user.get(EMAIL);
if (email != null && email.isTextual()) {
updated.put(EMAIL, email.asText());
}
}
}
}
return updated;
}
private static MultiValueMap<String, String> toMultiMap(Map<String, String[]> map) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(map.size());
map.forEach((key, values) -> {
if (values.length > 0) {
for (String value : values) {
params.add(key, value);
}
}
});
return params;
}
}

3
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Service(value = "basicOAuth2ClientMapper")
@ -30,7 +31,7 @@ import java.util.Map;
public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
Map<String, Object> attributes = token.getPrincipal().getAttributes();
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;
@Service(value = "customOAuth2ClientMapper")
@Slf4j
public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
@ -39,7 +41,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);

3
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java

@ -29,6 +29,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
@ -46,7 +47,7 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private OAuth2Configuration oAuth2Configuration;
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java

@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;
public interface OAuth2ClientMapper {
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration);
SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration);
}

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java

@ -37,6 +37,10 @@ public class OAuth2ClientMapperProvider {
@Qualifier("githubOAuth2ClientMapper")
private OAuth2ClientMapper githubOAuth2ClientMapper;
@Autowired
@Qualifier("appleOAuth2ClientMapper")
private OAuth2ClientMapper appleOAuth2ClientMapper;
public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) {
switch (oauth2MapperType) {
case CUSTOM:
@ -45,6 +49,8 @@ public class OAuth2ClientMapperProvider {
return basicOAuth2ClientMapper;
case GITHUB:
return githubOAuth2ClientMapper;
case APPLE:
return appleOAuth2ClientMapper;
default:
throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!");
}

1
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java

@ -36,7 +36,6 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component(value = "oauth2AuthenticationFailureHandler")
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

2
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -90,7 +90,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
token.getAuthorizedClientRegistrationId(),
token.getPrincipal().getName());
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
registration);
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);

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

@ -332,6 +332,7 @@ actors:
cache:
# caffeine or redis
type: "${CACHE_TYPE:caffeine}"
maximumPoolSize: "${CACHE_MAXIMUM_POOL_SIZE:16}" # max pool size to process futures that calls the external cache
attributes:
# make sure that if cache.type is 'redis' and cache.attributes.enabled is 'true' that you change 'maxmemory-policy' Redis config property to 'allkeys-lru', 'allkeys-lfu' or 'allkeys-random'
enabled: "${CACHE_ATTRIBUTES_ENABLED:true}"
@ -677,12 +678,11 @@ transport:
timeout: "${LWM2M_TIMEOUT:120000}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
uplink_pool_size: "${LWM2M_UPLINK_POOL_SIZE:10}"
downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}"
ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}"
registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"

19
application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java

@ -141,10 +141,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
Assert.assertEquals(FILE_NAME, savedFirmware.getFileName());
Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType());
Assert.assertEquals(CHECKSUM_ALGORITHM, savedFirmware.getChecksumAlgorithm().name());
Assert.assertEquals(CHECKSUM, savedFirmware.getChecksum());
}
@Test
@ -189,11 +191,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
OtaPackage foundFirmware = doGet("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString(), OtaPackage.class);
Assert.assertNotNull(foundFirmware);
Assert.assertEquals(savedFirmware, foundFirmware);
Assert.assertEquals(savedFirmware, new OtaPackageInfo(foundFirmware));
Assert.assertEquals(DATA, foundFirmware.getData());
}
@Test
@ -228,8 +231,8 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
if (i > 100) {
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
otaPackages.add(new OtaPackageInfo(savedFirmware));
OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
otaPackages.add(savedFirmware);
} else {
otaPackages.add(savedFirmwareInfo);
}
@ -271,7 +274,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
if (i > 100) {
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
savedFirmwareInfo = new OtaPackageInfo(savedFirmware);
otaPackagesWithData.add(savedFirmwareInfo);
}
@ -318,11 +321,11 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
}
protected OtaPackage savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
postRequest.file(content);
setJwtToken(postRequest);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackage.class);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class);
}
}

4
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -103,7 +103,9 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
" }\n" +
" },\n" +
" \"clientLwM2mSettings\": {\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" \"clientOnlyObserveAfterConnect\": 1,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"swUpdateStrategy\": 1\n" +
" }\n" +
"}";

81
application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java

@ -17,11 +17,14 @@ package org.thingsboard.server.transport.lwm2m;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@ -43,15 +46,15 @@ import java.util.List;
import static org.eclipse.leshan.client.object.Security.noSec;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
private final int PORT = 5685;
private final Security SECURITY = noSec("coap://localhost:" + PORT, 123);
private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
private final String ENDPOINT = "deviceAEndpoint";
private final String ENDPOINT = "noSecEndpoint";
@NotNull
private Device createDevice() throws Exception {
Device device = new Device();
device.setName("Device A");
@ -74,6 +77,29 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
return device;
}
private OtaPackageInfo createFirmware() throws Exception {
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
OtaPackageInfo firmwareInfo = new OtaPackageInfo();
firmwareInfo.setDeviceProfileId(deviceProfile.getId());
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle("My firmware");
firmwareInfo.setVersion("v1.0");
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1});
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256");
}
protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
postRequest.file(content);
setJwtToken(postRequest);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class);
}
@Test
public void testConnectAndObserveTelemetry() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
@ -111,4 +137,53 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
client.destroy();
}
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
Device device = createDevice();
OtaPackageInfo firmware = createFirmware();
LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT);
client.init(SECURITY, COAP_CONFIG);
Thread.sleep(1000);
device.setFirmwareId(firmware.getId());
device = doPost("/api/device", device, Device.class);
Thread.sleep(1000);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "fw_state")));
EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("fw_state");
Assert.assertEquals("FAILED", tsValue.getValue());
client.destroy();
}
}

8
application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java

@ -19,6 +19,7 @@ import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
@ -52,7 +53,6 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
private final String endpoint = "deviceAEndpoint";
private final String serverUri = "coaps://localhost:" + port;
@NotNull
private Device createDevice(X509ClientCredentials clientCredentials) throws Exception {
Device device = new Device();
device.setName("Device A");
@ -75,11 +75,13 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
return device;
}
//TODO: use different endpoints to isolate tests.
@Ignore()
@Test
public void testConnectAndObserveTelemetry() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
X509ClientCredentials credentials = new X509ClientCredentials();
credentials.setEndpoint(endpoint);
credentials.setEndpoint(endpoint+1);
Device device = createDevice(credentials);
SingleEntityFilter sef = new SingleEntityFilter();
@ -97,7 +99,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1);
Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
client.init(security, coapConfig);
String msg = wsClient.waitForUpdate();

2
application/src/test/resources/logback.xml

@ -14,7 +14,7 @@
<logger name="org.springframework.boot.test" level="WARN"/>
<logger name="org.apache.cassandra" level="WARN"/>
<logger name="org.cassandraunit" level="INFO"/>
<logger name="org.eclipse.leshan" level="TRACE"/>
<logger name="org.eclipse.leshan" level="INFO"/>
<root level="WARN">

37
common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java

@ -41,6 +41,7 @@ public class ActorSystemTest {
public static final String ROOT_DISPATCHER = "root-dispatcher";
private static final int _100K = 100 * 1024;
public static final int TIMEOUT_AWAIT_MAX_SEC = 10;
private volatile TbActorSystem actorSystem;
private volatile ExecutorService submitPool;
@ -52,7 +53,7 @@ public class ActorSystemTest {
parallelism = Math.max(2, cores / 2);
TbActorSystemSettings settings = new TbActorSystemSettings(5, parallelism, 42);
actorSystem = new DefaultTbActorSystem(settings);
submitPool = Executors.newWorkStealingPool(parallelism);
submitPool = Executors.newFixedThreadPool(parallelism); //order guaranteed
}
@After
@ -122,13 +123,23 @@ public class ActorSystemTest {
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID()));
submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1)));
submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2)));
final CountDownLatch initLatch = new CountDownLatch(1);
final CountDownLatch actorsReadyLatch = new CountDownLatch(2);
submitPool.submit(() -> {
actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1, initLatch));
actorsReadyLatch.countDown();
});
submitPool.submit(() -> {
actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2, initLatch));
actorsReadyLatch.countDown();
});
initLatch.countDown(); //replacement for Thread.wait(500) in the SlowCreateActorCreator
Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
Thread.sleep(1000);
actorSystem.tell(actorId, new IntTbActorMsg(42));
Assert.assertTrue(testCtx1.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertTrue(testCtx1.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
Assert.assertFalse(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
}
@ -137,13 +148,21 @@ public class ActorSystemTest {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
ActorTestCtx testCtx = getActorTestCtx(1);
TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID()));
for (int i = 0; i < 1000; i++) {
submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx)));
final int actorsCount = 1000;
final CountDownLatch initLatch = new CountDownLatch(1);
final CountDownLatch actorsReadyLatch = new CountDownLatch(actorsCount);
for (int i = 0; i < actorsCount; i++) {
submitPool.submit(() -> {
actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx, initLatch));
actorsReadyLatch.countDown();
});
}
Thread.sleep(1000);
initLatch.countDown();
Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
actorSystem.tell(actorId, new IntTbActorMsg(42));
Assert.assertTrue(testCtx.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertTrue(testCtx.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS));
//One for creation and one for message
Assert.assertEquals(2, testCtx.getInvocationCount().get());
}

15
common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java

@ -17,13 +17,18 @@ package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SlowCreateActor extends TestRootActor {
public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx) {
public static final int TIMEOUT_AWAIT_MAX_MS = 5000;
public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) {
super(actorId, testCtx);
try {
Thread.sleep(500);
initLatch.await(TIMEOUT_AWAIT_MAX_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
@ -34,10 +39,12 @@ public class SlowCreateActor extends TestRootActor {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
private final CountDownLatch initLatch;
public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx) {
public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) {
this.actorId = actorId;
this.testCtx = testCtx;
this.initLatch = initLatch;
}
@Override
@ -47,7 +54,7 @@ public class SlowCreateActor extends TestRootActor {
@Override
public TbActor createActor() {
return new SlowCreateActor(actorId, testCtx);
return new SlowCreateActor(actorId, testCtx, initLatch);
}
}
}

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

@ -36,6 +36,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME;
@Slf4j
@Component
@TbCoapServerComponent
@ -91,7 +93,16 @@ public class DefaultCoapServerService implements CoapServerService {
InetAddress addr = InetAddress.getByName(coapServerContext.getHost());
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort());
noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr);
noSecCoapEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard());
NetworkConfig networkConfig = new NetworkConfig();
networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
networkConfig.setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME);
networkConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024);
networkConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED");
networkConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024);
networkConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024);
networkConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4);
noSecCoapEndpointBuilder.setNetworkConfig(networkConfig);
CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build();
server.addEndpoint(noSecCoapEndpoint);

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

@ -51,6 +51,13 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur
private String privacyPassphrase;
private String engineId;
public SnmpDeviceTransportConfiguration() {
this.host = "localhost";
this.port = 161;
this.protocolVersion = SnmpProtocolVersion.V2C;
this.community = "public";
}
@Override
public DeviceTransportType getType() {
return DeviceTransportType.SNMP;
@ -76,7 +83,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur
isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName)
&& contextName != null && authenticationProtocol != null
&& StringUtils.isNotBlank(authenticationPassphrase)
&& privacyProtocol != null && privacyPassphrase != null && engineId != null;
&& privacyProtocol != null && StringUtils.isNotBlank(privacyPassphrase) && engineId != null;
break;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java

@ -22,10 +22,10 @@ import org.thingsboard.server.common.data.device.data.PowerMode;
public class OtherConfiguration {
private Integer fwUpdateStrategy;
private String fwUpdateResource;
private Integer swUpdateStrategy;
private String swUpdateResource;
private Integer clientOnlyObserveAfterConnect;
private PowerMode powerMode;
private String fwUpdateResource;
private String swUpdateResource;
}

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

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
@ -31,5 +32,6 @@ public class AlarmRule implements Serializable {
// Advanced
@NoXss
private String alarmDetails;
private DashboardId dashboardId;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java

@ -16,5 +16,5 @@
package org.thingsboard.server.common.data.oauth2;
public enum MapperType {
BASIC, CUSTOM, GITHUB;
BASIC, CUSTOM, GITHUB, APPLE;
}

2
common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java

@ -16,5 +16,5 @@
package org.thingsboard.server.common.msg.session;
public enum FeatureType {
ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION, FIRMWARE, SOFTWARE
ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION
}

5
common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java

@ -30,10 +30,7 @@ public enum SessionMsgType {
SESSION_OPEN, SESSION_CLOSE,
CLAIM_REQUEST(),
GET_FIRMWARE_REQUEST,
GET_SOFTWARE_REQUEST;
CLAIM_REQUEST();
private final boolean requiresRulesProcessing;

2
common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java

@ -24,6 +24,8 @@ public interface TbQueueRequestTemplate<Request extends TbQueueMsg, Response ext
ListenableFuture<Response> send(Request request);
ListenableFuture<Response> send(Request request, long timeoutNs);
void stop();
void setMessagesStats(MessagesStats messagesStats);

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

@ -19,7 +19,9 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin;
@ -31,13 +33,17 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.common.stats.MessagesStats;
import javax.annotation.Nullable;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response extends TbQueueMsg> extends AbstractTbQueueTemplate
@ -46,15 +52,15 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
private final TbQueueAdmin queueAdmin;
private final TbQueueProducer<Request> requestTemplate;
private final TbQueueConsumer<Response> responseTemplate;
private final ConcurrentMap<UUID, DefaultTbQueueRequestTemplate.ResponseMetaData<Response>> pendingRequests;
private final boolean internalExecutor;
private final ExecutorService executor;
private final long maxRequestTimeout;
private final long maxPendingRequests;
private final long pollInterval;
private volatile long tickTs = 0L;
private volatile long tickSize = 0L;
private volatile boolean stopped = false;
final ConcurrentHashMap<UUID, DefaultTbQueueRequestTemplate.ResponseMetaData<Response>> pendingRequests = new ConcurrentHashMap<>();
final boolean internalExecutor;
final ExecutorService executor;
final long maxRequestTimeoutNs;
final long maxPendingRequests;
final long pollInterval;
volatile boolean stopped = false;
long nextCleanupNs = 0L;
private final Lock cleanerLock = new ReentrantLock();
private MessagesStats messagesStats;
@ -65,79 +71,113 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
long maxRequestTimeout,
long maxPendingRequests,
long pollInterval,
ExecutorService executor) {
@Nullable ExecutorService executor) {
this.queueAdmin = queueAdmin;
this.requestTemplate = requestTemplate;
this.responseTemplate = responseTemplate;
this.pendingRequests = new ConcurrentHashMap<>();
this.maxRequestTimeout = maxRequestTimeout;
this.maxRequestTimeoutNs = TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout);
this.maxPendingRequests = maxPendingRequests;
this.pollInterval = pollInterval;
if (executor != null) {
internalExecutor = false;
this.executor = executor;
} else {
internalExecutor = true;
this.executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-queue-request-template-" + responseTemplate.getTopic()));
}
this.internalExecutor = (executor == null);
this.executor = internalExecutor ? createExecutor() : executor;
}
ExecutorService createExecutor() {
return Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-queue-request-template-" + responseTemplate.getTopic()));
}
@Override
public void init() {
queueAdmin.createTopicIfNotExists(responseTemplate.getTopic());
this.requestTemplate.init();
tickTs = System.currentTimeMillis();
requestTemplate.init();
responseTemplate.subscribe();
executor.submit(() -> {
long nextCleanupMs = 0L;
while (!stopped) {
try {
List<Response> responses = responseTemplate.poll(pollInterval);
if (responses.size() > 0) {
log.trace("Polling responses completed, consumer records count [{}]", responses.size());
}
responses.forEach(response -> {
byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER);
UUID requestId;
if (requestIdHeader == null) {
log.error("[{}] Missing requestId in header and body", response);
} else {
requestId = bytesToUuid(requestIdHeader);
log.trace("[{}] Response received: {}", requestId, response);
ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId);
if (expectedResponse == null) {
log.trace("[{}] Invalid or stale request", requestId);
} else {
expectedResponse.future.set(response);
}
executor.submit(this::mainLoop);
}
void mainLoop() {
while (!stopped) {
TbStopWatch sw = TbStopWatch.startNew();
try {
fetchAndProcessResponses();
} catch (Throwable e) {
long sleepNanos = TimeUnit.MILLISECONDS.toNanos(this.pollInterval) - sw.stopAndGetTotalTimeNanos();
log.warn("Failed to obtain and process responses from queue. Going to sleep " + sleepNanos + "ns", e);
sleep(sleepNanos);
}
}
}
void fetchAndProcessResponses() {
final long pendingRequestsCount = pendingRequests.mappingCount();
log.trace("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount);
List<Response> responses = doPoll(); //poll js responses
log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}] responses", responseTemplate.getTopic(), pendingRequestsCount, responses.size());
responses.forEach(this::processResponse); //this can take a long time
responseTemplate.commit();
tryCleanStaleRequests();
}
private boolean tryCleanStaleRequests() {
if (!cleanerLock.tryLock()) {
return false;
}
try {
log.trace("tryCleanStaleRequest...");
final long currentNs = getCurrentClockNs();
if (nextCleanupNs < currentNs) {
pendingRequests.forEach((key, value) -> {
if (value.expTime < currentNs) {
ResponseMetaData<Response> staleRequest = pendingRequests.remove(key);
if (staleRequest != null) {
setTimeoutException(key, staleRequest, currentNs);
}
});
responseTemplate.commit();
tickTs = System.currentTimeMillis();
tickSize = pendingRequests.size();
if (nextCleanupMs < tickTs) {
//cleanup;
pendingRequests.forEach((key, value) -> {
if (value.expTime < tickTs) {
ResponseMetaData<Response> staleRequest = pendingRequests.remove(key);
if (staleRequest != null) {
log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs);
staleRequest.future.setException(new TimeoutException());
}
}
});
nextCleanupMs = tickTs + maxRequestTimeout;
}
} catch (Throwable e) {
log.warn("Failed to obtain responses from queue.", e);
try {
Thread.sleep(pollInterval);
} catch (InterruptedException e2) {
log.trace("Failed to wait until the server has capacity to handle new responses", e2);
}
}
});
setupNextCleanup();
}
});
} finally {
cleanerLock.unlock();
}
return true;
}
void setupNextCleanup() {
nextCleanupNs = getCurrentClockNs() + maxRequestTimeoutNs;
log.trace("setupNextCleanup {}", nextCleanupNs);
}
List<Response> doPoll() {
return responseTemplate.poll(pollInterval);
}
void sleep(long nanos) {
LockSupport.parkNanos(nanos);
}
void setTimeoutException(UUID key, ResponseMetaData<Response> staleRequest, long currentNs) {
if (currentNs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) {
log.warn("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key);
} else {
log.error("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key);
}
staleRequest.future.setException(new TimeoutException());
}
void processResponse(Response response) {
byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER);
UUID requestId;
if (requestIdHeader == null) {
log.error("[{}] Missing requestId in header and body", response);
} else {
requestId = bytesToUuid(requestIdHeader);
log.trace("[{}] Response received: {}", requestId, String.valueOf(response).replace("\n", " ")); //TODO remove overhead
ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId);
if (expectedResponse == null) {
log.warn("[{}] Invalid or stale request, response: {}", requestId, String.valueOf(response).replace("\n", " "));
} else {
expectedResponse.future.set(response);
}
}
}
@Override
@ -164,17 +204,48 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
@Override
public ListenableFuture<Response> send(Request request) {
if (tickSize > maxPendingRequests) {
return send(request, this.maxRequestTimeoutNs);
}
@Override
public ListenableFuture<Response> send(Request request, long requestTimeoutNs) {
if (pendingRequests.mappingCount() >= maxPendingRequests) {
log.warn("Pending request map is full [{}]! Consider to increase maxPendingRequests or increase processing performance", maxPendingRequests);
return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!"));
}
UUID requestId = UUID.randomUUID();
request.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId));
request.getHeaders().put(RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic()));
request.getHeaders().put(REQUEST_TIME, longToBytes(System.currentTimeMillis()));
request.getHeaders().put(REQUEST_TIME, longToBytes(getCurrentTimeMs()));
long currentClockNs = getCurrentClockNs();
SettableFuture<Response> future = SettableFuture.create();
ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future);
pendingRequests.putIfAbsent(requestId, responseMetaData);
log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime);
ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(currentClockNs + requestTimeoutNs, future, currentClockNs, requestTimeoutNs);
log.trace("pending {}", responseMetaData);
if (pendingRequests.putIfAbsent(requestId, responseMetaData) != null) {
log.warn("Pending request already exists [{}]!", maxPendingRequests);
return Futures.immediateFailedFuture(new RuntimeException("Pending request already exists !" + requestId));
}
sendToRequestTemplate(request, requestId, future, responseMetaData);
return future;
}
/**
* MONOTONIC clock instead jumping wall clock.
* Wrapped into the method for the test purposes to travel through the time
* */
long getCurrentClockNs() {
return System.nanoTime();
}
/**
* Wall clock to send timestamp to an external service
* */
long getCurrentTimeMs() {
return System.currentTimeMillis();
}
void sendToRequestTemplate(Request request, UUID requestId, SettableFuture<Response> future, ResponseMetaData<Response> responseMetaData) {
log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, request);
if (messagesStats != null) {
messagesStats.incrementTotal();
}
@ -184,7 +255,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
if (messagesStats != null) {
messagesStats.incrementSuccessful();
}
log.trace("[{}] Request sent: {}", requestId, metadata);
log.trace("[{}] Request sent: {}, request {}", requestId, metadata, request);
}
@Override
@ -196,17 +267,32 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
future.setException(t);
}
});
return future;
}
private static class ResponseMetaData<T> {
@Getter
static class ResponseMetaData<T> {
private final long submitTime;
private final long timeout;
private final long expTime;
private final SettableFuture<T> future;
ResponseMetaData(long ts, SettableFuture<T> future) {
ResponseMetaData(long ts, SettableFuture<T> future, long submitTime, long timeout) {
this.submitTime = submitTime;
this.timeout = timeout;
this.expTime = ts;
this.future = future;
}
@Override
public String toString() {
return "ResponseMetaData{" +
"submitTime=" + submitTime +
", calculatedExpTime=" + (submitTime + timeout) +
", deltaMs=" + (expTime - submitTime) +
", expTime=" + expTime +
", future=" + future +
'}';
}
}
}

12
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -21,6 +21,7 @@ import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.springframework.util.StopWatch;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate;
@ -82,7 +83,16 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Override
protected List<ConsumerRecord<String, byte[]>> doPoll(long durationInMillis) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
log.trace("poll topic {} maxDuration {}", getTopic(), durationInMillis);
ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofMillis(durationInMillis));
stopWatch.stop();
log.trace("poll topic {} took {}ms", getTopic(), stopWatch.getTotalTimeMillis());
if (records.isEmpty()) {
return Collections.emptyList();
} else {
@ -99,7 +109,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Override
protected void doCommit() {
consumer.commitAsync();
consumer.commitSync();
}
@Override

211
common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java

@ -0,0 +1,211 @@
/**
* 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.common;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueProducer;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.hamcrest.MockitoHamcrest.longThat;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class DefaultTbQueueRequestTemplateTest {
@Mock
TbQueueAdmin queueAdmin;
@Mock
TbQueueProducer<TbQueueMsg> requestTemplate;
@Mock
TbQueueConsumer<TbQueueMsg> responseTemplate;
@Mock
ExecutorService executorMock;
ExecutorService executor;
String topic = "js-responses-tb-node-0";
long maxRequestTimeout = 10;
long maxPendingRequests = 32;
long pollInterval = 5;
DefaultTbQueueRequestTemplate inst;
@Before
public void setUp() throws Exception {
willReturn(topic).given(responseTemplate).getTopic();
inst = spy(new DefaultTbQueueRequestTemplate(
queueAdmin, requestTemplate, responseTemplate,
maxRequestTimeout, maxPendingRequests, pollInterval, executorMock));
}
@After
public void tearDown() throws Exception {
if (executor != null) {
executor.shutdownNow();
}
}
@Test
public void givenInstance_whenVerifyInitialParameters_thenOK() {
assertThat(inst.maxPendingRequests, equalTo(maxPendingRequests));
assertThat(inst.maxRequestTimeoutNs, equalTo(TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout)));
assertThat(inst.pollInterval, equalTo(pollInterval));
assertThat(inst.executor, is(executorMock));
assertThat(inst.stopped, is(false));
assertThat(inst.internalExecutor, is(false));
}
@Test
public void givenExternalExecutor_whenInitStop_thenOK() {
inst.init();
assertThat(inst.nextCleanupNs, equalTo(0L));
verify(queueAdmin, times(1)).createTopicIfNotExists(topic);
verify(requestTemplate, times(1)).init();
verify(responseTemplate, times(1)).subscribe();
verify(executorMock, times(1)).submit(any(Runnable.class));
inst.stop();
assertThat(inst.stopped, is(true));
verify(responseTemplate, times(1)).unsubscribe();
verify(requestTemplate, times(1)).stop();
verify(executorMock, never()).shutdownNow();
}
@Test
public void givenMainLoop_whenLoopFewTimes_thenVerifyInvocationCount() throws InterruptedException {
executor = inst.createExecutor();
CountDownLatch latch = new CountDownLatch(5);
willDoNothing().given(inst).sleep(anyLong());
willAnswer(invocation -> {
if (latch.getCount() == 1) {
inst.stop(); //stop the loop in natural way
}
if (latch.getCount() == 3 || latch.getCount() == 4) {
latch.countDown();
throw new RuntimeException("test catch block");
}
latch.countDown();
return null;
}).given(inst).fetchAndProcessResponses();
executor.submit(inst::mainLoop);
latch.await(10, TimeUnit.SECONDS);
verify(inst, times(5)).fetchAndProcessResponses();
verify(inst, times(2)).sleep(longThat(lessThan(TimeUnit.MILLISECONDS.toNanos(inst.pollInterval))));
}
@Test
public void givenMessages_whenSend_thenOK() {
willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any());
inst.init();
final int msgCount = 10;
for (int i = 0; i < msgCount; i++) {
inst.send(getRequestMsgMock());
}
assertThat(inst.pendingRequests.mappingCount(), equalTo((long) msgCount));
verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any());
}
@Test
public void givenMessagesOverMaxPendingRequests_whenSend_thenImmediateFailedFutureForTheOfRequests() {
willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any());
inst.init();
int msgOverflowCount = 10;
for (int i = 0; i < inst.maxPendingRequests; i++) {
assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only
}
for (int i = 0; i < msgOverflowCount; i++) {
assertThat("max pending requests overflow", inst.send(getRequestMsgMock()).isDone(), is(true)); //overflow, immediate failed future
}
assertThat(inst.pendingRequests.mappingCount(), equalTo(inst.maxPendingRequests));
verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any());
}
@Test
public void givenNothing_whenSendAndFetchAndProcessResponsesWithTimeout_thenFail() {
//given
AtomicLong currentTime = new AtomicLong();
willAnswer(x -> {
log.info("currentTime={}", currentTime.get());
return currentTime.get();
}).given(inst).getCurrentClockNs();
inst.init();
inst.setupNextCleanup();
willReturn(Collections.emptyList()).given(inst).doPoll();
//when
long stepNs = TimeUnit.MILLISECONDS.toNanos(1);
for (long i = 0; i <= inst.maxRequestTimeoutNs * 2; i = i + stepNs) {
currentTime.addAndGet(stepNs);
assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only
if (i % (inst.maxRequestTimeoutNs * 3 / 2) == 0) {
inst.fetchAndProcessResponses();
}
}
//then
ArgumentCaptor<DefaultTbQueueRequestTemplate.ResponseMetaData> argumentCaptorResp = ArgumentCaptor.forClass(DefaultTbQueueRequestTemplate.ResponseMetaData.class);
ArgumentCaptor<UUID> argumentCaptorUUID = ArgumentCaptor.forClass(UUID.class);
ArgumentCaptor<Long> argumentCaptorLong = ArgumentCaptor.forClass(Long.class);
verify(inst, atLeastOnce()).setTimeoutException(argumentCaptorUUID.capture(), argumentCaptorResp.capture(), argumentCaptorLong.capture());
List<DefaultTbQueueRequestTemplate.ResponseMetaData> responseMetaDataList = argumentCaptorResp.getAllValues();
List<Long> tickTsList = argumentCaptorLong.getAllValues();
for (int i = 0; i < responseMetaDataList.size(); i++) {
assertThat("tickTs >= calculatedExpTime", tickTsList.get(i), greaterThanOrEqualTo(responseMetaDataList.get(i).getSubmitTime() + responseMetaDataList.get(i).getTimeout()));
}
}
TbQueueMsg getRequestMsgMock() {
return mock(TbQueueMsg.class, RETURNS_DEEP_STUBS);
}
}

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

@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC
import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.common.msg.session.SessionMsgType;
@ -139,10 +138,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
processExchangeGetRequest(exchange, featureType.get());
} else if (featureType.get() == FeatureType.ATTRIBUTES) {
processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST);
} else if (featureType.get() == FeatureType.FIRMWARE) {
processRequest(exchange, SessionMsgType.GET_FIRMWARE_REQUEST);
} else if (featureType.get() == FeatureType.SOFTWARE) {
processRequest(exchange, SessionMsgType.GET_SOFTWARE_REQUEST);
} else {
log.trace("Invalid feature type parameter");
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
@ -349,12 +344,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
coapTransportAdaptor.convertToGetAttributes(sessionId, request),
new CoapNoOpCallback(exchange));
break;
case GET_FIRMWARE_REQUEST:
getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.FIRMWARE);
break;
case GET_SOFTWARE_REQUEST:
getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.SOFTWARE);
break;
}
} catch (AdaptorException e) {
log.trace("[{}] Failed to decode message: ", sessionId, e);
@ -366,16 +355,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB());
}
private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) {
TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder()
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setType(firmwareType.name()).build();
transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange));
}
private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) {
tokenToObserveNotificationSeqMap.remove(token);
return tokenToSessionInfoMap.remove(token);
@ -470,57 +449,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
}
}
private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> {
private final CoapExchange exchange;
OtaPackageCallback(CoapExchange exchange) {
this.exchange = exchange;
}
@Override
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) {
String title = exchange.getQueryParameter("title");
String version = exchange.getQueryParameter("version");
if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString();
if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) {
String strChunkSize = exchange.getQueryParameter("size");
String strChunk = exchange.getQueryParameter("chunk");
int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize);
int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk);
exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk));
}
else if (firmwareId != null) {
sendOtaData(exchange, firmwareId);
} else {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
}
} else {
exchange.respond(CoAP.ResponseCode.NOT_FOUND);
}
}
@Override
public void onError(Throwable e) {
log.warn("Failed to process request", e);
exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
}
}
private void sendOtaData(CoapExchange exchange, String firmwareId) {
Response response = new Response(CoAP.ResponseCode.CONTENT);
byte[] fwData = transportContext.getOtaPackageDataCache().get(firmwareId);
if (fwData != null && fwData.length > 0) {
response.setPayload(fwData);
if (exchange.getRequestOptions().getBlock2() != null) {
int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
boolean moreFlag = fwData.length > chunkSize;
response.getOptions().setBlock2(chunkSize, moreFlag, 0);
}
exchange.respond(response);
}
}
private static class CoapSessionListener implements SessionMsgListener {
private final CoapTransportResource coapTransportResource;

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

@ -18,17 +18,21 @@ package org.thingsboard.server.transport.coap;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.springframework.beans.factory.annotation.Autowired;
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.common.data.TbTransportService;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.UnknownHostException;
import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME;
@Service("CoapTransportService")
@TbCoapServerComponent
@Slf4j
@ -51,6 +55,14 @@ public class CoapTransportService implements TbTransportService {
public void init() throws UnknownHostException {
log.info("Starting CoAP transport...");
coapServer = coapServerService.getCoapServer();
coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
coapServer.getConfig().setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME);
coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024);
coapServer.getConfig().setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED");
coapServer.getConfig().setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024);
coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024);
coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10);
CoapResource api = new CoapResource(API);
api.add(new CoapTransportResource(coapTransportContext, coapServerService, V1));
@ -59,6 +71,8 @@ public class CoapTransportService implements TbTransportService {
efento.add(efentoMeasurementsTransportResource);
coapServer.add(api);
coapServer.add(efento);
coapServer.add(new OtaPackageTransportResource(coapTransportContext, OtaPackageType.FIRMWARE));
coapServer.add(new OtaPackageTransportResource(coapTransportContext, OtaPackageType.SOFTWARE));
log.info("CoAP transport started!");
}

150
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java

@ -0,0 +1,150 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.network.Exchange;
import org.eclipse.californium.core.observe.ObserveRelation;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.core.server.resources.ResourceObserver;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
@Slf4j
public class OtaPackageTransportResource extends AbstractCoapTransportResource {
private static final int ACCESS_TOKEN_POSITION = 2;
private final OtaPackageType otaPackageType;
public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) {
super(ctx, otaPackageType.getKeyPrefix());
this.otaPackageType = otaPackageType;
this.setObservable(true);
}
@Override
protected void processHandleGet(CoapExchange exchange) {
log.trace("Processing {}", exchange.advanced().getRequest());
exchange.accept();
Exchange advanced = exchange.advanced();
Request request = advanced.getRequest();
processAccessTokenRequest(exchange, request);
}
@Override
protected void processHandlePost(CoapExchange exchange) {
exchange.respond(CoAP.ResponseCode.METHOD_NOT_ALLOWED);
}
private void processAccessTokenRequest(CoapExchange exchange, Request request) {
Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
if (credentials.isEmpty()) {
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
return;
}
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> {
getOtaPackageCallback(sessionInfo, exchange, otaPackageType);
}));
}
private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) {
TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder()
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setType(firmwareType.name()).build();
transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange));
}
private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
List<String> uriPath = request.getOptions().getUriPath();
if (uriPath.size() == ACCESS_TOKEN_POSITION) {
return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
} else {
return Optional.empty();
}
}
@Override
public Resource getChild(String name) {
return this;
}
private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> {
private final CoapExchange exchange;
OtaPackageCallback(CoapExchange exchange) {
this.exchange = exchange;
}
@Override
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) {
String title = exchange.getQueryParameter("title");
String version = exchange.getQueryParameter("version");
if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString();
if ((title == null || msg.getTitle().equals(title)) && (version == null || msg.getVersion().equals(version))) {
String strChunkSize = exchange.getQueryParameter("size");
String strChunk = exchange.getQueryParameter("chunk");
int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize);
int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk);
respondOtaPackage(exchange, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk));
} else {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
}
} else {
exchange.respond(CoAP.ResponseCode.NOT_FOUND);
}
}
@Override
public void onError(Throwable e) {
log.warn("Failed to process request", e);
exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
}
}
private void respondOtaPackage(CoapExchange exchange, byte[] data) {
Response response = new Response(CoAP.ResponseCode.CONTENT);
if (data != null && data.length > 0) {
response.setPayload(data);
if (exchange.getRequestOptions().getBlock2() != null) {
int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
boolean lastFlag = data.length <= chunkSize;
response.getOptions().setBlock2(chunkSize, lastFlag, 0);
}
transportContext.getExecutor().submit(() -> exchange.respond(response));
}
}
}

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

@ -29,12 +29,11 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import java.io.IOException;
import java.security.GeneralSecurityException;
@ -46,6 +45,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
@Slf4j
@Service("LwM2MBootstrapSecurityStore")
@ -68,7 +68,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
@ -92,7 +92,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
public SecurityInfo getByIdentity(String identity) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP);
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);

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

@ -57,29 +57,21 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
private boolean recommendedSupportedGroups;
@Getter
@Value("${transport.lwm2m.response_pool_size:}")
private int responsePoolSize;
@Value("${transport.lwm2m.downlink_pool_size:}")
private int downlinkPoolSize;
@Getter
@Value("${transport.lwm2m.registered_pool_size:}")
private int registeredPoolSize;
@Value("${transport.lwm2m.uplink_pool_size:}")
private int uplinkPoolSize;
@Getter
@Value("${transport.lwm2m.registration_store_pool_size:}")
private int registrationStorePoolSize;
@Value("${transport.lwm2m.ota_pool_size:}")
private int otaPoolSize;
@Getter
@Value("${transport.lwm2m.clean_period_in_sec:}")
private int cleanPeriodInSec;
@Getter
@Value("${transport.lwm2m.update_registered_pool_size:}")
private int updateRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.un_registered_pool_size:}")
private int unRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.security.key_store_type:}")
private String keyStoreType;
@ -150,7 +142,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
URI uri = null;
try {
uri = Resources.getResource(keyStorePathFile).toURI();
log.error("URI: {}", uri);
log.info("URI: {}", uri);
File keyStoreFile = new File(uri);
InputStream inKeyStore = new FileInputStream(keyStoreFile);
keyStoreValue = KeyStore.getInstance(keyStoreType);

11
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -33,7 +33,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer;
import java.io.IOException;
import java.security.GeneralSecurityException;
@ -45,6 +45,7 @@ import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
import static org.eclipse.leshan.core.SecurityMode.PSK;
import static org.eclipse.leshan.core.SecurityMode.RPK;
import static org.eclipse.leshan.core.SecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
@Slf4j
@Component
@ -55,13 +56,15 @@ public class LwM2mCredentialsSecurityInfoValidator {
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTransportUtil.LwM2mTypeServer keyValue) {
public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTypeServer keyValue) {
CountDownLatch latch = new CountDownLatch(1);
final TbLwM2MSecurityInfo[] resultSecurityStore = new TbLwM2MSecurityInfo[1];
log.trace("Validating credentials [{}]", credentialsId);
context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(credentialsId).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
log.trace("Validated credentials: [{}] [{}]", credentialsId, msg);
String credentialsBody = msg.getCredentials();
resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue);
resultSecurityStore[0].setMsg(msg);
@ -91,11 +94,11 @@ public class LwM2mCredentialsSecurityInfoValidator {
* @param keyValue -
* @return SecurityInfo
*/
private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) {
private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTypeServer keyValue) {
TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo();
LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class);
if (credentials != null) {
if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) {
if (keyValue.equals(BOOTSTRAP)) {
result.setBootstrapCredentialConfig(credentials.getBootstrap());
if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) {
PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient();

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java

@ -42,7 +42,6 @@ import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
@ -57,6 +56,8 @@ import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@Slf4j
@Component
@TbLwM2mTransportComponent
@ -117,7 +118,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT);
ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null;
if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) {
LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class);

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

@ -27,6 +27,7 @@ import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -64,7 +65,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE
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.LwM2mNetworkConfig.getCoapConfig;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RESOURCE;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RESOURCE;
@Slf4j
@Component
@ -80,6 +81,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final LwM2mTransportServerHelper helper;
private final OtaPackageDataCache otaPackageDataCache;
private final DefaultLwM2MUplinkMsgHandler handler;
private final CaliforniumRegistrationStore registrationStore;
private final TbSecurityStore securityStore;
@ -103,8 +105,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
* "coap://host:port/{path}/{token}/{nameFile}"
*/
LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(handler, FIRMWARE_UPDATE_COAP_RESOURCE);
LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RESOURCE);
this.server.coap().getServer().add(otaCoapResource);
this.startLhServer();
this.context.setServer(server);

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

@ -84,10 +84,10 @@ public class LwM2mNetworkConfig {
Create new instance of udp endpoint context matcher.
Params:
checkAddress
– true with address check, (STRICT, UDP)
– true with address check, (STRICT, UDP) - if port Registration of client is changed - it is bad
- false, without
*/
coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT");
coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED");
/**
https://tools.ietf.org/html/rfc7959#section-2.9.3
The block size (number of bytes) to use when doing a blockwise transfer. \
@ -103,7 +103,7 @@ public class LwM2mNetworkConfig {
*/
coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024);
coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4);
coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10);
return coapConfig;
}

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

@ -27,8 +27,7 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle
import java.util.Collection;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
@Slf4j
public class LwM2mServerListener {
@ -93,7 +92,7 @@ public class LwM2mServerListener {
@Override
public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
if (registration != null) {
service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), response);
service.onUpdateValueAfterReadResponse(registration, convertObjectIdToVersionedId(observation.getPath().toString(), registration), response);
}
}

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

@ -33,7 +33,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseM
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService;
import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.Optional;
@ -54,7 +53,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
@Override
public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) {
this.attributesService.onAttributeUpdate(attributeUpdateNotification, this.sessionInfo);
this.attributesService.onAttributesUpdate(attributeUpdateNotification, this.sessionInfo);
}
@Override

30
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java

@ -24,26 +24,25 @@ import org.eclipse.californium.core.observe.ObserveRelation;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.core.server.resources.ResourceObserver;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RESOURCE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SOFTWARE_UPDATE_COAP_RESOURCE;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RESOURCE;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_UPDATE_COAP_RESOURCE;
@Slf4j
public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
private final ConcurrentMap<String, ObserveRelation> tokenToObserveRelationMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, AtomicInteger> tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>();
private final LwM2mUplinkMsgHandler handler;
private final OtaPackageDataCache otaPackageDataCache;
public LwM2mTransportCoapResource(LwM2mUplinkMsgHandler handler, String name) {
public LwM2mTransportCoapResource(OtaPackageDataCache otaPackageDataCache, String name) {
super(name);
this.handler = handler;
this.otaPackageDataCache = otaPackageDataCache;
this.setObservable(true); // enable observing
this.addObserver(new CoapResourceObserver());
}
@ -72,8 +71,8 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
protected void processHandleGet(CoapExchange exchange) {
log.warn("90) processHandleGet [{}]", exchange);
if (exchange.getRequestOptions().getUriPath().size() >= 2 &&
(FIRMWARE_UPDATE_COAP_RESOURCE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size()-2)) ||
SOFTWARE_UPDATE_COAP_RESOURCE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size()-2)))) {
(FIRMWARE_UPDATE_COAP_RESOURCE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size() - 2)) ||
SOFTWARE_UPDATE_COAP_RESOURCE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size() - 2)))) {
this.sendOtaData(exchange);
}
}
@ -132,7 +131,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
}
private void sendOtaData(CoapExchange exchange) {
String idStr = exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size()-1
String idStr = exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size() - 1
);
UUID currentId = UUID.fromString(idStr);
Response response = new Response(CoAP.ResponseCode.CONTENT);
@ -142,11 +141,10 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
response.setPayload(fwData);
if (exchange.getRequestOptions().getBlock2() != null) {
int chunkSize = exchange.getRequestOptions().getBlock2().getSzx();
boolean moreFlag = fwData.length > chunkSize;
response.getOptions().setBlock2(chunkSize, moreFlag, 0);
log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, moreFlag);
}
else {
boolean lastFlag = fwData.length <= chunkSize;
response.getOptions().setBlock2(chunkSize, lastFlag, 0);
log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, lastFlag);
} else {
log.warn("92) with block1 Send currentId: [{}], length: [{}], ", currentId.toString(), fwData.length);
}
exchange.respond(response);
@ -154,7 +152,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource {
}
private byte[] getOtaData(UUID currentId) {
return ((DefaultLwM2MUplinkMsgHandler) handler).otaPackageDataCache.get(currentId.toString());
return otaPackageDataCache.get(currentId.toString());
}
}

32
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -37,6 +37,8 @@ import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.ContentFormat;
import org.springframework.stereotype.Component;
@ -49,6 +51,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -58,6 +61,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
@Slf4j
@Component
@ -68,37 +72,15 @@ public class LwM2mTransportServerHelper {
private final LwM2mTransportContext context;
private final AtomicInteger atomicTs = new AtomicInteger(0);
public long getTS() {
return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) * 1000L + (atomicTs.getAndIncrement() % 1000);
}
/**
* send to Thingsboard Attribute || Telemetry
*
* @param msg - JsonObject: [{name: value}]
* @return - dummyWriteReplace {\"targetIdVer\":\"/19_1.0/0/0\",\"value\":0082}
*/
private <T> TransportServiceCallback<Void> getPubAckCallbackSendAttrTelemetry(final T msg) {
return new TransportServiceCallback<>() {
@Override
public void onSuccess(Void dummy) {
log.trace("Success to publish msg: {}, dummy: {}", msg, dummy);
}
@Override
public void onError(Throwable e) {
log.trace("[{}] Failed to publish msg: {}", msg, e);
}
};
}
public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder();
request.addAllKv(result);
PostAttributeMsg postAttributeMsg = request.build();
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg);
context.getTransportService().process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call));
context.getTransportService().process(sessionInfo, postAttributeMsg, TransportServiceCallback.EMPTY);
}
public void sendParametersOnThingsboardTelemetry(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
@ -108,8 +90,7 @@ public class LwM2mTransportServerHelper {
builder.addAllKv(result);
request.addTsKvList(builder.build());
PostTelemetryMsg postTelemetryMsg = request.build();
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg);
context.getTransportService().process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call));
context.getTransportService().process(sessionInfo, postTelemetryMsg, TransportServiceCallback.EMPTY);
}
/**
@ -226,4 +207,5 @@ public class LwM2mTransportServerHelper {
}
return null;
}
}

561
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java

@ -30,6 +30,7 @@ import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.SimpleDownlinkRequest;
@ -42,12 +43,13 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.ota.OtaPackageKey;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import org.thingsboard.server.common.data.ota.OtaPackageUtil;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import java.util.ArrayList;
@ -75,407 +77,20 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING;
import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME;
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.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_UPDATE_STATE_ID;
@Slf4j
public class LwM2mTransportUtil {
public static final String EVENT_AWAKE = "AWAKE";
public static final String RESPONSE_REQUEST_CHANNEL = "RESP_REQ";
public static final String RESPONSE_CHANNEL = "RESP";
public static final String OBSERVE_CHANNEL = "OBSERVE";
public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
public static final String BOOTSTRAP = "bootstrap";
public static final String SERVERS = "servers";
public static final String LWM2M_SERVER = "lwm2mServer";
public static final String BOOTSTRAP_SERVER = "bootstrapServer";
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
public static final String ATTRIBUTE = "attribute";
public static final String TELEMETRY = "telemetry";
public static final String KEY_NAME = "keyName";
public static final String OBSERVE_LWM2M = "observe";
public static final String ATTRIBUTE_LWM2M = "attributeLwm2m";
private static final String REQUEST = "/request";
private static final String ATTRIBUTES = "/" + ATTRIBUTE;
public static final String TELEMETRIES = "/" + TELEMETRY;
public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
public static final String LWM2M_VERSION_DEFAULT = "1.0";
public static final String LOG_LWM2M_TELEMETRY = "logLwm2m";
public static final String LOG_LWM2M_INFO = "info";
public static final String LOG_LWM2M_ERROR = "error";
public static final String LOG_LWM2M_WARN = "warn";
public static final String LOG_LWM2M_VALUE = "value";
public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
public static final String LWM2M_VERSION_DEFAULT = "1.0";
// RPC
public static final String TYPE_OPER_KEY = "typeOper";
public static final String TARGET_ID_VER_KEY = "targetIdVer";
public static final String KEY_NAME_KEY = "key";
public static final String VALUE_KEY = "value";
public static final String PARAMS_KEY = "params";
public static final String SEPARATOR_KEY = ":";
public static final String FINISH_VALUE_KEY = ",";
public static final String START_JSON_KEY = "{";
public static final String FINISH_JSON_KEY = "}";
public static final String INFO_KEY = "info";
public static final String RESULT_KEY = "result";
public static final String ERROR_KEY = "error";
public static final String METHOD_KEY = "methodName";
// Firmware
public static final String FIRMWARE_UPDATE_COAP_RESOURCE = "firmwareUpdateCoapResource";
public static final String FW_UPDATE = "Firmware update";
public static final Integer FW_5_ID = 5;
public static final Integer FW_19_ID = 19;
// Package W
public static final String FW_PACKAGE_5_ID = "/5/0/0";
public static final String FW_PACKAGE_19_ID = "/19/0/0";
// Package URI
public static final String FW_PACKAGE_URI_ID = "/5/0/1";
// State R
public static final String FW_STATE_ID = "/5/0/3";
// Update Result R
public static final String FW_RESULT_ID = "/5/0/5";
// PkgName R
public static final String FW_NAME_ID = "/5/0/6";
// PkgVersion R
public static final String FW_5_VER_ID = "/5/0/7";
/**
* Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8
* BC68JAR01A10
* # Request prodct type number
* ATI
* Quectel
* BC68
* Revision:BC68JAR01A10
*/
public static final String FW_3_VER_ID = "/3/0/3";
// Update E
public static final String FW_UPDATE_ID = "/5/0/2";
// Software
public static final String SOFTWARE_UPDATE_COAP_RESOURCE = "softwareUpdateCoapResource";
public static final String SW_UPDATE = "Software update";
public static final Integer SW_ID = 9;
// Package W
public static final String SW_PACKAGE_ID = "/9/0/2";
// Package URI
public static final String SW_PACKAGE_URI_ID = "/9/0/3";
// Update State R
public static final String SW_UPDATE_STATE_ID = "/9/0/7";
// Update Result R
public static final String SW_RESULT_ID = "/9/0/9";
// PkgName R
public static final String SW_NAME_ID = "/9/0/0";
// PkgVersion R
public static final String SW_VER_ID = "/9/0/1";
// Install E
public static final String SW_INSTALL_ID = "/9/0/4";
// Uninstall E
public static final String SW_UN_INSTALL_ID = "/9/0/6";
public enum LwM2mTypeServer {
BOOTSTRAP(0, "bootstrap"),
CLIENT(1, "client");
public int code;
public String type;
LwM2mTypeServer(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2mTypeServer fromLwM2mTypeServer(String type) {
for (LwM2mTypeServer sm : LwM2mTypeServer.values()) {
if (sm.type.equals(type)) {
return sm;
}
}
throw new IllegalArgumentException(String.format("Unsupported typeServer type : %d", type));
}
}
/**
* /** State R
* 0: Idle (before downloading or after successful updating)
* 1: Downloading (The data sequence is on the way)
* 2: Downloaded
* 3: Updating
*/
public enum StateFw {
IDLE(0, "Idle"),
DOWNLOADING(1, "Downloading"),
DOWNLOADED(2, "Downloaded"),
UPDATING(3, "Updating");
public int code;
public String type;
StateFw(int code, String type) {
this.code = code;
this.type = type;
}
public static StateFw fromStateFwByType(String type) {
for (StateFw to : StateFw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
}
public static StateFw fromStateFwByCode(int code) {
for (StateFw to : StateFw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State code : %s", code));
}
}
/**
* FW Update Result
* 0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value.
* 1: Firmware updated successfully.
* 2: Not enough flash memory for the new firmware package.
* 3: Out of RAM during downloading process.
* 4: Connection lost during downloading process.
* 5: Integrity check failure for new downloaded package.
* 6: Unsupported package type.
* 7: Invalid URI.
* 8: Firmware update failed.
* 9: Unsupported protocol.
*/
public enum UpdateResultFw {
INITIAL(0, "Initial value", false),
UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false),
NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false),
OUT_OFF_MEMORY(3, "Out of RAM during downloading process", false),
CONNECTION_LOST(4, "Connection lost during downloading process", true),
INTEGRITY_CHECK_FAILURE(5, "Integrity check failure for new downloaded package", true),
UNSUPPORTED_TYPE(6, "Unsupported package type", false),
INVALID_URI(7, "Invalid URI", false),
UPDATE_FAILED(8, "Firmware update failed", false),
UNSUPPORTED_PROTOCOL(9, "Unsupported protocol", false);
public int code;
public String type;
public boolean isAgain;
UpdateResultFw(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static UpdateResultFw fromUpdateResultFwByType(String type) {
for (UpdateResultFw to : UpdateResultFw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type));
}
public static UpdateResultFw fromUpdateResultFwByCode(int code) {
for (UpdateResultFw to : UpdateResultFw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result code : %s", code));
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static OtaPackageUpdateStatus equalsFwSateFwResultToFirmwareUpdateStatus(StateFw stateFw, UpdateResultFw updateResultFw) {
switch (updateResultFw) {
case INITIAL:
return equalsFwSateToFirmwareUpdateStatus(stateFw);
case UPDATE_SUCCESSFULLY:
return UPDATED;
case NOT_ENOUGH:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case INTEGRITY_CHECK_FAILURE:
case UNSUPPORTED_TYPE:
case INVALID_URI:
case UPDATE_FAILED:
case UNSUPPORTED_PROTOCOL:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", stateFw.name(), updateResultFw.name());
}
}
public static OtaPackageUpdateStatus equalsFwResultToFirmwareUpdateStatus(UpdateResultFw updateResultFw) {
switch (updateResultFw) {
case INITIAL:
return VERIFIED;
case UPDATE_SUCCESSFULLY:
return UPDATED;
case NOT_ENOUGH:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case INTEGRITY_CHECK_FAILURE:
case UNSUPPORTED_TYPE:
case INVALID_URI:
case UPDATE_FAILED:
case UNSUPPORTED_PROTOCOL:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name());
}
}
public static OtaPackageUpdateStatus equalsFwSateToFirmwareUpdateStatus(StateFw stateFw) {
switch (stateFw) {
case IDLE:
return VERIFIED;
case DOWNLOADING:
return DOWNLOADING;
case DOWNLOADED:
return DOWNLOADED;
case UPDATING:
return UPDATING;
default:
throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", stateFw);
}
}
/**
* SW Update State R
* 0: INITIAL Before downloading. (see 5.1.2.1)
* 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2)
* 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3)
* 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4)
* If executing the Install Resource failed, the state remains at DELIVERED.
* If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED.
* After executing the UnInstall Resource, the state changes to INITIAL.
* 4: INSTALLED
*/
public enum UpdateStateSw {
INITIAL(0, "Initial"),
DOWNLOAD_STARTED(1, "DownloadStarted"),
DOWNLOADED(2, "Downloaded"),
DELIVERED(3, "Delivered"),
INSTALLED(4, "Installed");
public int code;
public String type;
UpdateStateSw(int code, String type) {
this.code = code;
this.type = type;
}
public static UpdateStateSw fromUpdateStateSwByType(String type) {
for (UpdateStateSw to : UpdateStateSw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type));
}
public static UpdateStateSw fromUpdateStateSwByCode(int code) {
for (UpdateStateSw to : UpdateStateSw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code));
}
}
/**
* SW Update Result
* Contains the result of downloading or installing/uninstalling the software
* 0: Initial value.
* - Prior to download any new package in the Device, Update Result MUST be reset to this initial value.
* - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0".
* 1: Downloading.
* - The package downloading process is on-going.
* 2: Software successfully installed.
* 3: Successfully Downloaded and package integrity verified
* (( 4-49, for expansion, of other scenarios))
* ** Failed
* 50: Not enough storage for the new software package.
* 51: Out of memory during downloading process.
* 52: Connection lost during downloading process.
* 53: Package integrity check failure.
* 54: Unsupported package type.
* 56: Invalid URI
* 57: Device defined update error
* 58: Software installation failure
* 59: Uninstallation Failure during forUpdate(arg=0)
* 60-200 : (for expansion, selection to be in blocks depending on new introduction of features)
* This Resource MAY be reported by sending Observe operation.
*/
public enum UpdateResultSw {
INITIAL(0, "Initial value", false),
DOWNLOADING(1, "Downloading", false),
SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false),
SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false),
NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true),
OUT_OFF_MEMORY(51, "Out of memory during downloading process", true),
CONNECTION_LOST(52, "Connection lost during downloading process", false),
PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false),
UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false),
INVALID_URI(56, "Invalid URI", true),
UPDATE_ERROR(57, "Device defined update error", true),
INSTALL_FAILURE(58, "Software installation failure", true),
UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true);
public int code;
public String type;
public boolean isAgain;
UpdateResultSw(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static UpdateResultSw fromUpdateResultSwByType(String type) {
for (UpdateResultSw to : UpdateResultSw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type));
}
public static UpdateResultSw fromUpdateResultSwByCode(int code) {
for (UpdateResultSw to : UpdateResultSw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code));
}
}
public enum LwM2MClientStrategy {
CLIENT_STRATEGY_1(1, "Read only resources marked as observation"),
@ -508,108 +123,6 @@ public class LwM2mTransportUtil {
}
}
public enum LwM2MFirmwareUpdateStrategy {
OBJ_5_BINARY(1, "ObjectId 5, Binary"),
OBJ_5_TEMP_URL(2, "ObjectId 5, URI"),
OBJ_19_BINARY(3, "ObjectId 19, Binary");
public int code;
public String type;
LwM2MFirmwareUpdateStrategy(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MFirmwareUpdateStrategy fromStrategyFwByType(String type) {
for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
}
public static LwM2MFirmwareUpdateStrategy fromStrategyFwByCode(int code) {
for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Strategy code : %s", code));
}
}
public enum LwM2MSoftwareUpdateStrategy {
BINARY(1, "ObjectId 9, Binary"),
TEMP_URL(2, "ObjectId 9, URI");
public int code;
public String type;
LwM2MSoftwareUpdateStrategy(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MSoftwareUpdateStrategy fromStrategySwByType(String type) {
for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Strategy type : %s", type));
}
public static LwM2MSoftwareUpdateStrategy fromStrategySwByCode(int code) {
for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Strategy code : %s", code));
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static OtaPackageUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) {
switch (updateResultSw) {
case INITIAL:
switch (updateStateSw) {
case INITIAL:
case DOWNLOAD_STARTED:
return DOWNLOADING;
case DOWNLOADED:
return DOWNLOADED;
case DELIVERED:
return VERIFIED;
}
case DOWNLOADING:
return DOWNLOADING;
case SUCCESSFULLY_INSTALLED:
return UPDATED;
case SUCCESSFULLY_DOWNLOADED_VERIFIED:
return VERIFIED;
case NOT_ENOUGH_STORAGE:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case PACKAGE_CHECK_FAILURE:
case UNSUPPORTED_PACKAGE_TYPE:
case INVALID_URI:
case UPDATE_ERROR:
case INSTALL_FAILURE:
case UN_INSTALL_FAILURE:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name());
}
}
public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath
resourcePath) throws CodecException {
switch (type) {
@ -635,19 +148,19 @@ public class LwM2mTransportUtil {
if (path != null) {
if (FW_STATE_ID.equals(path)) {
lwM2mOtaConvert.setCurrentType(STRING);
lwM2mOtaConvert.setValue(StateFw.fromStateFwByCode(((Long) value).intValue()).type);
lwM2mOtaConvert.setValue(FirmwareUpdateState.fromStateFwByCode(((Long) value).intValue()).type);
return lwM2mOtaConvert;
} else if (FW_RESULT_ID.equals(path)) {
lwM2mOtaConvert.setCurrentType(STRING);
lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).type);
lwM2mOtaConvert.setValue(FirmwareUpdateResult.fromUpdateResultFwByCode(((Long) value).intValue()).getType());
return lwM2mOtaConvert;
} else if (SW_UPDATE_STATE_ID.equals(path)) {
lwM2mOtaConvert.setCurrentType(STRING);
lwM2mOtaConvert.setValue(UpdateStateSw.fromUpdateStateSwByCode(((Long) value).intValue()).type);
lwM2mOtaConvert.setValue(SoftwareUpdateState.fromUpdateStateSwByCode(((Long) value).intValue()).type);
return lwM2mOtaConvert;
} else if (SW_RESULT_ID.equals(path)) {
lwM2mOtaConvert.setCurrentType(STRING);
lwM2mOtaConvert.setValue(UpdateResultSw.fromUpdateResultSwByCode(((Long) value).intValue()).type);
lwM2mOtaConvert.setValue(SoftwareUpdateResult.fromUpdateResultSwByCode(((Long) value).intValue()).type);
return lwM2mOtaConvert;
}
}
@ -669,18 +182,6 @@ public class LwM2mTransportUtil {
return null;
}
// public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) {
// LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile();
// lwM2MClientProfile.setTenantId(tenantId);
// lwM2MClientProfile.setPostClientLwM2mSettings(profilesConfigData.get(CLIENT_LWM2M_SETTINGS).getAsJsonObject());
// lwM2MClientProfile.setPostKeyNameProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).getAsJsonObject());
// lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray());
// lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray());
// lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE_LWM2M).getAsJsonArray());
// lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject());
// return lwM2MClientProfile;
// }
public static Lwm2mDeviceProfileTransportConfiguration toLwM2MClientProfile(DeviceProfile deviceProfile) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
if (transportConfiguration.getType().equals(DeviceTransportType.LWM2M)) {
@ -753,7 +254,7 @@ public class LwM2mTransportUtil {
public static String fromVersionedIdToObjectId(String pathIdVer) {
try {
if(pathIdVer == null) {
if (pathIdVer == null) {
return null;
}
String[] keyArray = pathIdVer.split(LWM2M_SEPARATOR_PATH);
@ -795,13 +296,12 @@ public class LwM2mTransportUtil {
if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) {
return pathIdVer;
} else {
LwM2mPath pathObjId = new LwM2mPath(pathIdVer);
return convertPathFromObjectIdToIdVer(pathIdVer, registration);
return convertObjectIdToVersionedId(pathIdVer, registration);
}
}
}
public static String convertPathFromObjectIdToIdVer(String path, Registration registration) {
public static String convertObjectIdToVersionedId(String path, Registration registration) {
String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId());
ver = ver != null ? ver : LWM2M_VERSION_DEFAULT;
try {
@ -935,16 +435,19 @@ public class LwM2mTransportUtil {
}
}
public static boolean isFwSwWords(String pathName) {
return OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.SIZE).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName)
|| OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.SIZE).equals(pathName);
/**
* @param lwM2MClient -
* @param path -
* @return - return value of Resource by idPath
*/
public static LwM2mResource getResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
LwM2mResource lwm2mResourceValue = null;
ResourceValue resourceValue = lwM2MClient.getResources().get(path);
if (resourceValue != null) {
if (new LwM2mPath(fromVersionedIdToObjectId(path)).isResource()) {
lwm2mResourceValue = lwM2MClient.getResources().get(path).getLwM2mResource();
}
}
return lwm2mResourceValue;
}
}

177
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java

@ -20,6 +20,8 @@ import com.google.common.util.concurrent.SettableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ota.OtaPackageKey;
import org.thingsboard.server.common.data.ota.OtaPackageType;
@ -29,17 +31,30 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService;
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.isFwSwWords;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
@Slf4j
@Service
@ -52,6 +67,13 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
private final Map<Integer, SettableFuture<List<TransportProtos.TsKvProto>>> futures;
private final TransportService transportService;
private final LwM2mTransportServerHelper helper;
private final LwM2mClientContext clientContext;
private final LwM2MTransportServerConfig config;
private final LwM2mUplinkMsgHandler uplinkHandler;
private final LwM2mDownlinkMsgHandler downlinkHandler;
private final LwM2MTelemetryLogService logService;
private final LwM2MOtaUpdateService otaUpdateService;
@Override
public ListenableFuture<List<TransportProtos.TsKvProto>> getSharedAttributes(LwM2mClient client, Collection<String> keys) {
@ -96,55 +118,106 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
* @param msg -
*/
@Override
public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) {
// LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo);
// if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) {
// log.warn("2) OnAttributeUpdate, SharedUpdatedList() [{}]", msg.getSharedUpdatedList());
// msg.getSharedUpdatedList().forEach(tsKvProto -> {
// String pathName = tsKvProto.getKv().getKey();
// String pathIdVer = this.getObjectIdByKeyNameFromProfile(sessionInfo, pathName);
// Object valueNew = getValueFromKvProto(tsKvProto.getKv());
// if ((OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName)
// && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())))
// || (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName)
// && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentTitle())))) {
// this.getInfoFirmwareUpdate(lwM2MClient, null);
// } else if ((OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName)
// && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentVersion())))
// || (OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName)
// && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentTitle())))) {
// this.getInfoSoftwareUpdate(lwM2MClient, null);
// }
// if (pathIdVer != null) {
// ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config
// .getModelProvider());
// if (resourceModel != null && resourceModel.operations.isWritable()) {
// this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer);
// } else {
// 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_LWM2M_ERROR, pathIdVer, valueNew);
// this.logToTelemetry(lwM2MClient, logMsg);
// }
// } else if (!isFwSwWords(pathName)) {
// 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_LWM2M_ERROR, pathName, valueNew);
// this.logToTelemetry(lwM2MClient, logMsg);
// }
//
// });
// } else if (msg.getSharedDeletedCount() > 0 && lwM2MClient != null) {
// msg.getSharedUpdatedList().forEach(tsKvProto -> {
// String pathName = tsKvProto.getKv().getKey();
// Object valueNew = getValueFromKvProto(tsKvProto.getKv());
// if (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) {
// lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew);
// }
// });
// log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
// } else if (lwM2MClient == null) {
// log.error("OnAttributeUpdate, lwM2MClient is null");
// }
public void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) {
LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo);
if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) {
String newFirmwareTitle = null;
String newFirmwareVersion = null;
String newFirmwareUrl = null;
String newSoftwareTitle = null;
String newSoftwareVersion = null;
List<TransportProtos.TsKvProto> otherAttributes = new ArrayList<>();
for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) {
String attrName = tsKvProto.getKv().getKey();
if (DefaultLwM2MOtaUpdateService.FIRMWARE_TITLE.equals(attrName)) {
newFirmwareTitle = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) {
newFirmwareVersion = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) {
newFirmwareUrl = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) {
newSoftwareTitle = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) {
newSoftwareVersion = getStrValue(tsKvProto);
} else {
otherAttributes.add(tsKvProto);
}
}
if (newFirmwareTitle != null || newFirmwareVersion != null) {
otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl));
}
if (newSoftwareTitle != null || newSoftwareVersion != null) {
otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion);
}
if (!otherAttributes.isEmpty()) {
onAttributesUpdate(lwM2MClient, otherAttributes);
}
} else if (lwM2MClient == null) {
log.error("OnAttributeUpdate, lwM2MClient is null");
}
}
/**
* #1.1 If two names have equal path => last time attribute
* #2.1 if there is a difference in values between the current resource values and the shared attribute values
* => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path)
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
*/
@Override
public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos) {
log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos);
tsKvProtos.forEach(tsKvProto -> {
String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey());
if (pathIdVer != null) {
// #1.1
if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) {
if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
}
} else {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
}
}
});
// #2.1
lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> {
this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer),
getValueFromKvProto(tsKvProto.getKv()), pathIdVer);
});
}
private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) {
if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) {
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build();
downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId));
} else {
log.error("Failed update resource [{}] [{}]", versionedId, newValue);
String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad",
LOG_LWM2M_ERROR, versionedId, newValue);
logService.log(lwM2MClient, logMsg);
log.info("Failed update resource [{}] [{}]", versionedId, newValue);
}
}
/**
* @param pathIdVer - path resource
* @return - value of Resource into format KvProto or null
*/
private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) {
LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)));
} else {
return null;
}
}
private String getStrValue(TransportProtos.TsKvProto tsKvProto) {
return tsKvProto.getKv().getStringV();
}
}

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

@ -28,5 +28,7 @@ public interface LwM2MAttributesService {
void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg getAttributesResponse, TransportProtos.SessionInfoProto sessionInfo);
void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo);
void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo);
void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos);
}

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

@ -33,16 +33,12 @@ import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
@ -50,14 +46,13 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_VERSION_DEFAULT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId;
@ -98,12 +93,6 @@ public class LwM2mClient implements Cloneable {
private UUID profileId;
@Getter
@Setter
private volatile LwM2mFwSwUpdate fwUpdate;
@Getter
@Setter
private volatile LwM2mFwSwUpdate swUpdate;
@Getter
@Setter
private Registration registration;
private ValidateDeviceCredentialsResponse credentials;
@ -202,7 +191,7 @@ public class LwM2mClient implements Cloneable {
}
public Object getResourceValue(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer;
String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) {
return this.resources.get(pathRez).getLwM2mResource().getValue();
}
@ -210,7 +199,7 @@ public class LwM2mClient implements Cloneable {
}
public Object getResourceNameByRezId(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer;
String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) {
return this.resources.get(pathRez).getResourceModel().name;
}
@ -316,7 +305,7 @@ public class LwM2mClient implements Cloneable {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
String verRez = getVerFromPathIdVerOrId(path);
return verRez == null ? TRANSPORT_DEFAULT_LWM2M_VERSION.equals(verSupportedObject) : verRez.equals(verSupportedObject);
return verRez == null ? LWM2M_VERSION_DEFAULT.equals(verSupportedObject) : verRez.equals(verSupportedObject);
}
/**
@ -368,21 +357,5 @@ public class LwM2mClient implements Cloneable {
}
}
public LwM2mFwSwUpdate getFwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) {
if (this.fwUpdate == null) {
var profile = clientContext.getProfile(this.getProfileId());
this.fwUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy());
}
return this.fwUpdate;
}
public LwM2mFwSwUpdate getSwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) {
if (this.swUpdate == null) {
var profile = clientContext.getProfile(this.getProfileId());
this.swUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy());
}
return this.fwUpdate;
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java

@ -53,7 +53,10 @@ public interface LwM2mClientContext {
LwM2mClient getClientByDeviceId(UUID deviceId);
void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials);
String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String keyName);
String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName);
void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials);
}

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

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service;
@ -25,6 +26,7 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
@ -40,7 +42,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey;
@Slf4j
@Service
@ -49,6 +53,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c
public class LwM2mClientContextImpl implements LwM2mClientContext {
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final TbEditableSecurityStore securityStore;
private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>();
private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>();
@ -160,6 +165,28 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
return lwM2mClient;
}
/**
* Get path to resource from profile equal keyName
*
* @param sessionInfo -
* @param keyName -
* @return -
*/
@Override
public String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String keyName) {
return getObjectIdByKeyNameFromProfile(getClientBySessionInfo(sessionInfo), keyName);
}
@Override
public String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName) {
Lwm2mDeviceProfileTransportConfiguration profile = getProfile(lwM2mClient.getProfileId());
return profile.getObserveAttr().getKeyName().entrySet().stream()
.filter(e -> e.getValue().equals(keyName) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().orElseThrow(
() -> new IllegalArgumentException(keyName + " is not configured in the device profile!")
).getKey();
}
public Registration getRegistration(String registrationId) {
return this.lwM2mClientsByRegistrationId.get(registrationId).getRegistration();
}
@ -200,7 +227,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
Arrays.stream(client.getRegistration().getObjectLinks()).forEach(link -> {
LwM2mPath pathIds = new LwM2mPath(link.getUrl());
if (!pathIds.isRoot()) {
clientObjects.add(convertPathFromObjectIdToIdVer(link.getUrl(), client.getRegistration()));
clientObjects.add(convertObjectIdToVersionedId(link.getUrl(), client.getRegistration()));
}
});
return (clientObjects.size() > 0) ? clientObjects : null;
@ -211,4 +238,14 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
return lwM2mClientsByRegistrationId.values().stream().filter(e -> deviceId.equals(e.getDeviceId())).findFirst().orElse(null);
}
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config
.getModelProvider());
Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathIdVer);
return resourceModel != null && (isWritableNotOptional ?
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
}
}

483
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java

@ -1,483 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RESOURCE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_19_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_URI_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UN_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsFwSateToFirmwareUpdateStatus;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.splitCamelCaseString;
@Slf4j
public class LwM2mFwSwUpdate {
// 5/0/6 PkgName
// 9/0/0 PkgName
@Getter
@Setter
private volatile String currentTitle;
// 5/0/7 PkgVersion
// 9/0/1 PkgVersion
@Getter
@Setter
private volatile String currentVersion;
@Getter
@Setter
private volatile UUID currentId;
@Getter
@Setter
private volatile String stateUpdate;
@Getter
private String pathPackageId;
@Getter
private String pathStateId;
@Getter
private String pathResultId;
@Getter
private String pathNameId;
@Getter
private String pathVerId;
@Getter
private String pathInstallId;
@Getter
private String pathUnInstallId;
@Getter
private String wUpdate;
@Getter
@Setter
private volatile boolean infoFwSwUpdate = false;
private final OtaPackageType type;
private final LwM2mUplinkMsgHandler handler;
@Getter
LwM2mClient lwM2MClient;
@Getter
private final List<String> pendingInfoRequestsStart;
@Getter
@Setter
private volatile int updateStrategy;
public LwM2mFwSwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClient lwM2MClient, OtaPackageType type, int updateStrategy) {
this.handler = handler;
this.lwM2MClient = lwM2MClient;
this.pendingInfoRequestsStart = new CopyOnWriteArrayList<>();
this.type = type;
this.stateUpdate = null;
this.updateStrategy = updateStrategy;
this.initPathId();
}
private void initPathId() {
if (FIRMWARE.equals(this.type)) {
this.pathPackageId = LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ?
FW_PACKAGE_5_ID : LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy ?
FW_PACKAGE_URI_ID : FW_PACKAGE_19_ID;
this.pathStateId = FW_STATE_ID;
this.pathResultId = FW_RESULT_ID;
this.pathNameId = FW_NAME_ID;
this.pathVerId = FW_5_VER_ID;
this.pathInstallId = FW_UPDATE_ID;
this.wUpdate = FW_UPDATE;
} else if (SOFTWARE.equals(this.type)) {
this.pathPackageId = SW_PACKAGE_ID;
this.pathStateId = SW_UPDATE_STATE_ID;
this.pathResultId = SW_RESULT_ID;
this.pathNameId = SW_NAME_ID;
this.pathVerId = SW_VER_ID;
this.pathInstallId = SW_INSTALL_ID;
this.pathUnInstallId = SW_UN_INSTALL_ID;
this.wUpdate = SW_UPDATE;
}
}
public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request, String pathIdVer) {
if (pathIdVer != null) {
this.pendingInfoRequestsStart.remove(pathIdVer);
}
if (this.pendingInfoRequestsStart.size() == 0) {
this.infoFwSwUpdate = false;
// if (!FAILED.name().equals(this.stateUpdate)) {
boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart(handler) :
this.conditionalSwUpdateStart(handler);
if (conditionalStart) {
this.writeFwSwWare(handler, request);
}
// }
}
}
/**
* Send FsSw to Lwm2mClient:
* before operation Write: fw_state = DOWNLOADING
*/
public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) {
if (this.currentId != null) {
this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name();
this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_INFO, null);
String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration());
String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LWM2M_INFO,
LwM2mOperationType.FW_UPDATE.name(), this.pathPackageId);
handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId());
log.warn("8) Start firmware Update. Send save to: [{}] ver: [{}] path: [{}]", this.lwM2MClient.getDeviceName(), this.currentVersion, targetIdVer);
if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) {
int chunkSize = 0;
int chunk = 0;
byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk);
TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(firmwareChunk).timeout(handler.config.getTimeout()).build();
request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer));
} else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) {
String apiFont = "coap://176.36.143.9:5685";
String uri = apiFont + "/" + FIRMWARE_UPDATE_COAP_RESOURCE + "/" + this.currentId.toString();
log.warn("89) coapUri: [{}]", uri);
//TODO: user this.rpcRequest???
TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(uri).timeout(handler.config.getTimeout()).build();
request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer));
} else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) {
}
} else {
String msgError = "FirmWareId is null.";
log.warn("6) [{}]", msgError);
// if (this.rpcRequest != null) {
// TODO: refactor.
// handler.sentRpcResponse(this.rpcRequest, CONTENT.name(), msgError, LOG_LW2M_ERROR);
// }
log.error(msgError);
this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_ERROR, msgError);
}
}
public void sendLogs(DefaultLwM2MUplinkMsgHandler handler, String typeOper, String typeInfo, String msgError) {
// this.sendSateOnThingsBoard(handler);
String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.",
typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate);
if (LOG_LWM2M_ERROR.equals(typeInfo)) {
msg = String.format("%s Error: %s", msg, msgError);
}
handler.logToTelemetry(lwM2MClient, msg);
}
/**
* After inspection Update Result
* fw_state/sw_state = UPDATING
* send execute
*/
public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) {
this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_INFO, null);
//TODO: user this.rpcRequest???
TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(pathInstallId).timeout(handler.config.getTimeout()).build();
request.sendExecuteRequest(lwM2MClient, downlink, new TbLwM2MExecuteCallback(handler, lwM2MClient, pathInstallId));
}
/**
* Firmware start: Check if the version has changed and launch a new update.
* -ObjectId 5, Binary or ObjectId 5, URI
* -- If the result of the update - errors (more than 1) - This means that the previous. the update failed.
* - We launch the update regardless of the state of the firmware and its version.
* -- If the result of the update - errors (more than 1) - This means that the previous. the update failed.
* * ObjectId 5, Binary
* -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed.
* Compare current versions by equals.
* * ObjectId 5, URI
* -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed.
* Compare current versions by contains.
*/
private boolean conditionalFwUpdateStart(DefaultLwM2MUplinkMsgHandler handler) {
Long updateResultFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
String ver5 = (String) this.lwM2MClient.getResourceValue(null, this.pathVerId);
String pathName = (String) this.lwM2MClient.getResourceValue(null, this.pathNameId);
String ver3 = (String) this.lwM2MClient.getResourceValue(null, FW_3_VER_ID);
// #1/#2
String fwMsg = null;
if ((this.currentVersion != null && (
ver5 != null && ver5.equals(this.currentVersion) ||
ver3 != null && ver3.contains(this.currentVersion)
)) ||
(this.currentTitle != null && pathName != null && this.currentTitle.equals(pathName))) {
fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LWM2M_ERROR,
this.currentVersion);
} else if (updateResultFw != null && updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) {
fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LWM2M_ERROR,
updateResultFw);
}
if (fwMsg != null) {
handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId());
return false;
} else {
return true;
}
}
/**
* Before operation Execute inspection Update Result :
* 0 - Initial value
*/
public boolean conditionalFwExecuteStart() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.INITIAL.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* 1 - "Firmware updated successfully"
*/
public boolean conditionalFwExecuteAfterSuccess() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* > 1 error: "Firmware updated successfully"
*/
public boolean conditionalFwExecuteAfterError() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult;
}
/**
* Software start
* - If Update Result -errors (equal or more than 50) - This means that the previous. the update failed.
* * - We launch the update regardless of the state of the firmware and its version.
* - If Update Result is not errors (less than 50) and ver is not empty - This means that before. the update has passed.
* - If Update Result is not errors and ver is empty - This means that there was no update yet or before. UnInstall update
* - If Update Result is not errors and ver is not empty - This means that before unInstall update
* * - Check if the version has changed and launch a new update.
*/
private boolean conditionalSwUpdateStart(DefaultLwM2MUplinkMsgHandler handler) {
Long updateResultSw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
// #1/#2
return updateResultSw >= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code ||
(
(updateResultSw <= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code
) &&
(
(this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) ||
(this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId)))
)
);
}
/**
* Before operation Execute inspection Update Result :
* 3 - Successfully Downloaded and package integrity verified
*/
public boolean conditionalSwUpdateExecute() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_DOWNLOADED_VERIFIED.code == updateResult;
}
/**
* After finish operation Execute (success):
* -- inspection Update Result:
* ---- FW если Update Result == 1 ("Firmware updated successfully") или SW если Update Result == 2 ("Software successfully installed.")
* -- fw_state/sw_state = UPDATED
* <p>
* After finish operation Execute (error):
* -- inspection updateResult and send to thingsboard info about error
* --- send to telemetry ( key - this is name Update Result in model) (
* -- fw_state/sw_state = FAILED
*/
public void finishFwSwUpdate(DefaultLwM2MUplinkMsgHandler handler, boolean success) {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
String value = FIRMWARE.equals(this.type) ? LwM2mTransportUtil.UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type :
LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type;
String key = splitCamelCaseString((String) this.lwM2MClient.getResourceNameByRezId(null, this.pathResultId));
if (success) {
this.stateUpdate = OtaPackageUpdateStatus.UPDATED.name();
this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_INFO, null);
} else {
this.stateUpdate = OtaPackageUpdateStatus.FAILED.name();
this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_ERROR, value);
}
handler.helper.sendParametersOnThingsboardTelemetry(
handler.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession());
}
/**
* After operation Execute success inspection Update Result :
* 2 - "Software successfully installed."
*/
public boolean conditionalSwExecuteAfterSuccess() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* >= 50 - error "NOT_ENOUGH_STORAGE"
*/
public boolean conditionalSwExecuteAfterError() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult;
}
// private void observeStateUpdate(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) {
// request.sendAllRequest(lwM2MClient,
// convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE,
// null, null, 0, null);
// request.sendAllRequest(lwM2MClient,
// convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE,
// null, null, 0, null);
// }
public void sendSateOnThingsBoard(DefaultLwM2MUplinkMsgHandler handler) {
if (StringUtils.trimToNull(this.stateUpdate) != null) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>();
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(this.type, STATE));
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(stateUpdate);
result.add(kvProto.build());
handler.helper.sendParametersOnThingsboardTelemetry(result,
handler.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration()));
}
}
public void sendReadObserveInfo(LwM2mDownlinkMsgHandler request) {
this.infoFwSwUpdate = true;
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathStateId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathResultId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
FW_3_VER_ID, this.lwM2MClient.getRegistration()));
if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ||
LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy ||
SOFTWARE.equals(this.type)) {
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathVerId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathNameId, this.lwM2MClient.getRegistration()));
}
this.pendingInfoRequestsStart.forEach(versionedId -> {
TbLwM2MObserveRequest downlink = TbLwM2MObserveRequest.builder().versionedId(versionedId).build();
request.sendObserveRequest(this.lwM2MClient, downlink, new TbLwM2MObserveCallback(handler, lwM2MClient, versionedId));
});
}
/**
* Before operation Execute (FwUpdate) inspection Update Result :
* - after finished operation Write result: success (FwUpdate): fw_state = DOWNLOADED
* - before start operation Execute (FwUpdate) Update Result = 0 - Initial value
* - start Execute (FwUpdate)
* After finished operation Execute (FwUpdate) inspection Update Result :
* - after start operation Execute (FwUpdate): fw_state = UPDATING
* - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully")
* - finished operation Execute (FwUpdate)
*/
public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request,
Registration registration, String path, int value) {
if (OBJ_5_BINARY.code == this.getUpdateStrategy()) {
if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) {
if (DOWNLOADED.name().equals(this.getStateUpdate())
&& this.conditionalFwExecuteStart()) {
this.executeFwSwWare(handler, request);
} else if (UPDATING.name().equals(this.getStateUpdate())
&& this.conditionalFwExecuteAfterSuccess()) {
this.finishFwSwUpdate(handler, true);
} else if (UPDATING.name().equals(this.getStateUpdate())
&& this.conditionalFwExecuteAfterError()) {
this.finishFwSwUpdate(handler, false);
}
}
} else if (OBJ_5_TEMP_URL.code == this.getUpdateStrategy()) {
if (this.currentId != null && (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) {
String state = equalsFwSateToFirmwareUpdateStatus(LwM2mTransportUtil.StateFw.fromStateFwByCode(value)).name();
if (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) {
this.stateUpdate = state;
this.sendSateOnThingsBoard(handler);
}
if (value == LwM2mTransportUtil.StateFw.DOWNLOADED.code) {
this.executeFwSwWare(handler, request);
}
handler.firmwareUpdateState.put(lwM2MClient.getEndpoint(), value);
}
if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) {
if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.INITIAL.code) {
this.setStateUpdate(INITIATED.name());
} else if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) {
this.setStateUpdate(UPDATED.name());
} else if (value > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) {
this.setStateUpdate(FAILED.name());
}
this.sendSateOnThingsBoard(handler);
}
} else if (OBJ_19_BINARY.code == this.getUpdateStrategy()) {
}
}
}

41
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.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.transport.lwm2m.server.common;
import org.thingsboard.common.util.ThingsBoardExecutors;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
public abstract class LwM2MExecutorAwareService {
protected ExecutorService executor;
protected abstract int getExecutorSize();
protected abstract String getExecutorName();
protected void init() {
this.executor = ThingsBoardExecutors.newWorkStealingPool(getExecutorSize(), getExecutorName());
}
public void destroy() {
if (executor != null) {
executor.shutdownNow();
}
}
}

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java

@ -17,30 +17,30 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN;
@Slf4j
public abstract class AbstractTbLwM2MRequestCallback<R, T> implements DownlinkRequestCallback<R, T> {
protected final LwM2mUplinkMsgHandler handler;
protected final LwM2MTelemetryLogService logService;
protected final LwM2mClient client;
protected AbstractTbLwM2MRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) {
this.handler = handler;
protected AbstractTbLwM2MRequestCallback(LwM2MTelemetryLogService logService, LwM2mClient client) {
this.logService = logService;
this.client = client;
}
@Override
public void onValidationError(String params, String msg) {
log.trace("[{}] Request [{}] validation failed. Reason: {}", client.getEndpoint(), params, msg);
handler.logToTelemetry(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg));
logService.log(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg));
}
@Override
public void onError(String params, Exception e) {
log.trace("[{}] Request [{}] processing failed", client.getEndpoint(), params, e);
handler.logToTelemetry(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e));
logService.log(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e));
}
}

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

@ -26,7 +26,6 @@ import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CancelObservationRequest;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.request.DiscoverRequest;
@ -45,7 +44,6 @@ import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
@ -54,17 +52,18 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -74,25 +73,38 @@ import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN;
import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.STEP;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_REQUEST_CHANNEL;
@Slf4j
@Service
@TbLwM2mTransportComponent
@RequiredArgsConstructor
public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler {
private ExecutorService responseRequestExecutor;
public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mDownlinkMsgHandler {
public LwM2mValueConverterImpl converter;
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final LwM2MTelemetryLogService logService;
@PostConstruct
public void init() {
super.init();
this.converter = LwM2mValueConverterImpl.getInstance();
responseRequestExecutor = Executors.newFixedThreadPool(this.config.getResponsePoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL)));
}
@PreDestroy
public void destroy() {
super.destroy();
}
@Override
protected int getExecutorSize() {
return config.getDownlinkPoolSize();
}
@Override
protected String getExecutorName() {
return "LwM2M Downlink";
}
@Override
@ -221,7 +233,8 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler {
**/
Collection<LwM2mResource> resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter);
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider());
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, convertResourceModelTypeToContentFormat(client, resourceModelWrite.type), resultIds.getObjectId(),
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type);
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resources);
sendRequest(client, downlink, request.getTimeout(), callback);
} else if (resultIds.isObjectInstance()) {
@ -245,19 +258,24 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler {
private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback) {
Registration registration = client.getRegistration();
context.getServer().send(registration, request, timeoutInMs, response -> {
responseRequestExecutor.submit(() -> {
try {
callback.onSuccess(request, response);
} catch (Exception e) {
log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e);
}
try {
logService.log(client, String.format("[%s][%s] Sending request: %s to %s", registration.getId(), registration.getSocketAddress(), request.getClass().getSimpleName(), request.getPath()));
context.getServer().send(registration, request, timeoutInMs, response -> {
executor.submit(() -> {
try {
callback.onSuccess(request, response);
} catch (Exception e) {
log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e);
}
});
}, e -> {
executor.submit(() -> {
callback.onError(JacksonUtil.toString(request), e);
});
});
}, e -> {
responseRequestExecutor.submit(() -> {
callback.onError(JacksonUtil.toString(request), e);
});
});
} catch (Exception e) {
callback.onError(JacksonUtil.toString(request), e);
}
}
private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) {

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

@ -17,21 +17,21 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@Slf4j
public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelAllRequest, Integer> {
public TbLwM2MCancelAllObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) {
super(handler, client);
public TbLwM2MCancelAllObserveCallback(LwM2MTelemetryLogService logService, LwM2mClient client) {
super(logService, client);
}
@Override
public void onSuccess(TbLwM2MCancelAllRequest request, Integer canceledSubscriptionsCount) {
log.trace("[{}] Cancel of all observations was successful: {}", client.getEndpoint(), canceledSubscriptionsCount);
handler.logToTelemetry(client, String.format("[%s]: Cancel of all observations was successful. Result: [%s]", LOG_LWM2M_INFO, canceledSubscriptionsCount));
logService.log(client, String.format("[%s]: Cancel of all observations was successful. Result: [%s]", LOG_LWM2M_INFO, canceledSubscriptionsCount));
}
}

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

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@ -27,15 +28,15 @@ public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback
private final String versionedId;
public TbLwM2MCancelObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) {
super(handler, client);
public TbLwM2MCancelObserveCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) {
super(logService, client);
this.versionedId = versionedId;
}
@Override
public void onSuccess(TbLwM2MCancelObserveRequest request, Integer canceledSubscriptionsCount) {
log.trace("[{}] Cancel observation of [{}] successful: {}", client.getEndpoint(), versionedId, canceledSubscriptionsCount);
handler.logToTelemetry(client, String.format("[%s]: Cancel Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, versionedId, canceledSubscriptionsCount));
logService.log(client, String.format("[%s]: Cancel Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, versionedId, canceledSubscriptionsCount));
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java

@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.DeleteRequest;
import org.eclipse.leshan.core.response.DeleteResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback<DeleteRequest, DeleteResponse> {
public TbLwM2MDeleteCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MDeleteCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(logService, client, targetId);
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java

@ -17,13 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.DiscoverRequest;
import org.eclipse.leshan.core.response.DiscoverResponse;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback<DiscoverRequest, DiscoverResponse> {
public TbLwM2MDiscoverCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MDiscoverCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(logService, client, targetId);
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java

@ -17,13 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback<ExecuteRequest, ExecuteResponse> {
public TbLwM2MExecuteCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MExecuteCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(logService, client, targetId);
}
}

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

@ -18,14 +18,15 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@Slf4j
public class TbLwM2MObserveCallback extends TbLwM2MTargetedCallback<ObserveRequest, ObserveResponse> {
public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback<ObserveRequest, ObserveResponse> {
public TbLwM2MObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MObserveCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(handler, logService, client, targetId);
}
@Override

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

@ -19,13 +19,14 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.response.ReadResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
@Slf4j
public class TbLwM2MReadCallback extends TbLwM2MTargetedCallback<ReadRequest, ReadResponse> {
public class TbLwM2MReadCallback extends TbLwM2MUplinkTargetedCallback<ReadRequest, ReadResponse> {
public TbLwM2MReadCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MReadCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(handler, logService, client, targetId);
}
@Override

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

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@ -26,8 +27,8 @@ public abstract class TbLwM2MTargetedCallback<R, T> extends AbstractTbLwM2MReque
protected final String versionedId;
public TbLwM2MTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) {
super(handler, client);
public TbLwM2MTargetedCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) {
super(logService, client);
this.versionedId = versionedId;
}
@ -36,7 +37,7 @@ public abstract class TbLwM2MTargetedCallback<R, T> extends AbstractTbLwM2MReque
//TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")
String requestName = request.getClass().getSimpleName();
log.trace("[{}] {} [{}] successful: {}", client.getEndpoint(), requestName, versionedId, response);
handler.logToTelemetry(client, String.format("[%s]: %s [%s] successful. Result: [%s]", LOG_LWM2M_INFO, requestName, versionedId, response));
logService.log(client, String.format("[%s]: %s [%s] successful. Result: [%s]", LOG_LWM2M_INFO, requestName, versionedId, response));
}
}

35
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MUplinkTargetedCallback.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.lwm2m.server.downlink;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
@Slf4j
public abstract class TbLwM2MUplinkTargetedCallback<R, T> extends TbLwM2MTargetedCallback<R, T> {
protected LwM2mUplinkMsgHandler handler;
public TbLwM2MUplinkTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) {
super(logService, client, versionedId);
this.handler = handler;
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java

@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.response.WriteAttributesResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback<WriteAttributesRequest, WriteAttributesResponse> {
public TbLwM2MWriteAttributesCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MWriteAttributesCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(logService, client, targetId);
}
}

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

@ -17,17 +17,21 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import lombok.Builder;
import lombok.Getter;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.WriteResponse;
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType;
public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest<WriteResponse> {
@Getter
private final ContentFormat contentFormat;
@Getter
private final Object value;
@Builder
private TbLwM2MWriteReplaceRequest(String versionedId, long timeout, Object value) {
private TbLwM2MWriteReplaceRequest(String versionedId, long timeout, ContentFormat contentFormat, Object value) {
super(versionedId, timeout);
this.contentFormat = contentFormat;
this.value = value;
}

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

@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.WriteResponse;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
public class TbLwM2MWriteResponseCallback extends TbLwM2MTargetedCallback<WriteRequest, WriteResponse> {
public class TbLwM2MWriteResponseCallback extends TbLwM2MUplinkTargetedCallback<WriteRequest, WriteResponse> {
public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) {
super(handler, client, targetId);
public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) {
super(handler, logService, client, targetId);
}
@Override

56
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.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.transport.lwm2m.server.log;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
@Slf4j
@Service
@TbLwM2mTransportComponent
@RequiredArgsConstructor
public class DefaultLwM2MTelemetryLogService implements LwM2MTelemetryLogService {
private final LwM2mClientContext clientContext;
private final LwM2mTransportServerHelper helper;
/**
* @param logMsg - text msg
* @param registrationId - Id of Registration LwM2M Client
*/
@Override
public void log(String registrationId, String logMsg) {
log(clientContext.getClientByRegistrationId(registrationId), logMsg);
}
@Override
public void log(LwM2mClient client, String logMsg) {
if (logMsg != null && client != null && client.getSession() != null) {
if (logMsg.length() > 1024) {
logMsg = logMsg.substring(0, 1024);
}
this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), client.getSession());
}
}
}

26
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/LwM2MTelemetryLogService.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.transport.lwm2m.server.log;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
public interface LwM2MTelemetryLogService {
void log(LwM2mClient client, String msg);
void log(String registrationId, String msg);
}

440
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java

@ -17,67 +17,465 @@ package org.thingsboard.server.transport.lwm2m.server.ota;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.ContentFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.ota.OtaPackageKey;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService;
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
@Slf4j
@Service
@TbLwM2mTransportComponent
@RequiredArgsConstructor
public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService {
public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService implements LwM2MOtaUpdateService {
public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION);
public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE);
public static final String FIRMWARE_URL = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.URL);
public static final String SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION);
public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE);
public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL);
public static final String FIRMWARE_UPDATE_COAP_RESOURCE = "tbfw";
public static final String SOFTWARE_UPDATE_COAP_RESOURCE = "tbsw";
private static final String FW_PACKAGE_5_ID = "/5/0/0";
private static final String FW_URL_ID = "/5/0/1";
private static final String FW_EXECUTE_ID = "/5/0/2";
public static final String FW_STATE_ID = "/5/0/3";
public static final String FW_RESULT_ID = "/5/0/5";
public static final String FW_NAME_ID = "/5/0/6";
public static final String FW_5_VER_ID = "/5/0/7";
/**
* Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8
* Revision:BC68JAR01A10
*/
public static final String FW_3_VER_ID = "/3/0/3";
public static final String FW_DELIVERY_METHOD = "/5/0/9";
private static final String FW_NAME_ID = "/5/0/6";
private static final String FW_VER_ID = "/5/0/7";
private static final String SW_NAME_ID = "/9/0/0";
private static final String SW_VER_ID = "/9/0/1";
public static final String SW_PACKAGE_ID = "/9/0/2";
public static final String SW_PACKAGE_URI_ID = "/9/0/3";
public static final String SW_INSTALL_ID = "/9/0/4";
public static final String SW_UPDATE_STATE_ID = "/9/0/7";
public static final String SW_RESULT_ID = "/9/0/9";
public static final String SW_UN_INSTALL_ID = "/9/0/6";
private final Map<String, LwM2MClientOtaState> fwStates = new ConcurrentHashMap<>();
private final Map<String, LwM2MClientOtaState> swStates = new ConcurrentHashMap<>();
private final Map<String, LwM2MClientOtaInfo> fwStates = new ConcurrentHashMap<>();
private final Map<String, LwM2MClientOtaInfo> swStates = new ConcurrentHashMap<>();
private final LwM2MAttributesService attributesService;
private final TransportService transportService;
private final LwM2mClientContext clientContext;
private final LwM2MTransportServerConfig config;
private final LwM2mUplinkMsgHandler uplinkHandler;
private final LwM2mDownlinkMsgHandler downlinkHandler;
private final OtaPackageDataCache otaPackageDataCache;
private final LwM2MTelemetryLogService logService;
private final LwM2mTransportServerHelper helper;
@Autowired
@Lazy
private LwM2MAttributesService attributesService;
@PostConstruct
public void init() {
super.init();
}
@PreDestroy
public void destroy() {
super.destroy();
}
@Override
protected int getExecutorSize() {
return config.getOtaPoolSize();
}
@Override
protected String getExecutorName() {
return "LwM2M OTA";
}
@Override
public void init(LwM2mClient client) {
//TODO: add locks by client fwInfo.
//TODO: check that the client supports FW and SW by checking the supported objects in the model.
List<String> attributesToFetch = new ArrayList<>();
if (client.isValidObjectVersion(FW_NAME_ID) || client.isValidObjectVersion(FW_VER_ID)) {
LwM2MClientOtaState fwState = getOrInitFwSate(client);
attributesToFetch.add(getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE));
attributesToFetch.add(getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION));
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
if (fwInfo.isSupported()) {
attributesToFetch.add(FIRMWARE_TITLE);
attributesToFetch.add(FIRMWARE_VERSION);
attributesToFetch.add(FIRMWARE_URL);
}
if (client.isValidObjectVersion(SW_NAME_ID) || client.isValidObjectVersion(SW_VER_ID)) {
LwM2MClientOtaState swState = getOrInitSwSate(client);
attributesToFetch.add(getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE));
attributesToFetch.add(getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION));
if (!attributesToFetch.isEmpty()) {
var future = attributesService.getSharedAttributes(client, attributesToFetch);
DonAsynchron.withCallback(future, attrs -> {
if (fwInfo.isSupported()) {
Optional<String> newFirmwareTitle = getAttributeValue(attrs, FIRMWARE_TITLE);
Optional<String> newFirmwareVersion = getAttributeValue(attrs, FIRMWARE_VERSION);
Optional<String> newFirmwareUrl = getAttributeValue(attrs, FIRMWARE_URL);
if (newFirmwareTitle.isPresent() && newFirmwareVersion.isPresent()) {
onTargetFirmwareUpdate(client, newFirmwareTitle.get(), newFirmwareVersion.get(), newFirmwareUrl);
}
}
}, throwable -> {
if (fwInfo.isSupported()) {
fwInfo.setTargetFetchFailure(true);
}
}, executor);
}
}
@Override
public void forceFirmwareUpdate(LwM2mClient client) {
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setRetryAttempts(0);
fwInfo.setFailedPackageId(null);
startFirmwareUpdateIfNeeded(client, fwInfo);
}
var future = attributesService.getSharedAttributes(client, attributesToFetch);
@Override
public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl) {
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl);
startFirmwareUpdateIfNeeded(client, fwInfo);
}
private LwM2MClientOtaState getOrInitFwSate(LwM2mClient client) {
//TODO: fetch state from the cache.
return fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> new LwM2MClientOtaState());
@Override
public void onCurrentFirmwareNameUpdate(LwM2mClient client, String name) {
log.debug("[{}] Current fw name: {}", client.getEndpoint(), name);
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setCurrentName(name);
}
private LwM2MClientOtaState getOrInitSwSate(LwM2mClient client) {
//TODO: fetch state from the cache.
return swStates.computeIfAbsent(client.getEndpoint(), endpoint -> new LwM2MClientOtaState());
@Override
public void onCurrentFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) {
log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy());
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setFwStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(configuration.getFwUpdateStrategy()));
fwInfo.setBaseUrl(configuration.getFwUpdateResource());
startFirmwareUpdateIfNeeded(client, fwInfo);
}
@Override
public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) {
log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy());
LwM2MClientOtaInfo swInfo = getOrInitSwInfo(client);
swInfo.setSwStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(configuration.getSwUpdateStrategy()));
swInfo.setBaseUrl(configuration.getSwUpdateResource());
startSoftwareUpdateIfNeeded(client, swInfo);
}
@Override
public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) {
log.debug("[{}] Current fw version: {}", client.getEndpoint(), version);
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setCurrentVersion3(version);
}
@Override
public void onCurrentFirmwareVersion5Update(LwM2mClient client, String version) {
log.debug("[{}] Current fw version: {}", client.getEndpoint(), version);
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setCurrentVersion5(version);
}
@Override
public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) {
log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode);
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
FirmwareUpdateState state = FirmwareUpdateState.fromStateFwByCode(stateCode.intValue());
if (FirmwareUpdateState.DOWNLOADED.equals(state)) {
executeFwUpdate(client);
}
fwInfo.setUpdateState(state);
Optional<OtaPackageUpdateStatus> status = this.toOtaPackageUpdateStatus(state);
status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo,
otaStatus, "Firmware Update State: " + state.name()));
}
@Override
public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) {
log.debug("[{}] Current fw result: {}", client.getEndpoint(), code);
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue());
Optional<OtaPackageUpdateStatus> status = this.toOtaPackageUpdateStatus(result);
status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo,
otaStatus, "Firmware Update Result: " + result.name()));
if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) {
fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1);
startFirmwareUpdateIfNeeded(client, fwInfo);
} else {
fwInfo.setUpdateResult(result);
}
}
@Override
public void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient client, Long value) {
log.debug("[{}] Current fw delivery method: {}", client.getEndpoint(), value);
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.setDeliveryMethod(value.intValue());
}
@Override
public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion) {
}
private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo fwInfo) {
try {
if (!fwInfo.isSupported()) {
log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo);
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!");
} else if (fwInfo.isUpdateRequired()) {
if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) {
log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl());
startFirmwareUpdateUsingUrl(client, fwInfo.getTargetUrl());
} else {
log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion());
startFirmwareUpdateUsingBinary(client, fwInfo);
}
}
} catch (Exception e) {
log.warn("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e);
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Internal server error: " + e.getMessage());
}
}
private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo swInfo) {
}
private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) {
String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration());
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build();
downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, targetIdVer));
}
public void startFirmwareUpdateUsingBinary(LwM2mClient client, LwM2MClientOtaInfo fwInfo) {
String versionedId = convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client.getRegistration());
this.transportService.process(client.getSession(), createOtaPackageRequestMsg(client.getSession(), OtaPackageType.FIRMWARE.name()),
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) {
executor.submit(() -> doUpdateFirmwareUsingBinary(response, fwInfo, versionedId, client));
}
@Override
public void onError(Throwable e) {
logService.log(client, "Failed to process firmware update: " + e.getMessage());
}
});
}
private void doUpdateFirmwareUsingBinary(TransportProtos.GetOtaPackageResponseMsg response, LwM2MClientOtaInfo fwInfo, String versionedId, LwM2mClient client) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB());
LwM2MFirmwareUpdateStrategy strategy;
if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.BOTH.code) {
strategy = fwInfo.getFwStrategy();
} else {
strategy = fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY;
}
switch (strategy) {
case OBJ_5_BINARY:
byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0);
TbLwM2MWriteReplaceRequest writeRequest = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId)
.value(firmwareChunk).contentFormat(ContentFormat.OPAQUE)
.timeout(config.getTimeout()).build();
downlinkHandler.sendWriteReplaceRequest(client, writeRequest, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId));
break;
case OBJ_5_TEMP_URL:
startFirmwareUpdateUsingUrl(client, fwInfo.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RESOURCE + "/" + otaPackageId.toString());
break;
default:
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Unsupported strategy: " + strategy.name());
}
} else {
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Failed to fetch OTA package: " + response.getResponseStatus());
}
}
private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(TransportProtos.SessionInfoProto sessionInfo, String nameFwSW) {
return TransportProtos.GetOtaPackageRequestMsg.newBuilder()
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setType(nameFwSW)
.build();
}
private void executeFwUpdate(LwM2mClient client) {
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(FW_EXECUTE_ID).timeout(config.getTimeout()).build();
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, FW_EXECUTE_ID));
}
private Optional<String> getAttributeValue(List<TransportProtos.TsKvProto> attrs, String keyName) {
for (TransportProtos.TsKvProto attr : attrs) {
if (keyName.equals(attr.getKv().getKey())) {
if (attr.getKv().getType().equals(TransportProtos.KeyValueType.STRING_V)) {
return Optional.of(attr.getKv().getStringV());
} else {
return Optional.empty();
}
}
}
return Optional.empty();
}
public LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) {
//TODO: fetch state from the cache or DB.
return this.fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> {
var profile = clientContext.getProfile(client.getProfileId());
return new LwM2MClientOtaInfo(endpoint, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy(),
profile.getClientLwM2mSettings().getFwUpdateResource());
});
}
private LwM2MClientOtaInfo getOrInitSwInfo(LwM2mClient client) {
//TODO: fetch state from the cache or DB.
return swStates.computeIfAbsent(client.getEndpoint(), endpoint -> {
var profile = clientContext.getProfile(client.getProfileId());
return new LwM2MClientOtaInfo(endpoint, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy(), profile.getClientLwM2mSettings().getSwUpdateResource());
});
}
private void sendStateUpdateToTelemetry(LwM2mClient client, LwM2MClientOtaInfo fwInfo, OtaPackageUpdateStatus status, String log) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>();
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(fwInfo.getType(), STATE));
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(status.name());
result.add(kvProto.build());
kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(LOG_LWM2M_TELEMETRY);
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(log);
result.add(kvProto.build());
helper.sendParametersOnThingsboardTelemetry(result, client.getSession());
}
private static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(FirmwareUpdateResult fwUpdateResult) {
switch (fwUpdateResult) {
case INITIAL:
return Optional.empty();
case UPDATE_SUCCESSFULLY:
return Optional.of(UPDATED);
case NOT_ENOUGH:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case INTEGRITY_CHECK_FAILURE:
case UNSUPPORTED_TYPE:
case INVALID_URI:
case UPDATE_FAILED:
case UNSUPPORTED_PROTOCOL:
return Optional.of(FAILED);
default:
throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", fwUpdateResult.name());
}
}
private static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(FirmwareUpdateState firmwareUpdateState) {
switch (firmwareUpdateState) {
case IDLE:
return Optional.empty();
case DOWNLOADING:
return Optional.of(DOWNLOADING);
case DOWNLOADED:
return Optional.of(DOWNLOADED);
case UPDATING:
return Optional.of(UPDATING);
default:
throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", firmwareUpdateState);
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static Optional<OtaPackageUpdateStatus> toSwSateResultUpdateStatus(SoftwareUpdateState softwareUpdateState, SoftwareUpdateResult softwareUpdateResult) {
switch (softwareUpdateResult) {
case INITIAL:
switch (softwareUpdateState) {
case INITIAL:
case DOWNLOAD_STARTED:
return Optional.of(DOWNLOADING);
case DOWNLOADED:
return Optional.of(DOWNLOADED);
case DELIVERED:
return Optional.of(VERIFIED);
}
case DOWNLOADING:
return Optional.of(DOWNLOADING);
case SUCCESSFULLY_INSTALLED:
return Optional.of(UPDATED);
case SUCCESSFULLY_DOWNLOADED_VERIFIED:
return Optional.of(VERIFIED);
case NOT_ENOUGH_STORAGE:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case PACKAGE_CHECK_FAILURE:
case UNSUPPORTED_PACKAGE_TYPE:
case INVALID_URI:
case UPDATE_ERROR:
case INSTALL_FAILURE:
case UN_INSTALL_FAILURE:
return Optional.of(FAILED);
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", softwareUpdateState.name(), softwareUpdateResult.name());
}
}
}

111
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java

@ -0,0 +1,111 @@
/**
* 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.ota;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
import java.util.Optional;
@Data
public class LwM2MClientOtaInfo {
private final String endpoint;
private final OtaPackageType type;
private String baseUrl;
private boolean targetFetchFailure;
private String targetName;
private String targetVersion;
private String targetUrl;
private boolean currentFetchFailure;
private String currentName;
private String currentVersion3;
private String currentVersion5;
private Integer deliveryMethod;
//TODO: use value from device if applicable;
private LwM2MFirmwareUpdateStrategy fwStrategy;
private LwM2MSoftwareUpdateStrategy swStrategy;
private FirmwareUpdateState updateState;
private FirmwareUpdateResult updateResult;
private String failedPackageId;
private int retryAttempts;
public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, Integer strategyCode, String baseUrl) {
this.endpoint = endpoint;
this.type = type;
this.fwStrategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode);
this.baseUrl = baseUrl;
}
public void updateTarget(String targetName, String targetVersion, Optional<String> newFirmwareUrl) {
this.targetName = targetName;
this.targetVersion = targetVersion;
this.targetUrl = newFirmwareUrl.orElse(null);
}
public boolean isUpdateRequired() {
if (StringUtils.isEmpty(targetName) || StringUtils.isEmpty(targetVersion) || !isSupported()) {
return false;
} else {
String targetPackageId = getPackageId(targetName, targetVersion);
String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion5);
if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) {
return false;
} else {
if (targetPackageId.equals(currentPackageIdUsingObject5)) {
return false;
} else if (StringUtils.isNotEmpty(currentVersion3)) {
return !currentVersion3.contains(targetPackageId);
} else {
return true;
}
}
}
}
public boolean isSupported() {
return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion5) || StringUtils.isNotEmpty(currentVersion3);
}
public void setUpdateResult(FirmwareUpdateResult updateResult) {
this.updateResult = updateResult;
switch (updateResult) {
case INITIAL:
break;
case UPDATE_SUCCESSFULLY:
retryAttempts = 0;
break;
default:
failedPackageId = getPackageId(targetName, targetVersion);
break;
}
}
private static String getPackageId(String name, String version) {
return (StringUtils.isNotEmpty(name) ? name : "") + (StringUtils.isNotEmpty(version) ? version : "");
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaState.java

@ -15,5 +15,8 @@
*/
package org.thingsboard.server.transport.lwm2m.server.ota;
public class LwM2MClientOtaState {
public enum LwM2MClientOtaState {
IDLE, IN_PROGRESS, SUCCESS, FAILED
}

24
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java

@ -15,10 +15,34 @@
*/
package org.thingsboard.server.transport.lwm2m.server.ota;
import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import java.util.Optional;
public interface LwM2MOtaUpdateService {
void init(LwM2mClient client);
void forceFirmwareUpdate(LwM2mClient client);
void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl);
void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion);
void onCurrentFirmwareNameUpdate(LwM2mClient client, String name);
void onCurrentFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration);
void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration);
void onCurrentFirmwareVersion3Update(LwM2mClient client, String version);
void onCurrentFirmwareVersion5Update(LwM2mClient client, String version);
void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state);
void onCurrentFirmwareResultUpdate(LwM2mClient client, Long result);
void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient lwM2MClient, Long value);
}

48
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareDeliveryMethod.java

@ -0,0 +1,48 @@
/**
* 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.ota.firmware;
public enum FirmwareDeliveryMethod {
PULL(0, "Pull only"),
PUSH(1, "Push only"),
BOTH(2, "Push or Push");
public int code;
public String type;
FirmwareDeliveryMethod(int code, String type) {
this.code = code;
this.type = type;
}
public static FirmwareDeliveryMethod fromStateFwByType(String type) {
for (FirmwareDeliveryMethod to : FirmwareDeliveryMethod.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW delivery type : %s", type));
}
public static FirmwareDeliveryMethod fromStateFwByCode(int code) {
for (FirmwareDeliveryMethod to : FirmwareDeliveryMethod.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW delivery code : %s", code));
}
}

75
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateResult.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.ota.firmware;
import lombok.Getter;
/**
* FW Update Result
* 0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value.
* 1: Firmware updated successfully.
* 2: Not enough flash memory for the new firmware package.
* 3: Out of RAM during downloading process.
* 4: Connection lost during downloading process.
* 5: Integrity check failure for new downloaded package.
* 6: Unsupported package type.
* 7: Invalid URI.
* 8: Firmware update failed.
* 9: Unsupported protocol.
*/
public enum FirmwareUpdateResult {
INITIAL(0, "Initial value", false),
UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false),
NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false),
OUT_OFF_MEMORY(3, "Out of RAM during downloading process", false),
CONNECTION_LOST(4, "Connection lost during downloading process", true),
INTEGRITY_CHECK_FAILURE(5, "Integrity check failure for new downloaded package", true),
UNSUPPORTED_TYPE(6, "Unsupported package type", false),
INVALID_URI(7, "Invalid URI", false),
UPDATE_FAILED(8, "Firmware update failed", false),
UNSUPPORTED_PROTOCOL(9, "Unsupported protocol", false);
@Getter
private int code;
@Getter
private String type;
@Getter
private boolean again;
FirmwareUpdateResult(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.again = isAgain;
}
public static FirmwareUpdateResult fromUpdateResultFwByType(String type) {
for (FirmwareUpdateResult to : FirmwareUpdateResult.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type));
}
public static FirmwareUpdateResult fromUpdateResultFwByCode(int code) {
for (FirmwareUpdateResult to : FirmwareUpdateResult.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result code : %s", code));
}
}

56
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.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.transport.lwm2m.server.ota.firmware;
/**
* /** State R
* 0: Idle (before downloading or after successful updating)
* 1: Downloading (The data sequence is on the way)
* 2: Downloaded
* 3: Updating
*/
public enum FirmwareUpdateState {
IDLE(0, "Idle"),
DOWNLOADING(1, "Downloading"),
DOWNLOADED(2, "Downloaded"),
UPDATING(3, "Updating");
public int code;
public String type;
FirmwareUpdateState(int code, String type) {
this.code = code;
this.type = type;
}
public static FirmwareUpdateState fromStateFwByType(String type) {
for (FirmwareUpdateState to : FirmwareUpdateState.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
}
public static FirmwareUpdateState fromStateFwByCode(int code) {
for (FirmwareUpdateState to : FirmwareUpdateState.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State code : %s", code));
}
}

48
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MFirmwareUpdateStrategy.java

@ -0,0 +1,48 @@
/**
* 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.ota.firmware;
public enum LwM2MFirmwareUpdateStrategy {
OBJ_5_BINARY(1, "ObjectId 5, Binary"),
OBJ_5_TEMP_URL(2, "ObjectId 5, URI"),
OBJ_19_BINARY(3, "ObjectId 19, Binary");
public int code;
public String type;
LwM2MFirmwareUpdateStrategy(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MFirmwareUpdateStrategy fromStrategyFwByType(String type) {
for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
}
public static LwM2MFirmwareUpdateStrategy fromStrategyFwByCode(int code) {
for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Strategy code : %s", code));
}
}

48
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MSoftwareUpdateStrategy.java

@ -0,0 +1,48 @@
/**
* 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.ota.software;
public enum LwM2MSoftwareUpdateStrategy {
BINARY(1, "ObjectId 9, Binary"),
TEMP_URL(2, "ObjectId 9, URI");
public int code;
public String type;
LwM2MSoftwareUpdateStrategy(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MSoftwareUpdateStrategy fromStrategySwByType(String type) {
for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Strategy type : %s", type));
}
public static LwM2MSoftwareUpdateStrategy fromStrategySwByCode(int code) {
for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Strategy code : %s", code));
}
}

84
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java

@ -0,0 +1,84 @@
/**
* 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.ota.software;
/**
* SW Update Result
* Contains the result of downloading or installing/uninstalling the software
* 0: Initial value.
* - Prior to download any new package in the Device, Update Result MUST be reset to this initial value.
* - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0".
* 1: Downloading.
* - The package downloading process is on-going.
* 2: Software successfully installed.
* 3: Successfully Downloaded and package integrity verified
* (( 4-49, for expansion, of other scenarios))
* ** Failed
* 50: Not enough storage for the new software package.
* 51: Out of memory during downloading process.
* 52: Connection lost during downloading process.
* 53: Package integrity check failure.
* 54: Unsupported package type.
* 56: Invalid URI
* 57: Device defined update error
* 58: Software installation failure
* 59: Uninstallation Failure during forUpdate(arg=0)
* 60-200 : (for expansion, selection to be in blocks depending on new introduction of features)
* This Resource MAY be reported by sending Observe operation.
*/
public enum SoftwareUpdateResult {
INITIAL(0, "Initial value", false),
DOWNLOADING(1, "Downloading", false),
SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false),
SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false),
NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true),
OUT_OFF_MEMORY(51, "Out of memory during downloading process", true),
CONNECTION_LOST(52, "Connection lost during downloading process", false),
PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false),
UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false),
INVALID_URI(56, "Invalid URI", true),
UPDATE_ERROR(57, "Device defined update error", true),
INSTALL_FAILURE(58, "Software installation failure", true),
UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true);
public int code;
public String type;
public boolean isAgain;
SoftwareUpdateResult(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static SoftwareUpdateResult fromUpdateResultSwByType(String type) {
for (SoftwareUpdateResult to : SoftwareUpdateResult.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type));
}
public static SoftwareUpdateResult fromUpdateResultSwByCode(int code) {
for (SoftwareUpdateResult to : SoftwareUpdateResult.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code));
}
}

62
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java

@ -0,0 +1,62 @@
/**
* 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.ota.software;
/**
* SW Update State R
* 0: INITIAL Before downloading. (see 5.1.2.1)
* 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2)
* 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3)
* 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4)
* If executing the Install Resource failed, the state remains at DELIVERED.
* If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED.
* After executing the UnInstall Resource, the state changes to INITIAL.
* 4: INSTALLED
*/
public enum SoftwareUpdateState {
INITIAL(0, "Initial"),
DOWNLOAD_STARTED(1, "DownloadStarted"),
DOWNLOADED(2, "Downloaded"),
DELIVERED(3, "Delivered"),
INSTALLED(4, "Installed");
public int code;
public String type;
SoftwareUpdateState(int code, String type) {
this.code = code;
this.type = type;
}
public static SoftwareUpdateState fromUpdateStateSwByType(String type) {
for (SoftwareUpdateState to : SoftwareUpdateState.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type));
}
public static SoftwareUpdateState fromUpdateStateSwByCode(int code) {
for (SoftwareUpdateState to : SoftwareUpdateState.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code));
}
}

26
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -50,6 +50,7 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttrib
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.Map;
@ -69,6 +70,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
private final LwM2MTransportServerConfig config;
private final LwM2mUplinkMsgHandler uplinkHandler;
private final LwM2mDownlinkMsgHandler downlinkHandler;
private final LwM2MTelemetryLogService logService;
private final Map<UUID, Long> rpcSubscriptions = new ConcurrentHashMap<>();
@Override
@ -148,14 +150,14 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
private void sendReadRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MReadCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MReadCallback(uplinkHandler, logService, client, versionedId);
var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback);
downlinkHandler.sendReadRequest(client, request, rpcCallback);
}
private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MObserveCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MObserveCallback(uplinkHandler, logService, client, versionedId);
var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback);
downlinkHandler.sendObserveRequest(client, request, rpcCallback);
}
@ -172,14 +174,14 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
private void sendDiscoverRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MDiscoverCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MDiscoverCallback(logService, client, versionedId);
var rpcCallback = new RpcDiscoverCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendDiscoverRequest(client, request, rpcCallback);
}
private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MExecuteCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MExecuteCallback(logService, client, versionedId);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendExecuteRequest(client, downlink, rpcCallback);
}
@ -189,7 +191,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId)
.attributes(requestBody.getAttributes())
.timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MWriteAttributesCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MWriteAttributesCallback(logService, client, versionedId);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendWriteAttributesRequest(client, request, rpcCallback);
}
@ -198,38 +200,38 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class);
TbLwM2MWriteUpdateRequest.TbLwM2MWriteUpdateRequestBuilder builder = TbLwM2MWriteUpdateRequest.builder().versionedId(versionedId);
builder.value(requestBody.getValue()).timeout(this.config.getTimeout());
var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback);
}
private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class);
RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class);
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId)
.value(requestBody.getValue())
.timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendWriteReplaceRequest(client, request, rpcCallback);
}
private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MCancelObserveRequest downlink = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MCancelObserveCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MCancelObserveCallback(logService, client, versionedId);
var rpcCallback = new RpcCancelObserveCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendCancelObserveRequest(client, downlink, rpcCallback);
}
private void sendDeleteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MDeleteRequest downlink = TbLwM2MDeleteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MDeleteCallback(uplinkHandler, client, versionedId);
var mainCallback = new TbLwM2MDeleteCallback(logService, client, versionedId);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendDeleteRequest(client, downlink, rpcCallback);
}
private void sendCancelAllObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) {
TbLwM2MCancelAllRequest downlink = TbLwM2MCancelAllRequest.builder().timeout(this.config.getTimeout()).build();
var mainCallback = new TbLwM2MCancelAllObserveCallback(uplinkHandler, client);
var mainCallback = new TbLwM2MCancelAllObserveCallback(logService, client);
var rpcCallback = new RpcCancelAllObserveCallback(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback);
}
@ -238,7 +240,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class);
String targetId;
if (StringUtils.isNotEmpty(requestParams.getKey())) {
targetId = uplinkHandler.getObjectIdByKeyNameFromProfile(client, requestParams.getKey());
targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey());
} else if (StringUtils.isNotEmpty(requestParams.getId())) {
targetId = requestParams.getId();
} else {

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java

@ -15,9 +15,11 @@
*/
package org.thingsboard.server.transport.lwm2m.server.rpc;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class IdOrKeyRequest {
private String key;

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

@ -16,20 +16,13 @@
package org.thingsboard.server.transport.lwm2m.server.store;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.server.security.SecurityStore;
import org.eclipse.leshan.server.security.SecurityStoreListener;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import java.util.Collection;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@Slf4j
public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
@ -67,7 +60,7 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
@Nullable
public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) {
TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT);
try {
if (securityInfo != null) {
securityStore.put(securityInfo);
@ -85,6 +78,7 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
@Override
public void remove(String endpoint) {
securityStore.remove(endpoint);
//TODO: Make sure we delay removal of security store from endpoint due to reg/unreg race condition.
// securityStore.remove(endpoint);
}
}

387
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java

@ -37,15 +37,14 @@ import org.eclipse.leshan.server.registration.Registration;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes;
import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.ota.OtaPackageUtil;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
@ -66,31 +65,32 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientStateException;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mFwSwUpdate;
import org.thingsboard.server.transport.lwm2m.server.client.ParametersAnalyzeResult;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto;
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService;
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback;
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler;
import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -103,57 +103,52 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_5_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID;
@Slf4j
@Service
@TbLwM2mTransportComponent
public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mUplinkMsgHandler {
private ExecutorService registrationExecutor;
private ExecutorService updateRegistrationExecutor;
private ExecutorService unRegistrationExecutor;
public LwM2mValueConverterImpl converter;
private final TransportService transportService;
private final LwM2mTransportContext context;
private final LwM2MAttributesService attributesService;
private final LwM2MOtaUpdateService otaService;
public final LwM2MTransportServerConfig config;
public final OtaPackageDataCache otaPackageDataCache;
public final LwM2mTransportServerHelper helper;
private final LwM2MTransportServerConfig config;
private final LwM2MTelemetryLogService logService;
private final LwM2mTransportServerHelper helper;
private final TbLwM2MDtlsSessionStore sessionStore;
public final LwM2mClientContext clientContext;
private final LwM2mClientContext clientContext;
private final LwM2MRpcRequestHandler rpcHandler;
public final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler;
public final Map<String, Integer> firmwareUpdateState;
private final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler;
public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MAttributesService attributesService, LwM2MOtaUpdateService otaService,
LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
public DefaultLwM2MUplinkMsgHandler(TransportService transportService,
LwM2MTransportServerConfig config,
LwM2mTransportServerHelper helper,
LwM2mClientContext clientContext,
LwM2MTelemetryLogService logService,
@Lazy LwM2MOtaUpdateService otaService,
@Lazy LwM2MAttributesService attributesService,
@Lazy LwM2MRpcRequestHandler rpcHandler,
@Lazy LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler,
OtaPackageDataCache otaPackageDataCache,
LwM2mTransportContext context, TbLwM2MDtlsSessionStore sessionStore) {
this.transportService = transportService;
this.attributesService = attributesService;
@ -161,23 +156,35 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
this.config = config;
this.helper = helper;
this.clientContext = clientContext;
this.logService = logService;
this.rpcHandler = rpcHandler;
this.defaultLwM2MDownlinkMsgHandler = defaultLwM2MDownlinkMsgHandler;
this.otaPackageDataCache = otaPackageDataCache;
this.context = context;
this.firmwareUpdateState = new ConcurrentHashMap<>();
this.sessionStore = sessionStore;
}
@PostConstruct
public void initAttributes() {
public void init() {
super.init();
this.context.getScheduler().scheduleAtFixedRate(this::reportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS);
this.registrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getRegisteredPoolSize(), "LwM2M registration");
this.updateRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUpdateRegisteredPoolSize(), "LwM2M update registration");
this.unRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUnRegisteredPoolSize(), "LwM2M unRegistration");
this.converter = LwM2mValueConverterImpl.getInstance();
}
@PreDestroy
public void destroy() {
super.destroy();
}
@Override
protected String getExecutorName() {
return "LwM2M uplink";
}
@Override
protected int getExecutorSize() {
return config.getUplinkPoolSize();
}
/**
* Start registration device
* Create session: Map<String <registrationId >, LwM2MClient>
@ -193,7 +200,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
* @param previousObservations - may be null
*/
public void onRegistered(Registration registration, Collection<Observation> previousObservations) {
registrationExecutor.submit(() -> {
executor.submit(() -> {
LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint());
try {
log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId());
@ -203,7 +210,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
log.info("[{}] Closing old session: {}", registration.getEndpoint(), new UUID(oldSessionInfo.get().getSessionIdMSB(), oldSessionInfo.get().getSessionIdLSB()));
closeSession(oldSessionInfo.get());
}
this.logToTelemetry(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId());
logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId());
SessionInfoProto sessionInfo = lwM2MClient.getSession();
transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, attributesService, rpcHandler, sessionInfo));
log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));
@ -214,8 +221,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
.setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().setSessionType(TransportProtos.SessionType.ASYNC).build())
.build();
transportService.process(msg, null);
this.getInfoFirmwareUpdate(lwM2MClient);
this.getInfoSoftwareUpdate(lwM2MClient);
this.initClientTelemetry(lwM2MClient);
this.initAttributes(lwM2MClient);
otaService.init(lwM2MClient);
@ -228,11 +233,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
// Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again.
onRegistered(registration, previousObservations);
} else {
this.logToTelemetry(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState());
logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState());
}
} catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t);
this.logToTelemetry(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to: " + t.getMessage());
logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to: " + t.getMessage());
}
});
}
@ -243,10 +248,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
* @param registration - Registration LwM2M Client
*/
public void updatedReg(Registration registration) {
updateRegistrationExecutor.submit(() -> {
executor.submit(() -> {
LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint());
try {
log.warn("[{}] [{{}] Client: update after Registration", registration.getEndpoint(), registration.getId());
logService.log(lwM2MClient, String.format("[%s][%s] Updated registration.", registration.getId(), registration.getSocketAddress()));
clientContext.updateRegistration(lwM2MClient, registration);
TransportProtos.SessionInfoProto sessionInfo = lwM2MClient.getSession();
this.reportActivityAndRegister(sessionInfo);
@ -264,7 +270,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
}
} catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t);
this.logToTelemetry(lwM2MClient, LOG_LWM2M_ERROR + String.format(": Client update Registration, %s", t.getMessage()));
logService.log(lwM2MClient, LOG_LWM2M_ERROR + String.format(": Client update Registration, %s", t.getMessage()));
}
});
}
@ -274,10 +280,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
* @param observations - !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect
*/
public void unReg(Registration registration, Collection<Observation> observations) {
unRegistrationExecutor.submit(() -> {
executor.submit(() -> {
LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint());
try {
this.logToTelemetry(client, LOG_LWM2M_INFO + ": Client unRegistration");
logService.log(client, LOG_LWM2M_INFO + ": Client unRegistration");
clientContext.unregister(client, registration);
SessionInfoProto sessionInfo = client.getSession();
if (sessionInfo != null) {
@ -291,7 +297,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
log.info("[{}] delete registration: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage());
} catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t);
this.logToTelemetry(client, LOG_LWM2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage()));
logService.log(client, LOG_LWM2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage()));
}
});
}
@ -304,7 +310,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
@Override
public void onSleepingDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
this.logToTelemetry(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is sleeping!");
logService.log(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is sleeping!");
//TODO: associate endpointId with device information.
}
@ -402,29 +408,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
@Override
public void onAwakeDev(Registration registration) {
log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.logToTelemetry(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is awake!");
logService.log(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is awake!");
//TODO: associate endpointId with device information.
}
/**
* @param logMsg - text msg
* @param registrationId - Id of Registration LwM2M Client
*/
@Override
public void logToTelemetry(String registrationId, String logMsg) {
logToTelemetry(clientContext.getClientByRegistrationId(registrationId), logMsg);
}
@Override
public void logToTelemetry(LwM2mClient client, String logMsg) {
if (logMsg != null && client != null && client.getSession() != null) {
if (logMsg.length() > 1024) {
logMsg = logMsg.substring(0, 1024);
}
this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), client.getSession());
}
}
/**
* #1 clientOnlyObserveAfterConnect == true
* - Only Observe Request to the client marked as observe from the profile configuration.
@ -440,10 +427,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
Lwm2mDeviceProfileTransportConfiguration profile = clientContext.getProfile(lwM2MClient.getProfileId());
Set<String> supportedObjects = clientContext.getSupportedIdVerInClient(lwM2MClient);
if (supportedObjects != null && supportedObjects.size() > 0) {
if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientLwM2mSettings().getClientOnlyObserveAfterConnect()) {
// #2
supportedObjects.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId));
}
// #1
this.sendReadRequests(lwM2MClient, profile, supportedObjects);
this.sendObserveRequests(lwM2MClient, profile, supportedObjects);
@ -460,7 +443,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
CountDownLatch latch = new CountDownLatch(targetIds.size());
targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId,
new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, lwM2MClient, versionedId))));
new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId))));
try {
latch.await();
} catch (InterruptedException e) {
@ -474,7 +457,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
CountDownLatch latch = new CountDownLatch(targetIds.size());
targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId,
new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, lwM2MClient, targetId))));
new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, logService, lwM2MClient, targetId))));
try {
latch.await();
} catch (InterruptedException e) {
@ -500,11 +483,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
private void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId) {
TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(targetId).timeout(this.config.getTimeout()).build();
defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(this, lwM2MClient, targetId));
defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(logService, lwM2MClient, targetId));
}
private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) {
sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, lwM2MClient, versionedId));
sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId));
}
private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId, DownlinkRequestCallback<ReadRequest, ReadResponse> callback) {
@ -513,7 +496,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
}
private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId) {
sendObserveRequest(lwM2MClient, versionedId, new TbLwM2MObserveCallback(this, lwM2MClient, versionedId));
sendObserveRequest(lwM2MClient, versionedId, new TbLwM2MObserveCallback(this, logService, lwM2MClient, versionedId));
}
private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) {
@ -523,12 +506,12 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
private void sendWriteAttributesRequest(LwM2mClient lwM2MClient, String targetId, ObjectAttributes params) {
TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(targetId).attributes(params).timeout(this.config.getTimeout()).build();
defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(lwM2MClient, request, new TbLwM2MWriteAttributesCallback(this, lwM2MClient, targetId));
defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(lwM2MClient, request, new TbLwM2MWriteAttributesCallback(logService, lwM2MClient, targetId));
}
private void sendCancelObserveRequest(String versionedId, LwM2mClient client) {
TbLwM2MCancelObserveRequest request = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build();
defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(this, client, versionedId));
defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(logService, client, versionedId));
}
private void updateObjectResourceValue(LwM2mClient client, LwM2mObject lwM2mObject, String pathIdVer) {
@ -561,21 +544,18 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path) {
Registration registration = lwM2MClient.getRegistration();
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) {
/** version != null
* set setClient_fw_info... = value
**/
if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(this, this.defaultLwM2MDownlinkMsgHandler, path);
}
if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getSwUpdate().initReadValue(this, this.defaultLwM2MDownlinkMsgHandler, path);
}
if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path)) ||
(convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) {
LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(this, clientContext);
log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue());
fwUpdate.updateStateOta(this, defaultLwM2MDownlinkMsgHandler, registration, path, ((Long) lwM2mResource.getValue()).intValue());
if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) {
otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) {
otaService.onCurrentFirmwareVersion3Update(lwM2MClient, (String) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_5_VER_ID, registration))) {
otaService.onCurrentFirmwareVersion5Update(lwM2MClient, (String) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_STATE_ID, registration))) {
otaService.onCurrentFirmwareStateUpdate(lwM2MClient, (Long) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_RESULT_ID, registration))) {
otaService.onCurrentFirmwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue());
} else if (path.equals(convertObjectIdToVersionedId(FW_DELIVERY_METHOD, registration))) {
otaService.onCurrentFirmwareDeliveryMethodUpdate(lwM2MClient, (Long) lwM2mResource.getValue());
}
this.updateAttrTelemetry(registration, Collections.singleton(path));
} else {
@ -684,7 +664,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
String resourceName = names.get(pathIdVer);
if (resourceName != null && !resourceName.isEmpty()) {
try {
LwM2mResource resourceValue = getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
@ -720,38 +700,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
return null;
}
/**
* @param pathIdVer - path resource
* @return - value of Resource into format KvProto or null
*/
private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) {
LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)));
} else {
return null;
}
}
/**
* @param lwM2MClient -
* @param path -
* @return - return value of Resource by idPath
*/
private LwM2mResource getResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
LwM2mResource lwm2mResourceValue = null;
ResourceValue resourceValue = lwM2MClient.getResources().get(path);
if (resourceValue != null) {
if (new LwM2mPath(fromVersionedIdToObjectId(path)).isResource()) {
lwm2mResourceValue = lwM2MClient.getResources().get(path).getLwM2mResource();
}
}
return lwm2mResourceValue;
}
@Override
public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) {
if (request.getNode() instanceof LwM2mResource) {
@ -828,6 +776,21 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
clients.forEach(client -> sendCancelObserveRequest(targetId, client));
}
}
// update value in fwInfo
OtherConfiguration newLwM2mSettings = newProfile.getClientLwM2mSettings();
OtherConfiguration oldLwM2mSettings = oldProfile.getClientLwM2mSettings();
if (!newLwM2mSettings.getFwUpdateStrategy().equals(oldLwM2mSettings.getFwUpdateStrategy())
|| (StringUtils.isNotEmpty(newLwM2mSettings.getFwUpdateResource()) &&
!newLwM2mSettings.getFwUpdateResource().equals(oldLwM2mSettings.getFwUpdateResource()))) {
clients.forEach(lwM2MClient -> otaService.onCurrentFirmwareStrategyUpdate(lwM2MClient, newLwM2mSettings));
}
if (!newLwM2mSettings.getSwUpdateStrategy().equals(oldLwM2mSettings.getSwUpdateStrategy())
|| (StringUtils.isNotEmpty(newLwM2mSettings.getSwUpdateResource()) &&
!newLwM2mSettings.getSwUpdateResource().equals(oldLwM2mSettings.getSwUpdateResource()))) {
clients.forEach(lwM2MClient -> otaService.onCurrentSoftwareStrategyUpdate(lwM2MClient, newLwM2mSettings));
}
}
}
@ -890,19 +853,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
}
}
private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) {
if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) {
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build();
defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(this, lwM2MClient, versionedId));
} else {
log.error("Failed update resource [{}] [{}]", versionedId, newValue);
String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad",
LOG_LWM2M_ERROR, versionedId, newValue);
this.logToTelemetry(lwM2MClient, logMsg);
log.info("Failed update resource [{}] [{}]", versionedId, newValue);
}
}
/**
* @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...)
* config attr/telemetry... in profile
@ -912,59 +862,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList());
}
/**
* Get path to resource from profile equal keyName
*
* @param sessionInfo -
* @param keyName -
* @return -
*/
@Override
public String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String keyName) {
return getObjectIdByKeyNameFromProfile(clientContext.getClientBySessionInfo(sessionInfo), keyName);
}
@Override
public String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName) {
Lwm2mDeviceProfileTransportConfiguration profile = clientContext.getProfile(lwM2mClient.getProfileId());
return profile.getObserveAttr().getKeyName().entrySet().stream()
.filter(e -> e.getValue().equals(keyName) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().orElseThrow(
() -> new IllegalArgumentException(keyName + " is not configured in the device profile!")
).getKey();
}
/**
* #1.1 If two names have equal path => last time attribute
* #2.1 if there is a difference in values between the current resource values and the shared attribute values
* => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path)
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
* @param tsKvProtos
*/
public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos) {
log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos);
tsKvProtos.forEach(tsKvProto -> {
String pathIdVer = this.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey());
if (pathIdVer != null) {
// #1.1
if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) {
if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
}
} else {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
}
}
});
// #2.1
lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> {
this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer),
getValueFromKvProto(tsKvProto.getKv()), pathIdVer);
});
}
/**
* @param lwM2MClient -
* @return SessionInfoProto -
@ -1017,95 +914,9 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS);
keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS);
DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch),
v -> onAttributesUpdate(lwM2MClient, v),
v -> attributesService.onAttributesUpdate(lwM2MClient, v),
t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t),
registrationExecutor);
}
}
public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) {
if (lwM2MClient.getRegistration().getSupportedVersion(FW_5_ID) != null) {
SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient);
if (sessionInfo != null) {
DefaultLwM2MUplinkMsgHandler handler = this;
this.transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.FIRMWARE.name()),
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())
&& response.getType().equals(OtaPackageType.FIRMWARE.name())) {
LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(DefaultLwM2MUplinkMsgHandler.this, clientContext);
// if (rpcRequest != null) {
// fwUpdate.setStateUpdate(INITIATED.name());
// }
if (!FAILED.name().equals(fwUpdate.getStateUpdate())) {
log.warn("7) firmware start with ver: [{}]", response.getVersion());
// fwUpdate.setRpcRequest(rpcRequest);
fwUpdate.setCurrentVersion(response.getVersion());
fwUpdate.setCurrentTitle(response.getTitle());
fwUpdate.setCurrentId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()));
// if (rpcRequest == null) {
fwUpdate.sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler);
// } else {
// fwUpdate.writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler);
// }
} else {
String msgError = String.format("OtaPackage device: %s, version: %s, stateUpdate: %s",
lwM2MClient.getDeviceName(), response.getVersion(), fwUpdate.getStateUpdate());
log.warn("7_1 [{}]", msgError);
}
} else {
String msgError = String.format("OtaPackage device: %s, responseStatus: %s",
lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
log.trace(msgError);
// if (rpcRequest != null) {
//TODO: refactor
// sendErrorRpcResponse(rpcRequest, msgError, sessionInfo);
// }
}
}
@Override
public void onError(Throwable e) {
log.trace("Failed to process firmwareUpdate ", e);
}
});
}
}
}
public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient) {
if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) {
SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient);
if (sessionInfo != null) {
DefaultLwM2MUplinkMsgHandler handler = this;
transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.SOFTWARE.name()),
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())
&& response.getType().equals(OtaPackageType.SOFTWARE.name())) {
// lwM2MClient.getSwUpdate().setRpcRequest(rpcRequest);
lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion());
lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle());
lwM2MClient.getSwUpdate().setCurrentId(new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())).getId());
lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler);
// if (rpcRequest == null) {
lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler);
// } else {
// lwM2MClient.getSwUpdate().writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler);
// }
} else {
log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}
}
@Override
public void onError(Throwable e) {
log.trace("Failed to process softwareUpdate ", e);
}
});
}
executor);
}
}
@ -1124,16 +935,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler {
return profile.getObserveAttr().getKeyName();
}
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config
.getModelProvider());
Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathIdVer);
return resourceModel != null && (isWritableNotOptional ?
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
}
public LwM2MTransportServerConfig getConfig() {
return this.config;
}

39
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mTypeServer.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.uplink;
public enum LwM2mTypeServer {
BOOTSTRAP(0, "bootstrap"),
CLIENT(1, "client");
public int code;
public String type;
LwM2mTypeServer(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2mTypeServer fromLwM2mTypeServer(String type) {
for (LwM2mTypeServer sm : LwM2mTypeServer.values()) {
if (sm.type.equals(type)) {
return sm;
}
}
throw new IllegalArgumentException(String.format("Unsupported typeServer type : %d", type));
}
}

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

@ -52,17 +52,9 @@ public interface LwM2mUplinkMsgHandler {
void onAwakeDev(Registration registration);
void logToTelemetry(LwM2mClient client, String msg);
void logToTelemetry(String registrationId, String msg);
void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request);
void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials);
String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String name);
String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName);
LwM2MTransportServerConfig getConfig();
}

4
common/util/pom.xml

@ -36,6 +36,10 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

57
common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java

@ -0,0 +1,57 @@
/**
* 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.common.util;
import org.springframework.util.StopWatch;
/**
* Utility method that extends Spring Framework StopWatch
* It is a MONOTONIC time stopwatch.
* It is a replacement for any measurements with a wall-clock like System.currentTimeMillis()
* It is not affected by leap second, day-light saving and wall-clock adjustments by manual or network time synchronization
* The main features is a single call for common use cases:
* - create and start: TbStopWatch sw = TbStopWatch.startNew()
* - stop and get: sw.stopAndGetTotalTimeMillis() or sw.stopAndGetLastTaskTimeMillis()
* */
public class TbStopWatch extends StopWatch {
public static TbStopWatch startNew(){
TbStopWatch stopWatch = new TbStopWatch();
stopWatch.start();
return stopWatch;
}
public long stopAndGetTotalTimeMillis(){
stop();
return getTotalTimeMillis();
}
public long stopAndGetTotalTimeNanos(){
stop();
return getLastTaskTimeNanos();
}
public long stopAndGetLastTaskTimeMillis(){
stop();
return getLastTaskTimeMillis();
}
public long stopAndGetLastTaskTimeNanos(){
stop();
return getLastTaskTimeNanos();
}
}

16
dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java

@ -21,7 +21,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
@ -32,6 +31,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.stats.DefaultCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.cache.CacheExecutorService;
import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
@ -45,7 +45,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE;
import static org.thingsboard.server.dao.attributes.AttributeUtils.validate;
@Service
@ -59,12 +58,15 @@ public class CachedAttributesService implements AttributesService {
private final AttributesCacheWrapper cacheWrapper;
private final DefaultCounter hitCounter;
private final DefaultCounter missCounter;
private final CacheExecutorService cacheExecutorService;
public CachedAttributesService(AttributesDao attributesDao,
AttributesCacheWrapper cacheWrapper,
StatsFactory statsFactory) {
StatsFactory statsFactory,
CacheExecutorService cacheExecutorService) {
this.attributesDao = attributesDao;
this.cacheWrapper = cacheWrapper;
this.cacheExecutorService = cacheExecutorService;
this.hitCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "hit");
this.missCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "miss");
@ -88,7 +90,7 @@ public class CachedAttributesService implements AttributesService {
// TODO: think if it's a good idea to store 'empty' attributes
cacheWrapper.put(attributeCacheKey, foundAttrKvEntry.orElse(null));
return foundAttrKvEntry;
}, MoreExecutors.directExecutor());
}, cacheExecutorService);
}
}
@ -111,7 +113,7 @@ public class CachedAttributesService implements AttributesService {
notFoundAttributeKeys.removeAll(wrappedCachedAttributes.keySet());
ListenableFuture<List<AttributeKvEntry>> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys);
return Futures.transform(result, foundInDbAttributes -> mergeDbAndCacheAttributes(entityId, scope, cachedAttributes, notFoundAttributeKeys, foundInDbAttributes), MoreExecutors.directExecutor());
return Futures.transform(result, foundInDbAttributes -> mergeDbAndCacheAttributes(entityId, scope, cachedAttributes, notFoundAttributeKeys, foundInDbAttributes), cacheExecutorService);
}
@ -169,7 +171,7 @@ public class CachedAttributesService implements AttributesService {
// TODO: can do if (attributesCache.get() != null) attributesCache.put() instead, but will be more twice more requests to cache
List<String> attributeKeys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList());
future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), MoreExecutors.directExecutor());
future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutorService);
return future;
}
@ -177,7 +179,7 @@ public class CachedAttributesService implements AttributesService {
public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
validate(entityId, scope);
ListenableFuture<List<Void>> future = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys);
future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), MoreExecutors.directExecutor());
future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutorService);
return future;
}

33
dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.cache;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.AbstractListeningExecutor;
@Component
public class CacheExecutorService extends AbstractListeningExecutor {
@Value("${cache.maximumPoolSize}")
private int poolSize;
@Override
protected int getThreadPollSize() {
return poolSize;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java

@ -178,7 +178,7 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration>
.activateUser(activateUser)
.type(type)
.basic(
(type == MapperType.BASIC || type == MapperType.GITHUB) ?
(type == MapperType.BASIC || type == MapperType.GITHUB || type == MapperType.APPLE) ?
OAuth2BasicMapperConfig.builder()
.emailAttributeKey(emailAttributeKey)
.firstNameAttributeKey(firstNameAttributeKey)

3
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java

@ -377,9 +377,6 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se
if (StringUtils.isEmpty(clientRegistration.getScope())) {
throw new DataValidationException("Scope should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) {
throw new DataValidationException("User info uri should be specified!");
}
if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) {
throw new DataValidationException("User name attribute name should be specified!");
}

2
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -387,7 +387,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
client_secret varchar(2048),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),

2
dao/src/main/resources/sql/schema-entities.sql

@ -424,7 +424,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
client_secret varchar(2048),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),

1
dao/src/test/resources/application-test.properties

@ -10,6 +10,7 @@ audit-log.default_query_period=30
audit-log.sink.type=none
cache.type=caffeine
cache.maximumPoolSize=16
#cache.type=redis
caffeine.specs.relations.timeToLiveInMinutes=1440

30
msa/js-executor/api/httpServer.js

@ -0,0 +1,30 @@
/*
* 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.
*/
const config = require('config'),
logger = require('../config/logger')._logger('httpServer'),
express = require('express');
const httpPort = Number(config.get('http_port'));
const app = express();
app.get('/livenessProbe', async (req, res) => {
const date = new Date();
const message = { now: date.toISOString() };
res.send(message);
})
app.listen(httpPort, () => logger.info(`Started http endpoint on port ${httpPort}. Please, use /livenessProbe !`))

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

Loading…
Cancel
Save