bt.protocol.Protocols Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bt-core Show documentation
Show all versions of bt-core Show documentation
BitTorrent Client Library (Core)
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 extends Message> 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);
}
}