Merge pull request #12405 from thingsboard/tbel_new_bytes_parsing_methods

tbel: add hexToBytesArray, base64ToBytesList
This commit is contained in:
Andrew Shvayka 2025-01-10 16:48:17 +02:00 committed by GitHub
commit 801c06aea0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 134 additions and 22 deletions

View File

@ -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<String, Object> 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<String, Object> 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);
}

View File

@ -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",
@ -664,21 +668,14 @@ public class TbUtils {
}
public static ExecutionArrayList<Byte> 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!");
String hex = validateAndPrepareHex(value);
byte[] data = hexToBytes(hex);
return bytesToExecutionArrayList(ctx, data);
}
byte [] data = hexToBytes(hex);
return bytesToExecutionArrayList(ctx, data);
public static byte[] hexToBytesArray(String value) {
String hex = validateAndPrepareHex(value);
return hexToBytes(hex);
}
public static List<Integer> printUnsignedBytes(ExecutionContext ctx, List<Byte> byteArray) {
@ -839,6 +836,11 @@ public class TbUtils {
return Base64.getDecoder().decode(input);
}
public static ExecutionArrayList<Byte> base64ToBytesList(ExecutionContext ctx, String input) {
byte[] bytes = Base64.getDecoder().decode(input);
return bytesToExecutionArrayList(ctx, bytes);
}
public static int parseBytesToInt(List<Byte> 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<Byte> data) {
return parseBytesToUnsignedInt(data, 0);
}
public static long parseBytesToUnsignedInt(List<Byte> data, int offset) {
return parseBytesToUnsignedInt(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX));
}
public static long parseBytesToUnsignedInt(List<Byte> data, int offset, int length) {
return parseBytesToUnsignedInt(data, offset, length, true);
}
public static long parseBytesToUnsignedInt(List<Byte> data, int offset, int length, boolean bigEndian) {
return parseBytesToUnsignedInt(Bytes.toArray(data), offset, length, bigEndian);
}
public static long parseBytesToLong(List<Byte> 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;
}
}

View File

@ -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<Byte> actual = TbUtils.hexToBytes(ctx, input);
Assertions.assertEquals(toList(expected), actual);
List<Byte> 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<Byte> actual = TbUtils.base64ToBytesList(ctx, validInput);
ExecutionArrayList<Byte> 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};