diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java index cc51a7063c..91eae788c3 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java @@ -1467,6 +1467,26 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest { assertEquals(expected, actual); } + // hexToBytes List or Array + @Test + public void hexToBytes_Test() throws ExecutionException, InterruptedException { + msgStr = "{}"; + decoderStr = """ + var validInputList = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF33"; + var validInputArray = "AABBCCDDEE"; + return { + "hexToBytes": hexToBytes(validInputList), + "hexToBytesArray": hexToBytesArray(validInputArray), + } + """; + Object actual = invokeScript(evalScript(decoderStr), msgStr); + LinkedHashMap expected = new LinkedHashMap<>(); + expected.put("hexToBytes", bytesToList(new byte[]{1, 117, 43, 3, 103, -6, 0, 5, 0, 1, 4, -120, -1, -1, -1, -1, -1, -1, -1, -1, 51})); + // [-86, -69, -52, -35, -18] == new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE} + expected.put("hexToBytesArray", bytesToList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE})); + assertEquals( expected, actual); + } + // parseBinaryArray @Test public void parseBinaryArray_Test() throws ExecutionException, InterruptedException { @@ -1759,13 +1779,15 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest { return { "base64ToHex": base64ToHex("Kkk="), "bytesToBase64": bytesToBase64([42, 73]), - "base64ToBytes": base64ToBytes("Kkk=") + "base64ToBytes": base64ToBytes("Kkk="), + "base64ToBytesList": base64ToBytesList("AQIDBAU=") } """; LinkedHashMap expected = new LinkedHashMap<>(); expected.put("base64ToHex", "2A49"); expected.put("bytesToBase64", "Kkk="); expected.put("base64ToBytes", bytesToList(new byte[]{42, 73})); + expected.put("base64ToBytesList", bytesToList(new byte[]{1, 2, 3, 4, 5})); Object actual = invokeScript(evalScript(decoderStr), msgStr); assertEquals(expected, actual); } 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 6b321a3570..fae91d14ca 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 @@ -257,6 +257,8 @@ public class TbUtils { float.class, int.class))); parserConfig.addImport("hexToBytes", new MethodStub(TbUtils.class.getMethod("hexToBytes", ExecutionContext.class, String.class))); + parserConfig.addImport("hexToBytesArray", new MethodStub(TbUtils.class.getMethod("hexToBytesArray", + String.class))); parserConfig.addImport("intToHex", new MethodStub(TbUtils.class.getMethod("intToHex", Integer.class))); parserConfig.addImport("intToHex", new MethodStub(TbUtils.class.getMethod("intToHex", @@ -297,6 +299,8 @@ public class TbUtils { String.class))); parserConfig.addImport("base64ToBytes", new MethodStub(TbUtils.class.getMethod("base64ToBytes", String.class))); + parserConfig.addImport("base64ToBytesList", new MethodStub(TbUtils.class.getMethod("base64ToBytesList", + ExecutionContext.class, String.class))); parserConfig.addImport("bytesToBase64", new MethodStub(TbUtils.class.getMethod("bytesToBase64", byte[].class))); parserConfig.addImport("bytesToHex", new MethodStub(TbUtils.class.getMethod("bytesToHex", @@ -335,7 +339,7 @@ public class TbUtils { byte.class))); parserConfig.addImport("parseByteToBinaryArray", new MethodStub(TbUtils.class.getMethod("parseByteToBinaryArray", byte.class, int.class))); - parserConfig.addImport("parseByteToBinaryArray", new MethodStub(TbUtils.class.getMethod("parseByteToBinaryArray", + parserConfig.addImport("parseByteToBinaryArray", new MethodStub(TbUtils.class.getMethod("parseByteToBinaryArray", byte.class, int.class, boolean.class))); parserConfig.addImport("parseBytesToBinaryArray", new MethodStub(TbUtils.class.getMethod("parseBytesToBinaryArray", List.class))); @@ -664,23 +668,16 @@ public class TbUtils { } public static ExecutionArrayList hexToBytes(ExecutionContext ctx, String value) { - String hex = prepareNumberString(value, true); - if (hex == null) { - throw new IllegalArgumentException("Hex string must be not empty!"); - } - int len = hex.length(); - if (len % 2 > 0) { - throw new IllegalArgumentException("Hex string must be even-length."); - } - int radix = isHexadecimal(value); - if (radix != HEX_RADIX) { - throw new NumberFormatException("Value: \"" + value + "\" is not numeric or hexDecimal format!"); - } - - byte [] data = hexToBytes(hex); + String hex = validateAndPrepareHex(value); + byte[] data = hexToBytes(hex); return bytesToExecutionArrayList(ctx, data); } + public static byte[] hexToBytesArray(String value) { + String hex = validateAndPrepareHex(value); + return hexToBytes(hex); + } + public static List printUnsignedBytes(ExecutionContext ctx, List byteArray) { ExecutionArrayList data = new ExecutionArrayList<>(ctx); for (Byte b : byteArray) { @@ -839,6 +836,11 @@ public class TbUtils { return Base64.getDecoder().decode(input); } + public static ExecutionArrayList base64ToBytesList(ExecutionContext ctx, String input) { + byte[] bytes = Base64.getDecoder().decode(input); + return bytesToExecutionArrayList(ctx, bytes); + } + public static int parseBytesToInt(List data) { return parseBytesToInt(data, 0); } @@ -879,6 +881,48 @@ public class TbUtils { return bb.getInt(); } + public static long parseBytesToUnsignedInt(byte[] data) { + return parseBytesToUnsignedInt(data, 0); + } + + public static long parseBytesToUnsignedInt(byte[] data, int offset) { + return parseBytesToUnsignedInt(data, offset, validateLength(data.length, offset, BYTES_LEN_INT_MAX)); + } + + public static long parseBytesToUnsignedInt(byte[] data, int offset, int length) { + return parseBytesToUnsignedInt(data, offset, length, true); + } + + public static long parseBytesToUnsignedInt(byte[] data, int offset, int length, boolean bigEndian) { + validationNumberByLength(data, offset, length, BYTES_LEN_INT_MAX); + + ByteBuffer bb = ByteBuffer.allocate(8); + if (!bigEndian) { + bb.order(ByteOrder.LITTLE_ENDIAN); + } + bb.position(bigEndian ? 8 - length : 0); + bb.put(data, offset, length); + bb.position(0); + + return bb.getLong(); + } + + public static long parseBytesToUnsignedInt(List data) { + return parseBytesToUnsignedInt(data, 0); + } + + public static long parseBytesToUnsignedInt(List data, int offset) { + return parseBytesToUnsignedInt(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX)); + } + + public static long parseBytesToUnsignedInt(List data, int offset, int length) { + return parseBytesToUnsignedInt(data, offset, length, true); + } + + public static long parseBytesToUnsignedInt(List data, int offset, int length, boolean bigEndian) { + return parseBytesToUnsignedInt(Bytes.toArray(data), offset, length, bigEndian); + } + public static long parseBytesToLong(List data) { return parseBytesToLong(data, 0); } @@ -1304,7 +1348,7 @@ public class TbUtils { public static byte[] parseByteToBinaryArray(byte byteValue, int binLength, boolean bigEndian) { byte[] bins = new byte[binLength]; for (int i = 0; i < binLength; i++) { - if(bigEndian) { + if (bigEndian) { bins[binLength - 1 - i] = (byte) ((byteValue >> i) & 1); } else { bins[i] = (byte) ((byteValue >> i) & 1); @@ -1435,16 +1479,32 @@ public class TbUtils { } private static byte[] hexToBytes(String hex) { - byte [] data = new byte[hex.length()/2]; + byte[] data = new byte[hex.length() / 2]; for (int i = 0; i < hex.length(); i += 2) { // Extract two characters from the hex string String byteString = hex.substring(i, i + 2); // Parse the hex string to a byte byte byteValue = (byte) Integer.parseInt(byteString, HEX_RADIX); // Add the byte to the ArrayList - data[i/2] = byteValue; + data[i / 2] = byteValue; } return data; } + + private static String validateAndPrepareHex(String value) { + String hex = prepareNumberString(value, true); + if (hex == null) { + throw new IllegalArgumentException("Hex string must be not empty!"); + } + int len = hex.length(); + if (len % 2 > 0) { + throw new IllegalArgumentException("Hex string must be even-length."); + } + int radix = isHexadecimal(value); + if (radix != HEX_RADIX) { + throw new NumberFormatException("Value: \"" + value + "\" is not numeric or hexDecimal format!"); + } + return hex; + } } 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 1c5a1a2e1a..21ee8538a0 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 @@ -19,7 +19,6 @@ import com.google.common.collect.Lists; import com.google.common.primitives.Bytes; import com.google.common.primitives.Ints; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.units.qual.A; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -36,6 +35,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Calendar; import java.util.Collections; import java.util.List; @@ -787,8 +787,12 @@ public class TbUtilsTest { public void hexToBytes_Test() { String input = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF33"; byte[] expected = {1, 117, 43, 3, 103, -6, 0, 5, 0, 1, 4, -120, -1, -1, -1, -1, -1, -1, -1, -1, 51}; - List actual = TbUtils.hexToBytes(ctx, input); - Assertions.assertEquals(toList(expected), actual); + List actualList = TbUtils.hexToBytes(ctx, input); + Assertions.assertEquals(toList(expected), actualList); + String validInput = "AABBCCDDEE"; + expected = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE}; + byte[] actualBytes = TbUtils.hexToBytesArray(validInput); + Assertions.assertArrayEquals(expected, actualBytes); try { input = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF3"; TbUtils.hexToBytes(ctx, input); @@ -807,6 +811,12 @@ public class TbUtilsTest { } catch (IllegalArgumentException e) { Assertions.assertTrue(e.getMessage().contains("Hex string must be not empty")); } + try { + input = null; + TbUtils.hexToBytes(ctx, input); + } catch (IllegalArgumentException e) { + Assertions.assertTrue(e.getMessage().contains("Hex string must be not empty")); + } } @Test @@ -1086,6 +1096,26 @@ public class TbUtilsTest { String actual = TbUtils.hexToBase64(hex); Assertions.assertEquals(expected, actual); } + + @Test + void base64ToBytesList_Test() { + String validInput = Base64.getEncoder().encodeToString(new byte[]{1, 2, 3, 4, 5}); + ExecutionArrayList actual = TbUtils.base64ToBytesList(ctx, validInput); + ExecutionArrayList expected = new ExecutionArrayList<>(ctx); + expected.addAll(List.of((byte) 1, (byte)2, (byte)3, (byte)4, (byte)5)); + Assertions.assertEquals(expected, actual); + + String emptyInput = Base64.getEncoder().encodeToString(new byte[]{}); + actual = TbUtils.base64ToBytesList(ctx, emptyInput); + Assertions.assertTrue(actual.isEmpty()); + String invalidInput = "NotAValidBase64String"; + Assertions.assertThrows(IllegalArgumentException.class, () -> { + TbUtils.base64ToBytesList(ctx, invalidInput); + }); + Assertions.assertThrows(NullPointerException.class, () -> { + TbUtils.base64ToBytesList(ctx, null); + }); + } @Test public void bytesToHex_Test() { byte[] bb = {(byte) 0xBB, (byte) 0xAA};