Browse Source

Merge branch 'master' into task/4098-modbus-tabs-validation

pull/11319/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
799474a066
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java
  2. 78
      application/src/main/java/org/thingsboard/server/config/RequestSizeFilter.java
  3. 4
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  5. 10
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  7. 37
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  8. 2
      application/src/main/resources/thingsboard.yml
  9. 33
      application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java
  10. 49
      application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
  11. 47
      application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
  12. 41
      application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
  13. 23
      common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java
  14. 250
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  15. 190
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  16. 51
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  17. 3
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  18. 22
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  19. 2
      pom.xml
  20. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  21. 2
      transport/http/src/main/resources/tb-http-transport.yml

3
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java

@ -50,6 +50,9 @@ public abstract class RuleEngineComponentActor<T extends EntityId, P extends Com
}
private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) {
if (processor == null) {
return;
}
systemContext.getNotificationRuleProcessor().process(RuleEngineComponentLifecycleEventTrigger.builder()
.tenantId(tenantId)
.ruleChainId(getRuleChainId())

78
application/src/main/java/org/thingsboard/server/config/RequestSizeFilter.java

@ -0,0 +1,78 @@
/**
* Copyright © 2016-2024 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.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import java.io.IOException;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestSizeFilter extends OncePerRequestFilter {
private final List<String> urls = List.of("/api/plugins/rpc/**", "/api/rpc/**");
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final ThingsboardErrorResponseHandler errorResponseHandler;
@Value("${transport.http.max_payload_size:65536}")
private int maxPayloadSize;
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request.getContentLength() > maxPayloadSize) {
if (log.isDebugEnabled()) {
log.debug("Too large payload size. Url: {}, client ip: {}, content length: {}", request.getRequestURL(),
request.getRemoteAddr(), request.getContentLength());
}
errorResponseHandler.handle(new MaxPayloadSizeExceededException(), response);
return;
}
chain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
for (String url : urls) {
if (pathMatcher.match(url, request.getRequestURI())) {
return false;
}
}
return true;
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
@Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
}

4
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -124,6 +124,9 @@ public class ThingsboardSecurityConfiguration {
@Autowired
private RateLimitProcessingFilter rateLimitProcessingFilter;
@Autowired
private RequestSizeFilter requestSizeFilter;
@Bean
protected FilterRegistrationBean<ShallowEtagHeaderFilter> buildEtagFilter() throws Exception {
ShallowEtagHeaderFilter etagFilter = new ShallowEtagHeaderFilter();
@ -225,6 +228,7 @@ public class ThingsboardSecurityConfiguration {
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(requestSizeFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null) {
http.oauth2Login(login -> login

2
application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java

@ -118,6 +118,7 @@ public class RpcV2Controller extends AbstractRpcController {
@ApiResponse(responseCode = "200", description = "Persistent RPC request was saved to the database or lightweight RPC request was sent to the device."),
@ApiResponse(responseCode = "400", description = "Invalid structure of the request."),
@ApiResponse(responseCode = "401", description = "User is not authorized to send the RPC request. Most likely, User belongs to different Customer or Tenant."),
@ApiResponse(responseCode = "413", description = "Request payload is too large"),
@ApiResponse(responseCode = "504", description = "Timeout to process the RPC call. Most likely, device is offline."),
})
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@ -136,6 +137,7 @@ public class RpcV2Controller extends AbstractRpcController {
@ApiResponse(responseCode = "200", description = "Persistent RPC request was saved to the database or lightweight RPC response received."),
@ApiResponse(responseCode = "400", description = "Invalid structure of the request."),
@ApiResponse(responseCode = "401", description = "User is not authorized to send the RPC request. Most likely, User belongs to different Customer or Tenant."),
@ApiResponse(responseCode = "413", description = "Request payload is too large"),
@ApiResponse(responseCode = "504", description = "Timeout to process the RPC call. Most likely, device is offline."),
})
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")

10
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java

@ -46,6 +46,7 @@ import org.springframework.web.util.WebUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
@ -146,6 +147,8 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
handleAccessDeniedException(response);
} else if (exception instanceof AuthenticationException) {
handleAuthenticationException((AuthenticationException) exception, response);
} else if (exception instanceof MaxPayloadSizeExceededException) {
handleMaxPayloadSizeExceededException(response, (MaxPayloadSizeExceededException) exception);
} else {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of(exception.getMessage(),
@ -184,6 +187,13 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS));
}
private void handleMaxPayloadSizeExceededException(HttpServletResponse response, MaxPayloadSizeExceededException exception) throws IOException {
response.setStatus(HttpStatus.PAYLOAD_TOO_LARGE.value());
JacksonUtil.writeValue(response.getWriter(),
ThingsboardErrorResponse.of(exception.getMessage(),
ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.PAYLOAD_TOO_LARGE));
}
private void handleSubscriptionException(ThingsboardException subscriptionException, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.FORBIDDEN.value());
JacksonUtil.writeValue(response.getWriter(),

3
application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -194,7 +195,7 @@ public class EdgeEventSourcingListener {
}
break;
case ALARM:
if (entity instanceof AlarmApiCallResult || entity instanceof Alarm) {
if (entity instanceof AlarmApiCallResult || entity instanceof Alarm || entity instanceof EntityAlarm) {
return false;
}
break;

37
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AssetId;
@ -45,6 +47,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -205,7 +208,7 @@ public class DefaultTbClusterService implements TbClusterService {
return;
}
} else {
HasRuleEngineProfile ruleEngineProfile = getRuleEngineProfileForEntityOrElseNull(tenantId, entityId);
HasRuleEngineProfile ruleEngineProfile = getRuleEngineProfileForEntityOrElseNull(tenantId, entityId, tbMsg);
tbMsg = transformMsg(tbMsg, ruleEngineProfile);
}
@ -213,13 +216,39 @@ public class DefaultTbClusterService implements TbClusterService {
toRuleEngineMsgs.incrementAndGet();
}
private HasRuleEngineProfile getRuleEngineProfileForEntityOrElseNull(TenantId tenantId, EntityId entityId) {
HasRuleEngineProfile getRuleEngineProfileForEntityOrElseNull(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
if (entityId.getEntityType().equals(EntityType.DEVICE)) {
return deviceProfileCache.get(tenantId, new DeviceId(entityId.getId()));
if (TbMsgType.ENTITY_DELETED.equals(tbMsg.getInternalType())) {
try {
Device deletedDevice = JacksonUtil.fromString(tbMsg.getData(), Device.class);
if (deletedDevice == null) {
return null;
}
return deviceProfileCache.get(tenantId, deletedDevice.getDeviceProfileId());
} catch (Exception e) {
log.warn("[{}][{}] Failed to deserialize device: {}", tenantId, entityId, tbMsg, e);
return null;
}
} else {
return deviceProfileCache.get(tenantId, new DeviceId(entityId.getId()));
}
} else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) {
return deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()));
} else if (entityId.getEntityType().equals(EntityType.ASSET)) {
return assetProfileCache.get(tenantId, new AssetId(entityId.getId()));
if (TbMsgType.ENTITY_DELETED.equals(tbMsg.getInternalType())) {
try {
Asset deletedAsset = JacksonUtil.fromString(tbMsg.getData(), Asset.class);
if (deletedAsset == null) {
return null;
}
return assetProfileCache.get(tenantId, deletedAsset.getAssetProfileId());
} catch (Exception e) {
log.warn("[{}][{}] Failed to deserialize asset: {}", tenantId, entityId, tbMsg, e);
return null;
}
} else {
return assetProfileCache.get(tenantId, new AssetId(entityId.getId()));
}
} else if (entityId.getEntityType().equals(EntityType.ASSET_PROFILE)) {
return assetProfileCache.get(tenantId, new AssetProfileId(entityId.getId()));
}

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

@ -959,6 +959,8 @@ transport:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
# HTTP maximum request processing timeout in milliseconds
max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
# Maximum request size
max_payload_size: "${HTTP_MAX_PAYLOAD_SIZE:65536}" # max payload size in bytes
# Local MQTT transport parameters
mqtt:
# Enable/disable mqtt transport protocol.

33
application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java

@ -22,6 +22,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
@ -38,6 +39,9 @@ import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@TestPropertySource(properties = {
"transport.http.max_payload_size=10000"
})
public class RpcControllerTest extends AbstractControllerTest {
private Tenant savedTenant;
@ -78,13 +82,18 @@ public class RpcControllerTest extends AbstractControllerTest {
}
private ObjectNode createDefaultRpc() {
return createDefaultRpc(100);
}
private ObjectNode createDefaultRpc(int size) {
ObjectNode rpc = JacksonUtil.newObjectNode();
rpc.put("method", "setGpio");
ObjectNode params = JacksonUtil.newObjectNode();
params.put("pin", 7);
params.put("value", 1);
String value = "a".repeat(size - 83);
params.put("value", value);
rpc.set("params", params);
rpc.put("persistent", true);
@ -122,6 +131,28 @@ public class RpcControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedDevice.getId(), savedRpc.getDeviceId());
}
@Test
public void testSaveLargeRpc() throws Exception {
Device device = createDefaultDevice();
Device savedDevice = doPost("/api/device", device, Device.class);
ObjectNode rpc = createDefaultRpc(10001);
doPost(
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(),
JacksonUtil.toString(rpc),
String.class,
status().isPayloadTooLarge()
);
ObjectNode validRpc = createDefaultRpc(10000);
doPost(
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(),
JacksonUtil.toString(validRpc),
String.class,
status().isOk()
);
}
@Test
public void testDeleteRpc() throws Exception {
Device device = createDefaultDevice();

49
application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java

@ -23,11 +23,20 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -249,4 +258,44 @@ public class DefaultTbClusterServiceTest {
queue.setPartitions(10);
return queue;
}
@Test
public void testGetRuleEngineProfileForUpdatedAndDeletedDevice() {
DeviceId deviceId = new DeviceId(UUID.randomUUID());
TenantId tenantId = new TenantId(UUID.randomUUID());
DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID());
Device device = new Device(deviceId);
device.setDeviceProfileId(deviceProfileId);
// device updated
TbMsg tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_UPDATED).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg);
verify(deviceProfileCache, times(1)).get(tenantId, deviceId);
// device deleted
tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(device)).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg);
verify(deviceProfileCache, times(1)).get(tenantId, deviceProfileId);
}
@Test
public void testGetRuleEngineProfileForUpdatedAndDeletedAsset() {
AssetId assetId = new AssetId(UUID.randomUUID());
TenantId tenantId = new TenantId(UUID.randomUUID());
AssetProfileId assetProfileId = new AssetProfileId(UUID.randomUUID());
Asset asset = new Asset(assetId);
asset.setAssetProfileId(assetProfileId);
// asset updated
TbMsg tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_UPDATED).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetId);
// asset deleted
tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(asset)).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetProfileId);
}
}

47
application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java

@ -39,12 +39,13 @@ import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
@DaoSqlTest
@TestPropertySource(properties = {
"js.evaluator=local",
"js.max_script_body_size=50",
"js.max_script_body_size=10000",
"js.max_total_args_size=50",
"js.max_result_size=50",
"js.local.max_errors=2",
@ -87,7 +88,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
@Test
void givenSimpleScriptMultiThreadTestPerformance() throws ExecutionException, InterruptedException, TimeoutException {
int iterations = 1000*4;
int iterations = 1000 * 4;
List<ListenableFuture<Object>> futures = new ArrayList<>(iterations);
UUID scriptId = evalScript("return msg.temperature > 20 ;");
// warmup
@ -125,7 +126,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
@Test
void givenTooBigScriptForEval_thenReturnError() {
String hugeScript = "var a = 'qwertyqwertywertyqwabababer'; return {a: a};";
String hugeScript = "var a = '" + "a".repeat(10000) + "'; return {a: a};";
assertThatThrownBy(() -> {
evalScript(hugeScript);
@ -159,6 +160,46 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
assertThatScriptIsBlocked(scriptId);
}
@Test
void givenComplexScript_testCompile() {
String script = """
function(data) {
if (data.get("propertyA") == "a special value 1" || data.get("propertyA") == "a special value 2") {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyC") == "a special value 1" || data.get("propertyJ") == "a special value 1" || data.get("propertyV") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "4" && (data.get("propertyD") == "a special value 1" || data.get("propertyV") == "a special value 1" || data.get("propertyW") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 2" && (data.get("propertyE") == "a special value 1" || data.get("propertyF") == "a special value 1" || data.get("propertyL") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyE") == "a special value 1" || data.get("propertyF") == "a special value 1" || data.get("propertyL") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else if (data.get("propertyB") == "a special value 3" && (data.get("propertyM") == "a special value 1" || data.get("propertyY") == "a special value 1" || data.get("propertyH") == "a special value 1")) {
return "a special value 1";
} else {
return "0"
};
}
""";
// with delight-nashorn-sandbox 0.4.2, this would throw delight.nashornsandbox.exceptions.ScriptCPUAbuseException: Regular expression running for too many iterations. The operation could NOT be gracefully interrupted.
assertDoesNotThrow(() -> {
evalScript(script);
});
}
private void assertThatScriptIsBlocked(UUID scriptId) {
assertThatThrownBy(() -> {
invokeScript(scriptId, "{}");

41
application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java

@ -20,6 +20,7 @@ import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
@ -29,6 +30,7 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@ -40,6 +42,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@TestPropertySource(properties = {
"transport.http.enabled=true",
"transport.http.max_payload_size=10000"
})
public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
@ -74,6 +77,44 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
}
@Test
public void testReplyToCommandWithLargeResponse() throws Exception {
String errorResponse = doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc/5",
JacksonUtil.toString(createRpcResponsePayload(10001)),
String.class,
status().isPayloadTooLarge());
assertThat(errorResponse).contains("Payload size exceeds the limit");
doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc/5",
JacksonUtil.toString(createRpcResponsePayload(10000)),
String.class,
status().isOk());
}
@Test
public void testPostRpcRequestWithLargeResponse() throws Exception {
String errorResponse = doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc",
JacksonUtil.toString(createRpcRequestPayload(10001)),
String.class,
status().isPayloadTooLarge());
assertThat(errorResponse).contains("Payload size exceeds the limit");
doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc",
JacksonUtil.toString(createRpcRequestPayload(10000)),
String.class,
status().isOk());
}
private String createRpcResponsePayload(int size) {
String value = "a".repeat(size - 19);
return "{\"result\":\"" + value + "\"}";
}
private String createRpcRequestPayload(int size) {
String value = "a".repeat(size - 50);
return "{\"method\":\"get\",\"params\":{\"value\":\"" + value + "\"}}";
}
protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception {
MockHttpServletRequestBuilder getRequest;
getRequest = get(urlTemplate, urlVariables);

23
common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java

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

250
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -194,6 +194,22 @@ public class TbUtils {
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
List.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
List.class, int.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
byte[].class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
byte[].class, int.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat",
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseLittleEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToDouble",
String.class)));
parserConfig.addImport("parseBigEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToDouble",
@ -218,6 +234,22 @@ public class TbUtils {
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
List.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
List.class, int.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
byte[].class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
byte[].class, int.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble",
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed",
double.class, int.class)));
parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed",
@ -292,6 +324,9 @@ public class TbUtils {
String.class)));
parserConfig.addImport("isHexadecimal", new MethodStub(TbUtils.class.getMethod("isHexadecimal",
String.class)));
parserConfig.addImport("byteArrayToExecutionArrayList", new MethodStub(TbUtils.class.getMethod("byteArrayToExecutionArrayList",
ExecutionContext.class, byte[].class)));
}
public static String btoa(String input) {
@ -753,7 +788,7 @@ public class TbUtils {
long bits = Double.doubleToRawLongBits(d);
// Format the integer bits as a hexadecimal string
String result = String.format("0x%16X", bits);
String result = String.format("0x%016X", bits);
return bigEndian ? result : reverseHexStringByOrder(result);
}
@ -770,15 +805,15 @@ public class TbUtils {
}
public static int parseBytesToInt(List<Byte> data) {
return parseBytesToInt(Bytes.toArray(data));
return parseBytesToInt(data, 0);
}
public static int parseBytesToInt(List<Byte> data, int offset) {
return parseBytesToInt(Bytes.toArray(data), offset);
return parseBytesToInt(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX));
}
public static int parseBytesToInt(List<Byte> data, int offset, int length) {
return parseBytesToInt(Bytes.toArray(data), offset, length);
return parseBytesToInt(data, offset, length, true);
}
public static int parseBytesToInt(List<Byte> data, int offset, int length, boolean bigEndian) {
@ -798,15 +833,7 @@ public class TbUtils {
}
public static int parseBytesToInt(byte[] data, int offset, int length, boolean bigEndian) {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if (length > BYTES_LEN_INT_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
validationNumberByLength(data, offset, length, BYTES_LEN_INT_MAX);
var bb = ByteBuffer.allocate(4);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
@ -818,15 +845,15 @@ public class TbUtils {
}
public static long parseBytesToLong(List<Byte> data) {
return parseBytesToLong(Bytes.toArray(data));
return parseBytesToLong(data, 0);
}
public static long parseBytesToLong(List<Byte> data, int offset) {
return parseBytesToLong(Bytes.toArray(data), offset);
return parseBytesToLong(data, offset, validateLength(data.size(), offset, BYTES_LEN_LONG_MAX));
}
public static long parseBytesToLong(List<Byte> data, int offset, int length) {
return parseBytesToLong(Bytes.toArray(data), offset, length);
return parseBytesToLong(data, offset, length, true);
}
public static long parseBytesToLong(List<Byte> data, int offset, int length, boolean bigEndian) {
@ -846,15 +873,7 @@ public class TbUtils {
}
public static long parseBytesToLong(byte[] data, int offset, int length, boolean bigEndian) {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if (length > BYTES_LEN_LONG_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
validationNumberByLength(data, offset, length, BYTES_LEN_LONG_MAX);
var bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
@ -866,15 +885,15 @@ public class TbUtils {
}
public static float parseBytesToFloat(List data) {
return parseBytesToFloat(Bytes.toArray(data), 0);
return parseBytesToFloat(data, 0);
}
public static float parseBytesToFloat(List data, int offset) {
return parseBytesToFloat(Bytes.toArray(data), offset, BYTES_LEN_INT_MAX);
return parseBytesToFloat(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX));
}
public static float parseBytesToFloat(List data, int offset, int length) {
return parseBytesToFloat(Bytes.toArray(data), offset, length, true);
return parseBytesToFloat(data, offset, length, true);
}
public static float parseBytesToFloat(List data, int offset, int length, boolean bigEndian) {
@ -886,7 +905,7 @@ public class TbUtils {
}
public static float parseBytesToFloat(byte[] data, int offset) {
return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX);
return parseBytesToFloat(data, offset, validateLength(data.length, offset, BYTES_LEN_INT_MAX));
}
public static float parseBytesToFloat(byte[] data, int offset, int length) {
@ -894,39 +913,65 @@ public class TbUtils {
}
public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) {
if (length > BYTES_LEN_INT_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_INT_MAX + " bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian);
if (bytesToNumber.length < BYTES_LEN_INT_MAX) {
byte[] extendedBytes = new byte[BYTES_LEN_INT_MAX];
Arrays.fill(extendedBytes, (byte) 0);
System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length);
bytesToNumber = extendedBytes;
var bb = ByteBuffer.allocate(BYTES_LEN_INT_MAX);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
float floatValue = ByteBuffer.wrap(bytesToNumber).getFloat();
if (!Float.isNaN(floatValue)) {
return floatValue;
} else {
long longValue = parseBytesToLong(bytesToNumber, 0, BYTES_LEN_INT_MAX);
BigDecimal bigDecimalValue = new BigDecimal(longValue);
return bigDecimalValue.floatValue();
bb.position(bigEndian ? BYTES_LEN_INT_MAX - length : 0);
bb.put(data, offset, length);
bb.position(0);
float floatValue = bb.getFloat();
if (Float.isNaN(floatValue)) {
throw new NumberFormatException("byte[] 0x" + bytesToHex(data) + " is a Not-a-Number (NaN) value");
}
return floatValue;
}
public static float parseBytesIntToFloat(List data) {
return parseBytesIntToFloat(data, 0);
}
public static float parseBytesIntToFloat(List data, int offset) {
return parseBytesIntToFloat(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX));
}
public static float parseBytesIntToFloat(List data, int offset, int length) {
return parseBytesIntToFloat(data, offset, length, true);
}
public static float parseBytesIntToFloat(List data, int offset, int length, boolean bigEndian) {
return parseBytesIntToFloat(Bytes.toArray(data), offset, length, bigEndian);
}
public static float parseBytesIntToFloat(byte[] data) {
return parseBytesIntToFloat(data, 0);
}
public static float parseBytesIntToFloat(byte[] data, int offset) {
return parseBytesIntToFloat(data, offset, validateLength(data.length, offset, BYTES_LEN_INT_MAX));
}
public static float parseBytesIntToFloat(byte[] data, int offset, int length) {
return parseBytesIntToFloat(data, offset, length, true);
}
public static float parseBytesIntToFloat(byte[] data, int offset, int length, boolean bigEndian) {
byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian, BYTES_LEN_INT_MAX);
long longValue = parseBytesToLong(bytesToNumber, 0, length);
BigDecimal bigDecimalValue = new BigDecimal(longValue);
return bigDecimalValue.floatValue();
}
public static double parseBytesToDouble(List data) {
return parseBytesToDouble(Bytes.toArray(data));
return parseBytesToDouble(data, 0);
}
public static double parseBytesToDouble(List data, int offset) {
return parseBytesToDouble(Bytes.toArray(data), offset);
return parseBytesToDouble(data, offset, validateLength(data.size(), offset, BYTES_LEN_LONG_MAX));
}
public static double parseBytesToDouble(List data, int offset, int length) {
return parseBytesToDouble(Bytes.toArray(data), offset, length);
return parseBytesToDouble(data, offset, length, true);
}
public static double parseBytesToDouble(List data, int offset, int length, boolean bigEndian) {
@ -938,7 +983,7 @@ public class TbUtils {
}
public static double parseBytesToDouble(byte[] data, int offset) {
return parseBytesToDouble(data, offset, BYTES_LEN_LONG_MAX);
return parseBytesToDouble(data, offset, validateLength(data.length, offset, BYTES_LEN_LONG_MAX));
}
public static double parseBytesToDouble(byte[] data, int offset, int length) {
@ -946,36 +991,58 @@ public class TbUtils {
}
public static double parseBytesToDouble(byte[] data, int offset, int length, boolean bigEndian) {
if (length > BYTES_LEN_LONG_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian);
if (bytesToNumber.length < BYTES_LEN_LONG_MAX) {
byte[] extendedBytes = new byte[BYTES_LEN_LONG_MAX];
Arrays.fill(extendedBytes, (byte) 0);
System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length);
bytesToNumber = extendedBytes;
var bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
double doubleValue = ByteBuffer.wrap(bytesToNumber).getDouble();
if (!Double.isNaN(doubleValue)) {
return doubleValue;
} else {
BigInteger bigInt = new BigInteger(1, bytesToNumber);
BigDecimal bigDecimalValue = new BigDecimal(bigInt);
return bigDecimalValue.doubleValue();
bb.position(bigEndian ? BYTES_LEN_LONG_MAX - length : 0);
bb.put(data, offset, length);
bb.position(0);
double doubleValue = bb.getDouble();
if (Double.isNaN(doubleValue)) {
throw new NumberFormatException("byte[] 0x" + bytesToHex(data) + " is a Not-a-Number (NaN) value");
}
return doubleValue;
}
private static byte[] prepareBytesToNumber(byte[] data, int offset, int length, boolean bigEndian) {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if ((offset + length) > data.length) {
throw new IllegalArgumentException("Default length is always " + length + " bytes. Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
public static double parseBytesLongToDouble(List data) {
return parseBytesLongToDouble(data, 0);
}
public static double parseBytesLongToDouble(List data, int offset) {
return parseBytesLongToDouble(data, offset, validateLength(data.size(), offset, BYTES_LEN_LONG_MAX));
}
public static double parseBytesLongToDouble(List data, int offset, int length) {
return parseBytesLongToDouble(data, offset, length, true);
}
public static double parseBytesLongToDouble(List data, int offset, int length, boolean bigEndian) {
return parseBytesLongToDouble(Bytes.toArray(data), offset, length, bigEndian);
}
public static double parseBytesLongToDouble(byte[] data) {
return parseBytesLongToDouble(data, 0);
}
public static double parseBytesLongToDouble(byte[] data, int offset) {
return parseBytesLongToDouble(data, offset, validateLength(data.length, offset, BYTES_LEN_LONG_MAX));
}
public static double parseBytesLongToDouble(byte[] data, int offset, int length) {
return parseBytesLongToDouble(data, offset, length, true);
}
public static double parseBytesLongToDouble(byte[] data, int offset, int length, boolean bigEndian) {
byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian, BYTES_LEN_LONG_MAX);
BigInteger bigInt = new BigInteger(1, bytesToNumber);
BigDecimal bigDecimalValue = new BigDecimal(bigInt);
return bigDecimalValue.doubleValue();
}
private static byte[] prepareBytesToNumber(byte[] data, int offset, int length, boolean bigEndian,
int bytesLenMax) {
validationNumberByLength(data, offset, length, bytesLenMax);
byte[] dataBytesArray = Arrays.copyOfRange(data, offset, (offset + length));
if (!bigEndian) {
ArrayUtils.reverse(dataBytesArray);
@ -1168,6 +1235,15 @@ public class TbUtils {
return str.matches("^-?(0[xX])?[0-9a-fA-F]+$") ? HEX_RADIX : -1;
}
public static List byteArrayToExecutionArrayList(ExecutionContext ctx, byte[] byteArray) {
List<Byte> byteList = new ArrayList<>();
for (byte b : byteArray) {
byteList.add(b);
}
List list = new ExecutionArrayList(byteList, ctx);
return list;
}
private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +
@ -1194,5 +1270,23 @@ public class TbUtils {
String result = reversedHex.toString();
return isHexPref ? "0x" + result : result;
}
private static void validationNumberByLength(byte[] data, int offset, int length, int bytesLenMax) {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
if (length > bytesLenMax) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + bytesLenMax + " bytes is allowed!");
}
}
private static int validateLength(int dataLength, int offset, int bytesLenMax) {
return (dataLength < offset) ? dataLength : Math.min((dataLength - offset), bytesLenMax);
}
}

190
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -276,7 +276,10 @@ public class TbUtilsTest {
@Test
public void parseBytesToFloat() {
byte[] floatValByte = {65, -22, 98, -52};
byte[] floatValByte = {0x0A};
Assertions.assertEquals(0, Float.compare(1.4E-44f, TbUtils.parseBytesToFloat(floatValByte)));
floatValByte = new byte[]{65, -22, 98, -52};
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false)));
@ -284,40 +287,101 @@ public class TbUtilsTest {
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValList, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValList, 0, 4, false)));
// 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF}
floatValByte = new byte[]{-1, -1, -1, -1};
float floatExpectedBe = 4294.9673f;
float floatExpectedLe = 4.2949673E9f;
// 1.1803216E8f == 0x4CE120E4
floatValByte = new byte[]{0x4C, (byte) 0xE1, (byte) 0x20, (byte) 0xE4};
float floatExpectedBe = 118.03216f;
float actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false)));
floatValList = Bytes.asList(floatValByte);
actualBe = TbUtils.parseBytesToFloat(floatValList, 0);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 4, false)));
// 2 143 289 344L == {0x7F, 0xC0, 0x00, 0x00}
floatValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00};
floatExpectedBe = 2143.3547f;
floatExpectedLe = -3.984375f;
actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
float floatExpectedLe = 8.0821E-41f;
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2, false)));
floatExpectedBe = 2.7579E-41f;
Assertions.assertEquals(0, Float.compare(floatExpectedBe, TbUtils.parseBytesToFloat(floatValByte, 0, 2)));
floatValList = Bytes.asList(floatValByte);
floatExpectedLe = 4.2908055E9f;
actualBe = TbUtils.parseBytesToFloat(floatValList, 0);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
floatExpectedLe = 3.019557E-39f;
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 3, false)));
// 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF}
floatValByte = new byte[]{-1, -1, -1, -1};
String message = "is a Not-a-Number (NaN) value";
try {
TbUtils.parseBytesToFloat(floatValByte, 0, 4, true);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains(message));
}
// "01752B0367FA000500010488 FFFFFFFF FFFFFFFF 33";
String intToHexBe = "01752B0367FA000500010488FFFFFFFFFFFFFFFF33";
floatExpectedLe = 4294.9673f;
floatValList = TbUtils.hexToBytes(ctx, intToHexBe);
float actualLe = TbUtils.parseBytesToFloat(floatValList, 12, 4, false);
Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000));
actualLe = TbUtils.parseBytesToFloat(floatValList, 12 + 4, 4, false);
Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000));
try {
TbUtils.parseBytesToFloat(floatValList, 12, 4, false);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains(message));
}
}
@Test
public void parseBytesIntToFloat() {
byte[] intValByte = {0x00, 0x00, 0x00, 0x0A};
Float valueExpected = 10.0f;
Float valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, true);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, false);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 2, 2, true);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 2560.0f;
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 2, 2, false);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 10.0f;
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 0, 4, true);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 1.6777216E8f;
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 0, 4, false);
Assertions.assertEquals(valueExpected, valueActual);
String dataAT101 = "0x01756403671B01048836BF7701F000090722050000";
List<Byte> byteAT101 = TbUtils.hexToBytes(ctx, dataAT101);
float latitudeExpected = 24.62495f;
int offset = 9;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 4, false);
Assertions.assertEquals(latitudeExpected, valueActual / 1000000);
float longitudeExpected = 118.030576f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset + 4, 4, false);
Assertions.assertEquals(longitudeExpected, valueActual / 1000000);
valueExpected = 9.185175E8f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset);
Assertions.assertEquals(valueExpected, valueActual);
// 0x36BF
valueExpected = 14015.0f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 2);
Assertions.assertEquals(valueExpected, valueActual);
// 0xBF36
valueExpected = 48950.0f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 2, false);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 0.0f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, byteAT101.size());
Assertions.assertEquals(valueExpected, valueActual);
try {
TbUtils.parseBytesIntToFloat(byteAT101, byteAT101.size() + 1);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("is out of bounds for array with length:"));
}
}
@Test
@ -390,7 +454,10 @@ public class TbUtilsTest {
@Test
public void parseBytesToDouble() {
byte[] doubleValByte = {64, -101, 4, -79, 12, -78, -107, -22};
byte[] doubleValByte = {0x0A};
Assertions.assertEquals(0, Double.compare(4.9E-323, TbUtils.parseBytesToDouble(doubleValByte)));
doubleValByte = new byte[]{64, -101, 4, -79, 12, -78, -107, -22};
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValByte, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false)));
@ -398,34 +465,61 @@ public class TbUtilsTest {
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValList, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false)));
// 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1};
double doubleExpectedBe = 18446.744073709553d;
double doubleExpectedLe = 1.8446744073709552E19d;
double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe / 1000000000000000L));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false)));
doubleValList = Bytes.asList(doubleValByte);
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, TbUtils.parseBytesToDouble(doubleValList, 0) / 1000000000000000L));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false)));
doubleValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00, 0x7F, (byte) 0xC0, (byte) 0xFF, 0x00};
doubleExpectedBe = 2387013.651780523d;
doubleExpectedLe = 7.234601680440024E-304d;
actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
double doubleExpectedBe = 2387013.651780523d;
double doubleExpectedLe = 7.234601680440024E-304d;
double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
BigDecimal bigDecimal = new BigDecimal(actualBe);
// We move the decimal point to the left by 301 positions
actualBe = bigDecimal.movePointLeft(301).doubleValue();
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false)));
doubleValList = Bytes.asList(doubleValByte);
doubleExpectedLe = 5.828674572203954E303d;
actualBe = TbUtils.parseBytesToDouble(doubleValList, 0);
bigDecimal = new BigDecimal(actualBe);
actualBe = bigDecimal.movePointLeft(301).doubleValue();
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 5, false)));
doubleExpectedLe = 26950.174646662283d;
double actualLe = TbUtils.parseBytesToDouble(doubleValList, 0, 5, false);
bigDecimal = new BigDecimal(actualLe);
actualLe = bigDecimal.movePointRight(316).doubleValue();
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, actualLe));
// 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1};
String message = "is a Not-a-Number (NaN) value";
try {
TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains(message));
}
}
@Test
public void parseBytesLongToDouble() {
byte[] longValByte = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A};
Double valueExpected = 10.0d;
Double valueActual = TbUtils.parseBytesLongToDouble(longValByte);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 7, 1, true);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 7, 1, false);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 6, 2, true);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 2560.0d;
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 6, 2, false);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 10.0d;
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 0, 8, true);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 7.2057594037927936E17d;
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 0, 8, false);
Assertions.assertEquals(valueExpected, valueActual);
}
@Test
@ -717,6 +811,13 @@ public class TbUtilsTest {
Assertions.assertEquals(value, valueActual);
valueActual = TbUtils.parseHexToFloat(valueHexRev, false);
Assertions.assertEquals(value, valueActual);
String valueHex = "0x0000000A";
float expectedValue = 1.4E-44f;
valueActual = TbUtils.parseHexToFloat(valueHex);
Assertions.assertEquals(expectedValue, valueActual);
actual = TbUtils.floatToHex(expectedValue);
Assertions.assertEquals(valueHex, actual);
}
// If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f).
@ -748,6 +849,13 @@ public class TbUtilsTest {
actual = TbUtils.doubleToHex(doubleVal, false);
String expectedHexRev = "0xEA95B20CB1049B40";
Assertions.assertEquals(expectedHexRev, actual);
String valueHex = "0x000000000000000A";
Double expectedValue = 4.9E-323;
valueActual = TbUtils.parseHexToDouble(valueHex);
Assertions.assertEquals(expectedValue, valueActual);
actual = TbUtils.doubleToHex(expectedValue);
Assertions.assertEquals(valueHex, actual);
}
@Test

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

@ -21,9 +21,14 @@ import com.google.gson.JsonParser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
@ -31,6 +36,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@ -38,6 +44,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
@ -45,11 +53,11 @@ import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException;
import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -68,7 +76,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMs
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@ -128,6 +136,9 @@ public class DeviceApiController implements TbTransportService {
private static final String ACCESS_TOKEN_PARAM_DESCRIPTION = "Your device access token.";
@Value("${transport.http.max_payload_size:65536}")
private int maxPayloadSize;
@Autowired
private HttpTransportContext transportContext;
@ -265,6 +276,11 @@ public class DeviceApiController implements TbTransportService {
@Operation(summary = "Reply to RPC commands (replyToCommand)",
description = "Replies to server originated RPC command identified by 'requestId' parameter. The response is arbitrary JSON.\n\n" +
REQUIRE_ACCESS_TOKEN)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "RPC reply to command request was sent to Core."),
@ApiResponse(responseCode = "400", description = "Invalid structure of the request."),
@ApiResponse(responseCode = "413", description = "Request payload is too large."),
})
@RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> replyToCommand(
@Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN"))
@ -272,7 +288,8 @@ public class DeviceApiController implements TbTransportService {
@Parameter(description = "RPC request id from the incoming RPC request", required = true , schema = @Schema(defaultValue = "123"))
@PathVariable("requestId") Integer requestId,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Reply to the RPC request, JSON. For example: {\"status\":\"success\"}", required = true)
@RequestBody String json) {
@RequestBody String json, HttpServletRequest httpServletRequest) {
checkPayloadSize(httpServletRequest);
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -292,12 +309,18 @@ public class DeviceApiController implements TbTransportService {
"{\"result\": 4}" +
MARKDOWN_CODE_BLOCK_END +
REQUIRE_ACCESS_TOKEN)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "RPC request to server was sent to Rule Engine."),
@ApiResponse(responseCode = "400", description = "Invalid structure of the request."),
@ApiResponse(responseCode = "413", description = "Request payload too large."),
})
@RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> postRpcRequest(
@Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN"))
@PathVariable("deviceToken") String deviceToken,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The RPC request JSON", required = true)
@RequestBody String json) {
@RequestBody String json, HttpServletRequest httpServletRequest) {
checkPayloadSize(httpServletRequest);
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -420,6 +443,12 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
private void checkPayloadSize(HttpServletRequest httpServletRequest) {
if (httpServletRequest.getContentLength() > maxPayloadSize) {
throw new MaxPayloadSizeExceededException();
}
}
private DeferredResult<ResponseEntity> getOtaPackageCallback(String deviceToken, String title, String version, int size, int chunk, OtaPackageType firmwareType) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
@ -608,6 +637,20 @@ public class DeviceApiController implements TbTransportService {
}
@ExceptionHandler(MaxPayloadSizeExceededException.class)
public void handle(MaxPayloadSizeExceededException exception, HttpServletRequest request, HttpServletResponse response) {
log.debug("Too large payload size. Url: {}, client ip: {}, content length: {}", request.getRequestURL(),
request.getRemoteAddr(), request.getContentLength());
if (!response.isCommitted()) {
try {
response.setStatus(HttpStatus.PAYLOAD_TOO_LARGE.value());
JacksonUtil.writeValue(response.getWriter(), exception.getMessage());
} catch (IOException e) {
log.error("Can't handle exception", e);
}
}
}
private static MediaType parseMediaType(String contentType) {
try {
return MediaType.parseMediaType(contentType);

3
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -155,6 +155,9 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
resourceValidator.validateDelete(tenantId, resourceId);
}
TbResource resource = findResourceById(tenantId, resourceId);
if (resource == null) {
return;
}
resourceDao.removeById(tenantId, resourceId.getId());
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entity(resource).entityId(resourceId).build());
}

22
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -433,19 +433,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request.");
RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId());
if (ruleChain == null) {
return;
}
List<RuleNode> referencingRuleNodes = getReferencingRuleChainNodes(tenantId, ruleChainId);
Set<RuleChainId> referencingRuleChainIds = referencingRuleNodes.stream().map(RuleNode::getRuleChainId).collect(Collectors.toSet());
if (ruleChain != null) {
if (ruleChain.isRoot()) {
throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!");
}
if (RuleChainType.EDGE.equals(ruleChain.getType())) {
for (Edge edge : new PageDataIterable<>(link -> edgeService.findEdgesByTenantIdAndEntityId(tenantId, ruleChainId, link), DEFAULT_PAGE_SIZE)) {
if (edge.getRootRuleChainId() != null && edge.getRootRuleChainId().equals(ruleChainId)) {
throw new DataValidationException("Can't delete rule chain that is root for edge [" + edge.getName() + "]. Please assign another root rule chain first to the edge!");
}
if (ruleChain.isRoot()) {
throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!");
}
if (RuleChainType.EDGE.equals(ruleChain.getType())) {
for (Edge edge : new PageDataIterable<>(link -> edgeService.findEdgesByTenantIdAndEntityId(tenantId, ruleChainId, link), DEFAULT_PAGE_SIZE)) {
if (edge.getRootRuleChainId() != null && edge.getRootRuleChainId().equals(ruleChainId)) {
throw new DataValidationException("Can't delete rule chain that is root for edge [" + edge.getName() + "]. Please assign another root rule chain first to the edge!");
}
}
}
@ -457,6 +458,9 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
if (force) {
RuleChain ruleChain = findRuleChainById(tenantId, (RuleChainId) id);
if (ruleChain == null) {
return;
}
checkRuleNodesAndDelete(tenantId, ruleChain, null);
} else {
deleteRuleChainById(tenantId, (RuleChainId) id);

2
pom.xml

@ -105,7 +105,7 @@
org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
</sonar.exclusions>
<elasticsearch.version>8.13.2</elasticsearch.version>
<delight-nashorn-sandbox.version>0.4.2</delight-nashorn-sandbox.version>
<delight-nashorn-sandbox.version>0.4.5</delight-nashorn-sandbox.version>
<nashorn-core.version>15.4</nashorn-core.version>
<!-- IMPORTANT: If you change the version of the kafka client, make sure to synchronize our overwritten implementation of the
org.apache.kafka.common.network.NetworkReceive class in the application module. It addresses the issue https://issues.apache.org/jira/browse/KAFKA-4090.

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -118,8 +118,8 @@ public class TbHttpClient {
o.username(proxyUser).password(u -> proxyPassword);
}
});
SslContext sslContext = SslContextBuilder.forClient().build();
httpClient.secure(t -> t.sslContext(sslContext));
SslContext sslContext = config.getCredentials().initSslContext();
httpClient = httpClient.secure(t -> t.sslContext(sslContext));
}
} else if (!config.isUseSimpleClientHttpFactory()) {
if (CredentialsType.CERT_PEM == config.getCredentials().getType()) {

2
transport/http/src/main/resources/tb-http-transport.yml

@ -170,6 +170,8 @@ transport:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
# HTTP maximum request processing timeout in milliseconds
max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
# Maximum request size
max_payload_size: "${HTTP_MAX_PAYLOAD_SIZE:65536}" # max payload size in bytes
sessions:
# Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
# The parameter value is in milliseconds.

Loading…
Cancel
Save