content: package org.aion.util.bytes;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.math.BigInteger;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.Set;
  import org.aion.util.conversions.Hex;
  
  public class ByteUtil {
  
      public static final byte[] EMPTY_WORD = new byte[32];
      public static final byte[] EMPTY_HALFWORD = new byte[16];
      public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
      public static final byte[] ZERO_BYTE_ARRAY = new byte[] {0};
      public static final String EMPTY_STRING = "";
  
      /** Creates a copy of bytes and appends b to the end of it */
      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[] 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;
      }
  
      /**
       * Omitting sign indication byte. <br>
       * <br>
       * Instead of {@code org.spongycastle.util.BigIntegers#asUnsignedByteArray(BigInteger)} <br>
       * we use this custom method to avoid an empty array in case of BigInteger.ZERO
       *
       * @param value - any big integer number. A <code>null</code>-value will return <code>null
       *     </code>
       * @return A byte array without a leading zero byte if present in the signed encoding.
       *     BigInteger.ZERO will return an array with length 1 and byte-value 0.
       */
      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 BigInteger bytesToBigInteger(byte[] bb) {
          return 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;
      }
  
      /** Perform an in-place conversion of a byte array from big endian to little endian. */
      public static void toLEByteArray(byte[] toConvert) {
  
          if (toConvert == null) {
              return;
          }
  
          for (int i = 0; i < (toConvert.length >>> 1); i++) {
              byte temp = toConvert[i];
              toConvert[i] = toConvert[toConvert.length - 1 - i];
              toConvert[toConvert.length - 1 - i] = temp;
          }
      }
  
      /**
       * Converts a long value into a byte array.
       *
       * @param val - long value to convert
       * @return <code>byte[]</code> of length 8, representing the long value
       */
      public static byte[] longToBytes(long val) {
          return ByteBuffer.allocate(8).putLong(val).array();
      }
  
      public static byte[] longToBytesLE(long val) {
          ByteBuffer bb = ByteBuffer.allocate(8);
          bb.order(ByteOrder.LITTLE_ENDIAN);
          return bb.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(8).putLong(val).array();
  
          return stripLeadingZeroes(data);
      }
  
      /**
       * Converts int value into a byte array.
       *
       * @param val - int value to convert
       * @return <code>byte[]</code> of length 4, representing the int value
       */
      public static byte[] intToBytes(int val) {
          return ByteBuffer.allocate(4).putInt(val).array();
      }
  
      /**
       * Converts and int value to a byte array (Little Endian) @ param val
       *
       * @return <code>byte[]</code> of length 4; representing the int value in LE order
       */
      public static byte[] intToBytesLE(int val) {
          ByteBuffer bb = ByteBuffer.allocate(4);
          bb.order(ByteOrder.LITTLE_ENDIAN);
          return bb.putInt(val).array();
      }
  
      /**
       * Converts and int value to a byte array (Big Endian) @ param val
       *
       * @return <code>byte[]</code> of length 4; representing the int value in LE order
       */
      public static byte[] intToBytesBE(int val) {
          ByteBuffer bb = ByteBuffer.allocate(4);
          bb.order(ByteOrder.BIG_ENDIAN);
          return bb.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;
          } else if (val < 0 || val > 16777215) {
              return new byte[] {
                  (byte) (val >>> 24), (byte) (val >>> 16), (byte) (val >>> 8), (byte) val
              };
          } else if (val < 256) {
              return new byte[] {(byte) val};
          } else if (val < 65536) {
              return new byte[] {(byte) (val >>> 8), (byte) val};
          } else {
              return new byte[] {(byte) (val >>> 16), (byte) (val >>> 8), (byte) val};
          }
      }
  
      /**
       * Convert a byte-array into a hex String.<br>
       * Works similar to {@link Hex#toHexString} but allows for <code>null</code>
       *
       * @param data - byte-array to convert to a hex-string
       * @return hex representation of the data.<br>
       *     Returns an empty String if the input is <code>null</code> TODO: swap out with more
       *     efficient implementation, for now seems like we are stuck with this
       * @see Hex#toHexString
       */
      public static String toHexString(byte[] data) {
          return data == null ? "" : Hex.toHexString(data);
      }
  
      public static String toHexStringWithPrefix(byte[] data) {
          return "0x" + toHexString(data);
      }
  
      /**
       * 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), (byte) (msgLen >> 16), (byte) (msgLen >> 8), (byte) (msgLen)
          };
      }
  
      /**
       * Cast hex encoded value from byte[] to int
       *
       * <p>Limited to Integer.MAX_VALUE: 2^31-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
       *
       * <p>Limited to Long.MAX_VALUE: 2^63-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();
      }
  
      /** Used in conjunction w/ RLP, casts to byte */
      public static byte toByte(byte[] b) {
          if (b == null || b.length == 0) {
              return 0;
          }
          return b[0];
      }
  
      /**
       * Turn nibbles to a pretty looking output string
       *
       * <p>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
       */
      public 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 <code>src</code>
       */
      public static byte[] copyToArray(BigInteger value) {
          byte[] src = ByteUtil.bigIntegerToBytes(value);
          byte[] dest = ByteBuffer.allocate(32).array();
          System.arraycopy(src, 0, dest, dest.length - src.length, src.length);
          return dest;
      }
  
      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 & (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
       */
      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<byte[]> difference(Set<byte[]> setA, Set<byte[]> setB) {
  
          Set<byte[]> 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 byte[] intsToBytes(int[] arr, boolean bigEndian) {
          byte[] ret = new byte[arr.length * 4];
          intsToBytes(arr, ret, bigEndian);
          return ret;
      }
  
      public static int[] bytesToInts(byte[] arr, boolean bigEndian) {
          int[] ret = new int[arr.length >>> 2];
          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 void intsToBytes(int[] arr, byte[] b, boolean bigEndian) {
          if (!bigEndian) {
              int off = 0;
              for (int ii : arr) {
                  b[off++] = (byte) ii;
                  b[off++] = (byte) (ii >>> 8);
                  b[off++] = (byte) (ii >>> 16);
                  b[off++] = (byte) (ii >>> 24);
              }
          } else {
              int off = 0;
              for (int ii : arr) {
                  b[off++] = (byte) (ii >>> 24);
                  b[off++] = (byte) (ii >>> 16);
                  b[off++] = (byte) (ii >>> 8);
                  b[off++] = (byte) ii;
              }
          }
      }
  
      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 - with no leading 0, like 0xabc v.s. 0x0abc
       *
       * @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() & 1) == 1) {
              data = "0" + data;
          }
          return Hex.decode(data);
      }
  
      /**
       * Chops a 32-byte value into a 16-byte value. Keep in mind the subtlety that a "chopped"
       * bytearray is a different reference from a "unchopped" bytearray, so make no assummptions as
       * to whether this function news the element.
       *
       * @return 16-byte value representing the LOWER portion of the original
       */
      public static byte[] chop(byte[] in) {
          if (in.length <= 16) {
              return in;
          }
          return Arrays.copyOfRange(in, in.length - 16, in.length);
      }
  }
  