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

bt.protocol.Protocols Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2016—2021 Andrei Tomashpolskiy and individual contributors.
 *
 * 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 bt.protocol;

import bt.BtException;
import bt.net.buffer.ByteBufferView;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.BitSet;

/**
 * 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 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
     * @throws IllegalStateException if there are insufficient bytes in buffer
     *         (i.e. buffer.remaining() < 4)
     * @since 1.9
     */
    public static int readInt(ByteBufferView buffer) {
        if (buffer.remaining() < Integer.BYTES) {
            throw new IllegalStateException("Insufficient room to read an int");
        }
        return buffer.getInt();
    }

    /**
     * Check that remaining bytes in buffer are enough to read an int
     *
     * @param buffer Buffer to read from.
     * @return true if buffer has enough remaining length to read an int
     * @since 1.10
     */
    public static boolean canReadInt(ByteBufferView buffer) {
        return buffer.remaining() >= Integer.BYTES;
    }

    /**
     * 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();
    }

    /**
     * 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.9
     */
    public static Short readShort(ByteBufferView 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 bitOrder Order of bits in a byte
     * @param i Bit index (0-based)
     * @since 1.7
     */
    public static void setBit(byte[] bytes, BitOrder bitOrder, 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 = (bitOrder == BitOrder.BIG_ENDIAN) ? bitIndex : (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 bitOrder Order of bits in a byte
     * @param i Bit index (0-based)
     * @return 1 if bit is set, 0 otherwise
     * @since 1.7
     */
    public static int getBit(byte[] bytes, BitOrder bitOrder, int i) {
        int byteIndex = i >> 3; // divide by 8
        if (byteIndex >= bytes.length) {
            throw new BtException("bit index is too large: " + i);
        }

        int bitIndex = i & 0b111;
        int shift = (bitOrder == BitOrder.BIG_ENDIAN) ? bitIndex : (7 - bitIndex);
        int bitMask = 0b1 << shift ;
        return (bytes[byteIndex] & bitMask) >> shift;
    }

    /**
     * Check if i-th bit in the bitmask is set.
     *
     * @param bytes Bitmask.
     * @param bitOrder Order of bits in a byte
     * @param i Bit index (0-based)
     * @return true if i-th bit in the bitmask is set, false otherwise
     * @since 1.7
     */
    public static boolean isSet(byte[] bytes, BitOrder bitOrder, int i) {
        return getBit(bytes, bitOrder, i) == 1;
    }

    /**
     * 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;
    }

    /**
     * Get 20-bytes long info hash from its' base32-encoded representation (regardless of case).
     *
     * @param s base32-encoded representation of info hash
     * @return Binary data
     * @throws IllegalArgumentException if {@code s.length()} is not 32 characters long
     * @since 1.8
     */
    public static byte[] infoHashFromBase32(String s) {
        if (s.isEmpty() || s.length() != 32) {
            throw new IllegalArgumentException("Invalid string: " + s);
        }
        String base32CodeBase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
        StringBuilder hexCache = new StringBuilder();
        for (int i = 0; i < s.length(); i += 4) {
            int hexValue = base32CodeBase.indexOf(s.charAt(i)) << 15 |
                    base32CodeBase.indexOf(s.charAt(i+1)) << 10 |
                    base32CodeBase.indexOf(s.charAt(i+2)) << 5 |
                    base32CodeBase.indexOf(s.charAt(i+3));
            hexCache.append(String.format("%05X", hexValue));
        }
        return fromHex(hexCache.toString());
    }

    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);
    }

    /**
     * Returns a copy of the provided byte array with each byte reversed.
     *
     * @return A copy of the provided byte array with each byte reversed.
     * @since 1.7
     */
    public static byte[] reverseBits(byte[] bytes) {
        byte[] arr = Arrays.copyOf(bytes, bytes.length);
        for (int i = 0; i < arr.length; i++) {
            arr[i] = reverseBits(arr[i]);
        }
        return arr;
    }

    /**
     * Returns a reversed byte.
     *
     * @return A reversed byte.
     * @since 1.7
     */
    public static byte reverseBits(byte b) {
        int i = b;
        // swap halves
        i = (i & 0b11110000) >> 4 | (i & 0b00001111) << 4;
        // swap adjacent pairs
        i = (i & 0b11001100) >> 2 | (i & 0b00110011) << 2;
        // swap adjacent bits
        i = (i & 0b10101010) >> 1 | (i & 0b01010101) << 1;
        return (byte) i;
    }

    /**
     * Get a copy of the provided bitset.
     *
     * @return A copy of the provided bitset.
     * @since 1.7
     */
    public static BitSet copyOf(BitSet bitSet) {
        return (BitSet) bitSet.clone();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy