org.fisco.bcos.sdk.utils.ByteUtils Maven / Gradle / Ivy
/**
* Copyright 2014-2020 [fisco-dev]
*
* 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.fisco.bcos.sdk.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class ByteUtils {
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static final byte[] ZERO_BYTE_ARRAY = new byte[] {0};
/**
* Creates a copy of bytes and appends b to the end of it
*
* @param bytes the original bytes
* @param b the appended byte
* @return a appended bytes @
*/
public static byte[] appendByte(byte[] bytes, byte b) {
byte[] result = Arrays.copyOf(bytes, bytes.length + 1);
result[result.length - 1] = b;
return result;
}
/**
* The regular {@link BigInteger#toByteArray()} method isn't quite what we often need: it
* appends a leading zero to indicate that the number is positive and may need padding.
*
* @param b the integer to format into a byte array
* @param numBytes the desired size of the resulting byte array
* @return numBytes byte long array.
*/
public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) {
if (b == null) {
return null;
}
byte[] bytes = new byte[numBytes];
byte[] biBytes = b.toByteArray();
int start = (biBytes.length == numBytes + 1) ? 1 : 0;
int length = Math.min(biBytes.length, numBytes);
System.arraycopy(biBytes, start, bytes, numBytes - length, length);
return bytes;
}
public static byte[] bigIntegerToBytes(BigInteger value) {
if (value == null) {
return null;
}
byte[] data = value.toByteArray();
if (data.length != 1 && data[0] == 0) {
byte[] tmp = new byte[data.length - 1];
System.arraycopy(data, 1, tmp, 0, tmp.length);
data = tmp;
}
return data;
}
public static byte[] bigIntegerToBytesSigned(BigInteger b, int numBytes) {
if (b == null) {
return null;
}
byte[] bytes = new byte[numBytes];
Arrays.fill(bytes, b.signum() < 0 ? (byte) 0xFF : 0x00);
byte[] biBytes = b.toByteArray();
int start = (biBytes.length == numBytes + 1) ? 1 : 0;
int length = Math.min(biBytes.length, numBytes);
System.arraycopy(biBytes, start, bytes, numBytes - length, length);
return bytes;
}
/**
* Cast hex encoded value from byte[] to BigInteger null is parsed like byte[0]
*
* @param bb byte array contains the values
* @return unsigned positive BigInteger value.
*/
public static BigInteger bytesToBigInteger(byte[] bb) {
return (bb == null || bb.length == 0) ? BigInteger.ZERO : new BigInteger(1, bb);
}
/**
* Returns the amount of nibbles that match each other from 0 ... amount will never be larger
* than smallest input
*
* @param a - first input
* @param b - second input
* @return Number of bytes that match
*/
public static int matchingNibbleLength(byte[] a, byte[] b) {
int i = 0;
int length = a.length < b.length ? a.length : b.length;
while (i < length) {
if (a[i] != b[i]) {
return i;
}
i++;
}
return i;
}
/**
* Converts a long value into a byte array.
*
* @param val - long value to convert
* @return byte[]
of length 8, representing the long value
*/
public static byte[] longToBytes(long val) {
return ByteBuffer.allocate(Long.BYTES).putLong(val).array();
}
/**
* Converts a long value into a byte array.
*
* @param val - long value to convert
* @return decimal value with leading byte that are zeroes striped
*/
public static byte[] longToBytesNoLeadZeroes(long val) {
// todo: improve performance by while strip numbers until (long >> 8 == 0)
if (val == 0) {
return EMPTY_BYTE_ARRAY;
}
byte[] data = ByteBuffer.allocate(Long.BYTES).putLong(val).array();
return stripLeadingZeroes(data);
}
/**
* Converts int value into a byte array.
*
* @param val - int value to convert
* @return byte[]
of length 4, representing the int value
*/
public static byte[] intToBytes(int val) {
return ByteBuffer.allocate(Integer.BYTES).putInt(val).array();
}
/**
* Converts a int value into a byte array.
*
* @param val - int value to convert
* @return value with leading byte that are zeroes striped
*/
public static byte[] intToBytesNoLeadZeroes(int val) {
if (val == 0) {
return EMPTY_BYTE_ARRAY;
}
int length = 0;
int tmpVal = val;
while (tmpVal != 0) {
tmpVal = tmpVal >>> 8;
++length;
}
byte[] result = new byte[length];
int index = result.length - 1;
while (val != 0) {
result[index] = (byte) (val & 0xFF);
val = val >>> 8;
index -= 1;
}
return result;
}
/**
* Calculate packet length
*
* @param msg byte[]
* @return byte-array with 4 elements
*/
public static byte[] calcPacketLength(byte[] msg) {
int msgLen = msg.length;
return new byte[] {
(byte) ((msgLen >> 24) & 0xFF),
(byte) ((msgLen >> 16) & 0xFF),
(byte) ((msgLen >> 8) & 0xFF),
(byte) ((msgLen) & 0xFF)
};
}
/**
* Cast hex encoded value from byte[] to int null is parsed like byte[0]
*
*
Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes)
*
* @param b array contains the values
* @return unsigned positive int value.
*/
public static int byteArrayToInt(byte[] b) {
if (b == null || b.length == 0) {
return 0;
}
return new BigInteger(1, b).intValue();
}
/**
* Cast hex encoded value from byte[] to long null is parsed like byte[0]
*
*
Limited to Long.MAX_VALUE: 263-1 (8 bytes)
*
* @param b array contains the values
* @return unsigned positive long value.
*/
public static long byteArrayToLong(byte[] b) {
if (b == null || b.length == 0) {
return 0;
}
return new BigInteger(1, b).longValue();
}
/**
* Turn nibbles to a pretty looking output string
*
*
Example. [ 1, 2, 3, 4, 5 ] becomes '\x11\x23\x45'
*
* @param nibbles - getting byte of data [ 04 ] and turning it to a '\x04' representation
* @return pretty string of nibbles
*/
public static String nibblesToPrettyString(byte[] nibbles) {
StringBuilder builder = new StringBuilder();
for (byte nibble : nibbles) {
final String nibbleString = oneByteToHexString(nibble);
builder.append("\\x").append(nibbleString);
}
return builder.toString();
}
public static String oneByteToHexString(byte value) {
String retVal = Integer.toString(value & 0xFF, 16);
if (retVal.length() == 1) {
retVal = "0" + retVal;
}
return retVal;
}
/**
* Calculate the number of bytes need to encode the number
*
* @param val - number
* @return number of min bytes used to encode the number
*/
public static int numBytes(String val) {
BigInteger bInt = new BigInteger(val);
int bytes = 0;
while (!bInt.equals(BigInteger.ZERO)) {
bInt = bInt.shiftRight(8);
++bytes;
}
if (bytes == 0) {
++bytes;
}
return bytes;
}
/**
* @param arg - not more that 32 bits
* @return - bytes of the value pad with complete to 32 zeroes
*/
private static byte[] encodeValFor32Bits(Object arg) {
byte[] data;
// check if the string is numeric
if (arg.toString().trim().matches("-?\\d+(\\.\\d+)?")) {
data = new BigInteger(arg.toString().trim()).toByteArray();
}
// check if it's hex number
else if (arg.toString().trim().matches("0[xX][0-9a-fA-F]+")) {
data = new BigInteger(arg.toString().trim().substring(2), 16).toByteArray();
} else {
data = arg.toString().trim().getBytes();
}
if (data.length > 32) {
throw new RuntimeException("values can't be more than 32 byte");
}
byte[] val = new byte[32];
int j = 0;
for (int i = data.length; i > 0; --i) {
val[31 - j] = data[i - 1];
++j;
}
return val;
}
/**
* encode the values and concatenate together
*
* @param args Object
* @return byte[]
*/
public static byte[] encodeDataList(Object... args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (Object arg : args) {
byte[] val = encodeValFor32Bits(arg);
try {
baos.write(val);
} catch (IOException e) {
throw new Error("Happen something that should never happen ", e);
}
}
return baos.toByteArray();
}
public static int firstNonZeroByte(byte[] data) {
for (int i = 0; i < data.length; ++i) {
if (data[i] != 0) {
return i;
}
}
return -1;
}
public static byte[] stripLeadingZeroes(byte[] data) {
if (data == null) {
return null;
}
final int firstNonZero = firstNonZeroByte(data);
switch (firstNonZero) {
case -1:
return ZERO_BYTE_ARRAY;
case 0:
return data;
default:
byte[] result = new byte[data.length - firstNonZero];
System.arraycopy(data, firstNonZero, result, 0, data.length - firstNonZero);
return result;
}
}
/**
* increment byte array as a number until max is reached
*
* @param bytes byte[]
* @return boolean
*/
public static boolean increment(byte[] bytes) {
final int startIndex = 0;
int i;
for (i = bytes.length - 1; i >= startIndex; i--) {
bytes[i]++;
if (bytes[i] != 0) {
break;
}
}
// we return false when all bytes are 0 again
return (i >= startIndex || bytes[startIndex] != 0);
}
/**
* Utility function to copy a byte array into a new byte array with given size. If the src
* length is smaller than the given size, the result will be left-padded with zeros.
*
* @param value - a BigInteger with a maximum value of 2^256-1
* @return Byte array of given size with a copy of the src
*/
public static byte[] copyToArray(BigInteger value) {
byte[] src = ByteUtils.bigIntegerToBytes(value);
byte[] dest = ByteBuffer.allocate(32).array();
if (src != null) {
System.arraycopy(src, 0, dest, dest.length - src.length, src.length);
}
return dest;
}
// public static ByteArrayWrapper wrap(byte[] data) {
// return new ByteArrayWrapper(data);
// }
public static byte[] setBit(byte[] data, int pos, int val) {
if ((data.length * 8) - 1 < pos) {
throw new Error("outside byte array limit, pos: " + pos);
}
int posByte = data.length - 1 - (pos) / 8;
int posBit = (pos) % 8;
byte setter = (byte) (1 << (posBit));
byte toBeSet = data[posByte];
byte result;
if (val == 1) {
result = (byte) (toBeSet | setter);
} else {
result = (byte) (toBeSet & ~setter);
}
data[posByte] = result;
return data;
}
public static int getBit(byte[] data, int pos) {
if ((data.length * 8) - 1 < pos) {
throw new Error("outside byte array limit, pos: " + pos);
}
int posByte = data.length - 1 - pos / 8;
int posBit = pos % 8;
byte dataByte = data[posByte];
return Math.min(1, ((dataByte & 0xff) & (1 << (posBit))));
}
public static byte[] and(byte[] b1, byte[] b2) {
if (b1.length != b2.length) {
throw new RuntimeException("Array sizes differ");
}
byte[] ret = new byte[b1.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (byte) (b1[i] & b2[i]);
}
return ret;
}
public static byte[] or(byte[] b1, byte[] b2) {
if (b1.length != b2.length) {
throw new RuntimeException("Array sizes differ");
}
byte[] ret = new byte[b1.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (byte) (b1[i] | b2[i]);
}
return ret;
}
public static byte[] xor(byte[] b1, byte[] b2) {
if (b1.length != b2.length) {
throw new RuntimeException("Array sizes differ");
}
byte[] ret = new byte[b1.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (byte) (b1[i] ^ b2[i]);
}
return ret;
}
/**
* XORs byte arrays of different lengths by aligning length of the shortest via adding zeros at
* beginning
*
* @param b1 the first byte array
* @param b2 the second byte array
* @return a byte array contains XORS of b1 and b2
*/
public static byte[] xorAlignRight(byte[] b1, byte[] b2) {
if (b1.length > b2.length) {
byte[] b2_ = new byte[b1.length];
System.arraycopy(b2, 0, b2_, b1.length - b2.length, b2.length);
b2 = b2_;
} else if (b2.length > b1.length) {
byte[] b1_ = new byte[b2.length];
System.arraycopy(b1, 0, b1_, b2.length - b1.length, b1.length);
b1 = b1_;
}
return xor(b1, b2);
}
/**
* @param arrays - arrays to merge
* @return - merged array
*/
public static byte[] merge(byte[]... arrays) {
int count = 0;
for (byte[] array : arrays) {
count += array.length;
}
// Create new array and copy all array contents
byte[] mergedArray = new byte[count];
int start = 0;
for (byte[] array : arrays) {
System.arraycopy(array, 0, mergedArray, start, array.length);
start += array.length;
}
return mergedArray;
}
public static boolean isNullOrZeroArray(byte[] array) {
return (array == null) || (array.length == 0);
}
public static boolean isSingleZero(byte[] array) {
return (array.length == 1 && array[0] == 0);
}
public static Set difference(Set setA, Set setB) {
Set result = new HashSet<>();
for (byte[] elementA : setA) {
boolean found = false;
for (byte[] elementB : setB) {
if (Arrays.equals(elementA, elementB)) {
found = true;
break;
}
}
if (!found) {
result.add(elementA);
}
}
return result;
}
public static int length(byte[]... bytes) {
int result = 0;
for (byte[] array : bytes) {
result += (array == null) ? 0 : array.length;
}
return result;
}
public static int[] bytesToInts(byte[] arr, boolean bigEndian) {
int[] ret = new int[arr.length / 4];
bytesToInts(arr, ret, bigEndian);
return ret;
}
public static void bytesToInts(byte[] b, int[] arr, boolean bigEndian) {
if (!bigEndian) {
int off = 0;
for (int i = 0; i < arr.length; i++) {
int ii = b[off++] & 0x000000FF;
ii |= (b[off++] << 8) & 0x0000FF00;
ii |= (b[off++] << 16) & 0x00FF0000;
ii |= (b[off++] << 24);
arr[i] = ii;
}
} else {
int off = 0;
for (int i = 0; i < arr.length; i++) {
int ii = b[off++] << 24;
ii |= (b[off++] << 16) & 0x00FF0000;
ii |= (b[off++] << 8) & 0x0000FF00;
ii |= b[off++] & 0x000000FF;
arr[i] = ii;
}
}
}
public static byte[] intsToBytes(int[] arr, boolean bigEndian) {
byte[] ret = new byte[arr.length * 4];
intsToBytes(arr, ret, bigEndian);
return ret;
}
public static void intsToBytes(int[] arr, byte[] b, boolean bigEndian) {
if (!bigEndian) {
int off = 0;
for (int i = 0; i < arr.length; i++) {
int ii = arr[i];
b[off++] = (byte) (ii & 0xFF);
b[off++] = (byte) ((ii >> 8) & 0xFF);
b[off++] = (byte) ((ii >> 16) & 0xFF);
b[off++] = (byte) ((ii >> 24) & 0xFF);
}
} else {
int off = 0;
for (int i = 0; i < arr.length; i++) {
int ii = arr[i];
b[off++] = (byte) ((ii >> 24) & 0xFF);
b[off++] = (byte) ((ii >> 16) & 0xFF);
b[off++] = (byte) ((ii >> 8) & 0xFF);
b[off++] = (byte) (ii & 0xFF);
}
}
}
public static short bigEndianToShort(byte[] bs) {
return bigEndianToShort(bs, 0);
}
public static short bigEndianToShort(byte[] bs, int off) {
int n = bs[off] << 8;
++off;
n |= bs[off] & 0xFF;
return (short) n;
}
public static byte[] shortToBytes(short n) {
return ByteBuffer.allocate(2).putShort(n).array();
}
/**
* Converts string hex representation to data bytes Accepts following hex: - with or without 0x
* prefix
*
* @param data String like '0xa5e..' or just 'a5e..'
* @return decoded bytes array
*/
public static byte[] hexStringToBytes(String data) {
if (data == null) {
return EMPTY_BYTE_ARRAY;
}
if (data.startsWith("0x")) {
data = data.substring(2);
}
if (data.length() % 2 == 1) {
data = "0" + data;
}
return Hex.decode(data);
}
/**
* Converts string representation of host/ip to 4-bytes byte[] IPv4
*
* @param ip the ip string of host
* @return a 4-bytes byte[] IPv4
*/
public static byte[] hostToBytes(String ip) {
byte[] bytesIp;
try {
bytesIp = InetAddress.getByName(ip).getAddress();
} catch (UnknownHostException e) {
bytesIp = new byte[4]; // fall back to invalid 0.0.0.0 address
}
return bytesIp;
}
/**
* Converts 4 bytes IPv4 IP to String representation
*
* @param bytesIp the 4 bytes IPv4 IP
* @return a String representation of the IP
*/
public static String bytesToIp(byte[] bytesIp) {
StringBuilder sb = new StringBuilder();
sb.append(bytesIp[0] & 0xFF);
sb.append(".");
sb.append(bytesIp[1] & 0xFF);
sb.append(".");
sb.append(bytesIp[2] & 0xFF);
sb.append(".");
sb.append(bytesIp[3] & 0xFF);
String ip = sb.toString();
return ip;
}
/**
* Returns a number of zero bits preceding the highest-order ("leftmost") one-bit interpreting
* input array as a big-endian integer value
*
* @param bytes the byte array
* @return the number of leading zeros
*/
public static int numberOfLeadingZeros(byte[] bytes) {
int i = firstNonZeroByte(bytes);
if (i == -1) {
return bytes.length * 8;
} else {
int byteLeadingZeros = Integer.numberOfLeadingZeros((int) bytes[i] & 0xff) - 24;
return i * 8 + byteLeadingZeros;
}
}
/**
* Parses fixed number of bytes starting from {@code offset} in {@code input} array. If {@code
* input} has not enough bytes return array will be right padded with zero bytes. I.e. if {@code
* offset} is higher than {@code input.length} then zero byte array of length {@code len} will
* be returned
*
* @param input the input bytes array
* @param offset an offset in {@code input} array to start parsing from
* @param len the length of zero byte array
* @return a fixed bytes array
*/
public static byte[] parseBytes(byte[] input, int offset, int len) {
if (offset >= input.length || len == 0) {
return EMPTY_BYTE_ARRAY;
}
byte[] bytes = new byte[len];
System.arraycopy(input, offset, bytes, 0, Math.min(input.length - offset, len));
return bytes;
}
/**
* Parses 32-bytes word from given input. Uses {@link #parseBytes(byte[], int, int)} method,
* thus, result will be right-padded with zero bytes if there is not enough bytes in {@code
* input}
*
* @param input the input bytes array
* @param idx an index of the word starting from {@code 0}
* @return a fixed bytes array
*/
public static byte[] parseWord(byte[] input, int idx) {
return parseBytes(input, 32 * idx, 32);
}
/**
* Parses 32-bytes word from given input. Uses {@link #parseBytes(byte[], int, int)} method,
* thus, result will be right-padded with zero bytes if there is not enough bytes in {@code
* input}
*
* @param input the input bytes array
* @param idx an index of the word starting from {@code 0}
* @param offset an offset in {@code input} array to start parsing from
* @return a fixed bytes array
*/
public static byte[] parseWord(byte[] input, int offset, int idx) {
return parseBytes(input, offset + 32 * idx, 32);
}
public static byte[] trimLeadingBytes(byte[] bytes, byte b) {
int offset = 0;
for (; offset < bytes.length - 1; offset++) {
if (bytes[offset] != b) {
break;
}
}
return Arrays.copyOfRange(bytes, offset, bytes.length);
}
public static byte[] trimLeadingZeroes(byte[] bytes) {
return trimLeadingBytes(bytes, (byte) 0);
}
}