From b6cfc32b6be6c29dfc504bed3c264d0e8a3d3fcc Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 16 Jul 2024 11:22:54 +0300 Subject: [PATCH 1/8] TbHttpClient - fixed proxy ssl configuration --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 26de3ad5e5..b6a7234cac 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/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()) { From ed8a20b89d86361137f8288933d7e39cbe1db7c2 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 16 Jul 2024 11:23:23 +0300 Subject: [PATCH 2/8] EdgeEventSourcing - ignore EntityAlarm save events. Alarms pushed to edge using push_to_edge node --- .../server/service/edge/EdgeEventSourcingListener.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 11974d2ebc..3de4c95cc3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/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; From a2e4cb9a7d7b27706a546ec4e5e28d3830133de5 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 27 Jun 2024 19:06:38 +0300 Subject: [PATCH 3/8] Handle properly DELETE cases for ASSET/DEVICE to push ENTITY_DELETED msg into correct rule chain of the profile --- .../queue/DefaultTbClusterService.java | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index eabec663a3..7538e498eb 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/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) { + private 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())); } From b7c505efcd1911d953e4d0077d7c3245b98c6ee8 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 17 Jul 2024 11:59:20 +0300 Subject: [PATCH 4/8] DefaultTbClusterService.getRuleEngineProfileForEntityOrElseNull - Added unit tests --- .../queue/DefaultTbClusterService.java | 2 +- .../queue/DefaultTbClusterServiceTest.java | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 7538e498eb..49ec72d27a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -216,7 +216,7 @@ public class DefaultTbClusterService implements TbClusterService { toRuleEngineMsgs.incrementAndGet(); } - private HasRuleEngineProfile getRuleEngineProfileForEntityOrElseNull(TenantId tenantId, EntityId entityId, TbMsg tbMsg) { + HasRuleEngineProfile getRuleEngineProfileForEntityOrElseNull(TenantId tenantId, EntityId entityId, TbMsg tbMsg) { if (entityId.getEntityType().equals(EntityType.DEVICE)) { if (TbMsgType.ENTITY_DELETED.equals(tbMsg.getInternalType())) { try { diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java index 22f3620d8c..78cca2de62 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java +++ b/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); + } } \ No newline at end of file From 8d34a2003ec0a62c581ce4df855f65fbd5c3d7a7 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 19 Jul 2024 18:26:34 +0300 Subject: [PATCH 5/8] Fix entities deletion task reprocessing --- .../ruleChain/RuleEngineComponentActor.java | 3 +++ .../dao/resource/BaseResourceService.java | 3 +++ .../server/dao/rule/BaseRuleChainService.java | 22 +++++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java index bae9216446..f05fdb1fb9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java @@ -50,6 +50,9 @@ public abstract class RuleEngineComponentActor referencingRuleNodes = getReferencingRuleChainNodes(tenantId, ruleChainId); Set 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); From b48474861ed65f8bfde32c8751de6729fd07ecd5 Mon Sep 17 00:00:00 2001 From: Kulikov <44275303+nickAS21@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:20:30 +0300 Subject: [PATCH 6/8] TBEL: added parseBytesIntToFloat, parseBytesLongToDouble, byteArrayToExecutionArrayList (#11296) * tbel: added parseBytesIntLongToFloat * tbel: added to doubleToHex_Test() String valueHex = "0x000000000000000A"; * tbel: added byteArray to ExecutionArrayList; --- .../thingsboard/script/api/tbel/TbUtils.java | 250 ++++++++++++------ .../script/api/tbel/TbUtilsTest.java | 190 ++++++++++--- 2 files changed, 321 insertions(+), 119 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index d8aecb2f09..2884ba6214 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/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 data) { - return parseBytesToInt(Bytes.toArray(data)); + return parseBytesToInt(data, 0); } public static int parseBytesToInt(List 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 data, int offset, int length) { - return parseBytesToInt(Bytes.toArray(data), offset, length); + return parseBytesToInt(data, offset, length, true); } public static int parseBytesToInt(List 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 data) { - return parseBytesToLong(Bytes.toArray(data)); + return parseBytesToLong(data, 0); } public static long parseBytesToLong(List 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 data, int offset, int length) { - return parseBytesToLong(Bytes.toArray(data), offset, length); + return parseBytesToLong(data, offset, length, true); } public static long parseBytesToLong(List 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 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); + } } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index a7292d4280..c9ee9a977c 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/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 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 From f34361e1eac041bf4de5002e5101ed6a50697b2b Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 30 Jul 2024 12:02:01 +0300 Subject: [PATCH 7/8] Fix Nashorn sandbox script compile error (ScriptCPUAbuseException) --- .../script/NashornJsInvokeServiceTest.java | 47 +++++++++++++++++-- pom.xml | 2 +- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 28834a0ab7..33e7ccea23 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java +++ b/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> 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, "{}"); diff --git a/pom.xml b/pom.xml index f75e782bef..9b8943cff3 100755 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* 8.13.2 - 0.4.2 + 0.4.5 15.4