All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bt.protocol.Protocols Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
package bt.protocol;

import bt.BtException;

import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * Provides utility functions for binary protocol implementations.
 *
 * @since 1.0
 */
public class Protocols {

    //-------------------------//
    //--- utility functions ---//
    //-------------------------//

    /**
     * Get 8-bytes binary representation of a {@link Long}.
     *
     * @since 1.0
     */
    public static byte[] getLongBytes(long l) {
        return new byte[] {
                (byte) (l >> 56),
                (byte) (l >> 48),
                (byte) (l >> 40),
                (byte) (l >> 32),
                (byte) (l >> 24),
                (byte) (l >> 16),
                (byte) (l >> 8),
                (byte) l};
    }

    /**
     * Get 4-bytes binary representation of an {@link Integer}.
     *
     * @since 1.0
     */
    public static byte[] getIntBytes(int i) {
        return new byte[] {
                (byte) (i >> 24),
                (byte) (i >> 16),
                (byte) (i >> 8),
                (byte) i};
    }

    /**
     * Get 2-bytes binary representation of a {@link Short}.
     *
     * @since 1.0
     */
    public static byte[] getShortBytes(int s) {
        return new byte[] {
                (byte) (s >> 8),
                (byte) s};
    }

    /**
     * Decode the binary representation of a {@link Long} from a byte array.
     *
     * @param bytes Arbitrary byte array.
     *              It's length must be at least offset + 8.
     * @param offset Offset in byte array to start decoding from (inclusive, 0-based)
     * @since 1.0
     */
    public static long readLong(byte[] bytes, int offset) {

        if (bytes.length < offset + Long.BYTES) {
            throw new ArrayIndexOutOfBoundsException("insufficient byte array length (length: " + bytes.length +
                    ", offset: " + offset + ")");
        }
        return ((bytes[offset]     & 0xFFL) << 56) |
               ((bytes[offset + 1] & 0xFFL) << 48) |
               ((bytes[offset + 2] & 0xFFL) << 40) |
               ((bytes[offset + 3] & 0xFFL) << 32) |
               ((bytes[offset + 4] & 0xFFL)  << 24) |
               ((bytes[offset + 5] & 0xFF)  << 16) |
               ((bytes[offset + 6] & 0xFF)  << 8)  |
                (bytes[offset + 7] & 0xFF);
    }

    /**
     * Decode the binary representation of a {@link Long} from a buffer.
     *
     * @param buffer Buffer to read from.
     *               Decoding will be done starting with the index denoted by {@link Buffer#position()}
     * @return Decoded value, or null if there are insufficient bytes in buffer
     *         (i.e. buffer.remaining() < 8)
     * @since 1.0
     */
    public static Long readLong(ByteBuffer buffer) {
        if (buffer.remaining() < Long.BYTES) {
            return null;
        }
        return buffer.getLong();
    }

    /**
     * Decode the binary representation of an {@link Integer} from a byte array.
     *
     * @param bytes Arbitrary byte array.
     *              It's length must be at least offset + 4.
     * @param offset Offset in byte array to start decoding from (inclusive, 0-based)
     * @since 1.0
     */
    public static int readInt(byte[] bytes, int offset) {

        if (bytes.length < offset + Integer.BYTES) {
            throw new ArrayIndexOutOfBoundsException("insufficient byte array length (length: " + bytes.length +
                    ", offset: " + offset + ")");
        }
        return ((bytes[offset]     & 0xFF) << 24) |
               ((bytes[offset + 1] & 0xFF) << 16) |
               ((bytes[offset + 2] & 0xFF) << 8)  |
                (bytes[offset + 3] & 0xFF);
    }

    /**
     * Decode the binary representation of an {@link Integer} from a buffer.
     *
     * @param buffer Buffer to read from.
     *               Decoding will be done starting with the index denoted by {@link Buffer#position()}
     * @return Decoded value, or null if there are insufficient bytes in buffer
     *         (i.e. buffer.remaining() < 4)
     * @since 1.0
     */
    public static Integer readInt(ByteBuffer buffer) {
        if (buffer.remaining() < Integer.BYTES) {
            return null;
        }
        return buffer.getInt();
    }

    /**
     * Decode the binary representation of a {@link Short} from a byte array.
     *
     * @param bytes Arbitrary byte array.
     *              It's length must be at least offset + 2.
     * @param offset Offset in byte array to start decoding from (inclusive, 0-based)
     * @since 1.0
     */
    public static short readShort(byte[] bytes, int offset) {

        if (bytes.length < offset + Short.BYTES) {
            throw new ArrayIndexOutOfBoundsException("insufficient byte array length (length: " + bytes.length +
                    ", offset: " + offset + ")");
        }
        return (short)(((bytes[offset]     & 0xFF) << 8) |
                       ((bytes[offset + 1] & 0xFF)));
    }

