1

I have a byte array from which I need to read specific bits and convert to int (see the byte array structure below). Even though the bits information I want to read is in 3 bytes, I tried reading 4 bytes (6-9) as integer and then read the bits from that integer value with bits or bitsValue method but somehow I am not able to see the right values from the bit manipulation. And with my expertise in bits I am pretty sure I am doing something wrong. Can someone please suggest I am doing it correctly and why its not working. Thanks in Advance!!

Byte array is in Little Endian format.

0th byte - Some Value 1st Byte - Some Value 2nd - 5th Byte - Some Value 6th - 9th Byte - first 18 bits represent some value - Next 5 bits represent some value - Next 1 bit represent some value - Last 8 bits represent some value

 public class Test {

  public static void main(String... dataProvider) {
    String s = "46 00 ef 30 e9 08 cc a5 03 43";
    byte[] bytes = new byte[s.length()];
    bytes = hexStringToByteArray(s);

    int bytePointer = 0;
    int msgType = getIntFromSingleByte(bytes[bytePointer]); // 0th byte
    int version = getIntFromSingleByte(bytes[++bytePointer]); // 1st byte
    int tickInMS = getIntValue(bytes, ++bytePointer); // 2nd-5th bytes

    bytePointer = bytePointer + 4;
    int headr = getIntValue(bytes, bytePointer); // 6th-9th bytes
    int utcTime = bits(headr, 0, 18); // 6th-9th bytes - 18 bits
    int reserved = bits(headr, 18, 5); // 6th-9th bytes- 5 bits
    int reportOrEvent = bits(headr, 23, 1); // 6th-9th bytes - 1 bits
    int reportId = bitsValue(headr, 24, 32); // 6th-9th- 8 bits

  }



    public static int getIntFromSingleByte(byte data) {
        return (data & 0xFF);
      }

      public static int getIntValue(byte[] bytes, int startPosition) {
        byte[] dest = new byte[4];
        System.arraycopy(bytes, startPosition, dest, 0, dest.length);
        return toInt(dest);
      }

      // took from Stack overflow
      static int bits(int n, int offset, int length) {
        // shift the bits rightward, so that the desired chunk is at the right end
        n = n >> (31 - offset - length);
        // prepare a mask where only the rightmost `length` bits are 1's
        int mask = ~(-1 << length);
        // zero out all bits but the right chunk
        return n & mask;
      }

      public static int bitsValue(int intNum, int startBitPos, int endBitPos) {
        // parameters checking ignored for now
        int tempValue = intNum << endBitPos;
        return tempValue >> (startBitPos + endBitPos);
      }

      public static byte[] hexStringToByteArray(final String s) {
        String[] splits = s.split(" ");
        final byte[] data = new byte[splits.length];
        for (int i = 0; i < splits.length; i++) {
          char first = splits[i].length() < 2 ? '0' : splits[i].charAt(0);
          char second = splits[i].length() < 2 ? splits[i].charAt(0) : splits[i].charAt(1);
          data[i] = (byte) ((Character.digit(first, 16) << 4) + Character.digit(second, 16));
        }
    return data;
  }

  public static int toInt(byte[] data) {
    if (data == null || data.length != 4)
      return 0x0;

    return (int) ((0xff & data[0]) << 24 | (0xff & data[1]) << 16 | (0xff & data[2]) << 8
        | (0xff & data[3]) << 0);
  }
}
Ashu
  • 1,703
  • 4
  • 16
  • 23

2 Answers2

2

Wrapping your input data in a ByteBuffer will simplify parsing and allow you to adjust endianness as necessary.

Your bits method is wrong. The constant 31 should be 32. Also, the method uses MSB 0 bit numbering, which is odd for little-endian data. You should confirm that your input is documented as using this bit numbering scheme.

Your bitsValue method is wrong too. May as well just use bits after fixing it.

This code is simpler and extracts the bit fields correctly:

public static void main(String... args) {
    String s = "46 0 79 37 a8 3 9f 37 1 43 eb 7a f 3 3 fe c4 1 c5 4 c5 5e";
    byte[] input = hexStringToByteArray(s);

    // Wrap the input in a ByteBuffer for parsing. Adjust endianness if necessary.
    ByteBuffer buffer = ByteBuffer.wrap(input).order(ByteOrder.BIG_ENDIAN);

    int msgType = buffer.get() & 0xff;
    int version = buffer.get() & 0xff;
    int tickInMS = buffer.getInt();
    int header = buffer.getInt();

    int utcTime = bits(header, 0, 18);          // 6th-9th bytes - 18 bits
    int reserved = bits(header, 18, 5);         // 6th-9th bytes - 5 bits
    int reportOrEvent = bits(header, 23, 1);    // 6th-9th bytes - 1 bit
    int reportId = bits(header, 24, 8);         // 6th-9th bytes - 8 bits

    System.out.printf("utc: %d, report? %d, id: %d\n", utcTime, reportOrEvent, reportId);
}

/**
 * Extract a bit field from an int. Bit numbering is MSB 0.
 */
public static int bits(int n, int offset, int length) {
    return n >> (32 - offset - length) & ~(-1 << length);
}
teppic
  • 7,051
  • 1
  • 29
  • 35
  • this is sample I was using "46 0 79 37 a8 3 9f 37 1 43 eb 7a f 3 3 fe c4 1 c5 4 c5 5e" 11 bit (eb) should be 235 but when I am passing it through logic its giving me -21 – Ashu Sep 12 '17 at 21:26
  • But you're not reading byte 11 in your example (it stops at 10). – teppic Sep 12 '17 at 21:46
0

Instead of error prone bit mangeling you should rather use the binary string representation of the numbers and do your "bit picking" as string operations:

String s = "46 00 ef 30 e9 08 cc a5 03 43";
String[] hexNumbers = s.split(" ");
for(String hexNumber : hexNumbers) {
  String binaryNumber = String.format("%8s", new BigInteger(hexNumber,16).toString(2)).replace(' ','0');
  System.out.print(String.format("value in hex : %s, value in binary: %s",  hexNumber,binaryNumber));
}
Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51
  • hex number were running the test cases. in production system I need to work with actual bytes. I don't want to add an additional step of converting hex and doing on manipulation in them. Even in that case I am not sure If I understand how can I read 18/5/1 bites from 3 hex numbers (3 bytes) – Ashu Sep 11 '17 at 19:54
  • Did you test that? %s with flag 0 throws for me on Oracle Java 8 and earlier, and the javadoc for `Formatter` agrees 0 doesn't work on category 'general' which 's' is. (This differs from C and C++ where 0 flag can be applied to any conversion.) – dave_thompson_085 Sep 11 '17 at 22:02