    /**
     * Decode the binary representation of a {@link Short} from a buffer.
     *
     * @param buffer Buffer to read from.
     *               Decoding will be done starting with the index denoted by {@link Buffer#position()}
     * @return Decoded value, or null if there are insufficient bytes in buffer
     *         (i.e. buffer.remaining() < 2)
     * @since 1.0
     */
    public static Short readShort(ByteBuffer buffer) {
        if (buffer.remaining() < Short.BYTES) {
            return null;
        }
        return buffer.getShort();
    }

    /**
     * Convenience method to check if actual message length is the same as expected length.
     *
     * @throws InvalidMessageException if expectedLength != actualLength
     * @since 1.0
     */
    public static void verifyPayloadHasLength(Class type, int expectedLength, int actualLength) {
        if (expectedLength != actualLength) {
            throw new InvalidMessageException("Unexpected payload length for " + type.getSimpleName() + ": " + actualLength +
                    " (expected " + expectedLength + ")");
        }
    }

    /**
     * Sets i-th bit in a bitmask.
     *
     * @param bytes Bitmask.
     * @param i Bit index (0-based)
     * @since 1.0
     */
    public static void setBit(byte[] bytes, int i) {

        int byteIndex = (int) (i / 8d);
        if (byteIndex >= bytes.length) {
            throw new BtException("bit index is too large: " + i);
        }

        int bitIndex = i % 8;
        int shift = (7 - bitIndex);
        int bitMask = 0b1 << shift;
        byte currentByte = bytes[byteIndex];
        bytes[byteIndex] = (byte) (currentByte | bitMask);
    }

    /**
     * Gets i-th bit in a bitmask.
     *
     * @param bytes Bitmask.
     * @param i Bit index (0-based)
     * @return 1 if bit is set, 0 otherwise
     * @since 1.0
     */
    public static int getBit(byte[] bytes, int i) {

        int byteIndex = (int) (i / 8d);
        if (byteIndex >= bytes.length) {
            throw new BtException("bit index is too large: " + i);
        }

        int bitIndex = i % 8;
        int shift = (7 - bitIndex);
        int bitMask = 0b1 << shift ;
        return (bytes[byteIndex] & bitMask) >> shift;
    }

    /**
     * Get hex-encoded representation of a binary array.
     *
     * @param bytes Binary data
     * @return String containing hex-encoded representation (lower case)
     * @since 1.3
     */
    public static String toHex(byte[] bytes) {
        if (bytes.length == 0) {
            throw new IllegalArgumentException("Empty array");
        }
        char[] chars = new char[bytes.length * 2];
        for (int i = 0, j = 0; i < bytes.length; i++, j = i * 2) {
            int b = bytes[i] & 0xFF;
            chars[j] = forHexDigit(b / 16);
            chars[j+1] = forHexDigit(b % 16);
        }
        return new String(chars);
    }

    private static char forHexDigit(int b) {
        if (b < 0 || b >= 16) {
            throw new IllegalArgumentException("Illegal hexadecimal digit: " + b);
        }
        return (b < 10) ? (char)('0' + b) : (char)('a' + b - 10);
    }

    /**
     * Get binary data from its' hex-encoded representation (regardless of case).
     *
     * @param s Hex-encoded representation of binary data
     * @return Binary data
     * @since 1.3
     */
    public static byte[] fromHex(String s) {
        if (s.isEmpty() || s.length() % 2 != 0) {
            throw new IllegalArgumentException("Invalid string: " + s);
        }
        char[] chars = s.toCharArray();
        int len = chars.length / 2;
        byte[] bytes = new byte[len];
        for (int i = 0, j = 0; i < len; i++, j = i * 2) {
            bytes[i] = (byte) (hexDigit(chars[j]) * 16 + hexDigit(chars[j + 1]));
        }
        return bytes;
    }

    private static int hexDigit(char c) {
        if (c >= '0' && c <= '9') {
            return c - '0';
        }
        else if (c >= 'A' && c <= 'F') {
            return c - 'A' + 10;
        }
        else if (c >= 'a' && c <= 'f') {
            return c - 'a' + 10;
        }
        throw new IllegalArgumentException("Illegal hexadecimal character: " + c);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy