Обновленеи протокола, убран legacy код, переход на более практичную схему TLV

This commit is contained in:
RoyceDa
2026-04-18 17:49:58 +02:00
parent 9432969cb4
commit 068492b56d
20 changed files with 1353 additions and 528 deletions

View File

@@ -1,3 +0,0 @@
package io.orprotocol;
public class Context extends Object {}

View File

@@ -13,11 +13,11 @@ import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer; import org.java_websocket.server.WebSocketServer;
import io.orprotocol.client.Client; import io.orprotocol.client.Client;
import io.orprotocol.frame.FrameDecoder;
import io.orprotocol.index.ClientIndexer; import io.orprotocol.index.ClientIndexer;
import io.orprotocol.lock.ThreadLocker; import io.orprotocol.lock.ThreadLocker;
import io.orprotocol.packet.Packet; import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketExecutor; import io.orprotocol.packet.PacketExecutor;
import io.orprotocol.packet.PacketFactory;
import io.orprotocol.packet.PacketManager; import io.orprotocol.packet.PacketManager;
public class Server extends WebSocketServer { public class Server extends WebSocketServer {
@@ -104,9 +104,9 @@ public class Server extends WebSocketServer {
/** /**
* Создаем пакет из полученных байтов. * Создаем пакет из полученных байтов.
*/ */
PacketFactory packetFactory = new PacketFactory(bytes, this.packetManager); FrameDecoder frameDecoder = new FrameDecoder(bytes, client, this.packetManager);
Packet packet = packetFactory.createPacket(); Packet packet = frameDecoder.decode();
int packetId = packetFactory.getPacketId(); int packetId = packet.packetId;
/** /**
* Получаем обработчик пакета и вызываем его метод обработки. * Получаем обработчик пакета и вызываем его метод обработки.
* *
@@ -127,7 +127,7 @@ public class Server extends WebSocketServer {
if(listener != null && !listener.onPacketReceived(this, client, packet)) { if(listener != null && !listener.onPacketReceived(this, client, packet)) {
/** /**
* Если слушатель сервера вернул false, пакет не обрабатываем. * Если слушатель сервера вернул false, пакет не обрабатываем.
*/ */
return; return;
} }
/** /**
@@ -162,6 +162,13 @@ public class Server extends WebSocketServer {
* Если клиент не отправляет heartbeat в указанный интервал, его можно отключить. * Если клиент не отправляет heartbeat в указанный интервал, его можно отключить.
*/ */
Client client = new Client(socket, this.settings.heartbeatInterval, this); Client client = new Client(socket, this.settings.heartbeatInterval, this);
/**
* Устанавливаем версию клиента
*/
client.setVersion(this.extractVersionFromHandshake(handshake));
/**
* Получаем адрес клиента
*/
String ipAddress = handshake.getFieldValue("X-Forwarded-For"); String ipAddress = handshake.getFieldValue("X-Forwarded-For");
if (ipAddress == null || ipAddress.isEmpty()) { if (ipAddress == null || ipAddress.isEmpty()) {
ipAddress = socket.getRemoteSocketAddress().getAddress().getHostAddress(); ipAddress = socket.getRemoteSocketAddress().getAddress().getHostAddress();
@@ -182,6 +189,23 @@ public class Server extends WebSocketServer {
} }
} }
/**
* Версия передается в адресной строке при подключении клиента, например ws://.../?v=1,
* если версия не передана - возвращается первая версия по умолчанию (1)
* @param handshake
*/
private int extractVersionFromHandshake(ClientHandshake handshake) {
String versionStr = handshake.getFieldValue("v");
if(versionStr == null || versionStr.isEmpty()) {
return 1;
}
try {
return Integer.parseInt(versionStr);
} catch (NumberFormatException e) {
return 1;
}
}
@Override @Override
public void onStart() { public void onStart() {
/** /**

View File

@@ -1,402 +0,0 @@
package io.orprotocol;
import java.util.Arrays;
/**
* Поток побитовой/побайтовой записи и чтения.
*
* Поддержка:
* - signed: Int8/16/32/64 (классический two's complement)
* - unsigned: UInt8/16/32/64
* - String: длина UInt32 + символы UInt16
* - byte[]: длина UInt32 + сырые байты
*/
public class Stream {
private byte[] stream;
private int readPointer = 0; // bits
private int writePointer = 0; // bits
public Stream() {
this.stream = new byte[0];
}
public Stream(byte[] stream) {
this.stream = (stream == null) ? new byte[0] : stream;
this.readPointer = 0;
this.writePointer = this.stream.length << 3;
}
public byte[] getStream() {
return Arrays.copyOf(this.stream, length());
}
public void setStream(byte[] stream) {
this.stream = (stream == null) ? new byte[0] : stream;
this.readPointer = 0;
this.writePointer = this.stream.length << 3;
}
public byte[] getBuffer() {
return getStream();
}
public boolean isEmpty() {
return writePointer == 0;
}
/** Количество реально записанных байт (без резервной емкости). */
public int length() {
return (writePointer + 7) >> 3;
}
// ---------- bit / boolean ----------
public void writeBit(int value) {
writeBits(value & 1L, 1);
}
public int readBit() {
return (int) readBits(1);
}
public void writeBoolean(boolean value) {
writeBit(value ? 1 : 0);
}
public boolean readBoolean() {
return readBit() == 1;
}
// ---------- byte ----------
public void writeByte(byte b) {
writeUInt8(b & 0xFF);
}
public byte readByte() {
return (byte) readUInt8();
}
// ---------- UInt / Int 8 ----------
public void writeUInt8(int value) {
int v = value & 0xFF;
// fast path when byte-aligned
if ((writePointer & 7) == 0) {
reserveBits(8);
stream[writePointer >> 3] = (byte) v;
writePointer += 8;
return;
}
writeBits(v, 8);
}
public int readUInt8() {
if (remainingBits() < 8L) {
throw new IllegalStateException("Not enough bits to read UInt8");
}
// fast path when byte-aligned
if ((readPointer & 7) == 0) {
int value = stream[readPointer >> 3] & 0xFF;
readPointer += 8;
return value;
}
return (int) readBits(8);
}
public void writeInt8(int value) {
writeUInt8(value); // lower 8 bits (two's complement)
}
public int readInt8() {
return (byte) readUInt8(); // sign extension
}
// ---------- UInt / Int 16 ----------
public void writeUInt16(int value) {
int v = value & 0xFFFF;
writeUInt8((v >>> 8) & 0xFF);
writeUInt8(v & 0xFF);
}
public int readUInt16() {
int hi = readUInt8();
int lo = readUInt8();
return (hi << 8) | lo;
}
public void writeInt16(int value) {
writeUInt16(value); // lower 16 bits (two's complement)
}
public int readInt16() {
return (short) readUInt16(); // sign extension
}
// ---------- UInt / Int 32 ----------
public void writeUInt32(long value) {
if (value < 0 || value > 0xFFFFFFFFL) {
throw new IllegalArgumentException("UInt32 out of range: " + value);
}
writeUInt8((int) ((value >>> 24) & 0xFF));
writeUInt8((int) ((value >>> 16) & 0xFF));
writeUInt8((int) ((value >>> 8) & 0xFF));
writeUInt8((int) (value & 0xFF));
}
public long readUInt32() {
long b1 = readUInt8() & 0xFFL;
long b2 = readUInt8() & 0xFFL;
long b3 = readUInt8() & 0xFFL;
long b4 = readUInt8() & 0xFFL;
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
}
public void writeInt32(int value) {
writeUInt32(value & 0xFFFFFFFFL);
}
public int readInt32() {
return (int) readUInt32();
}
// ---------- UInt / Int 64 ----------
/**
* Записывает 64 бита как есть (для UInt64 это битовый паттерн в long).
*/
public void writeUInt64(long value) {
writeUInt8((int) ((value >>> 56) & 0xFF));
writeUInt8((int) ((value >>> 48) & 0xFF));
writeUInt8((int) ((value >>> 40) & 0xFF));
writeUInt8((int) ((value >>> 32) & 0xFF));
writeUInt8((int) ((value >>> 24) & 0xFF));
writeUInt8((int) ((value >>> 16) & 0xFF));
writeUInt8((int) ((value >>> 8) & 0xFF));
writeUInt8((int) (value & 0xFF));
}
/**
* Читает 64 бита как есть (битовый паттерн в long).
*/
public long readUInt64() {
long high = readUInt32() & 0xFFFFFFFFL;
long low = readUInt32() & 0xFFFFFFFFL;
return (high << 32) | low;
}
public void writeInt64(long value) {
writeUInt64(value);
}
public long readInt64() {
return readUInt64();
}
// ---------- float ----------
public void writeFloat32(float value) {
writeInt32(Float.floatToIntBits(value));
}
public float readFloat32() {
return Float.intBitsToFloat(readInt32());
}
// ---------- string / bytes ----------
/**
* String: length(UInt32) + chars(UInt16).
*/
public void writeString(String value) {
if (value == null) {
value = "";
}
int length = value.length();
writeUInt32(length);
if (length == 0) {
return;
}
reserveBits((long) length * 16L);
for (int i = 0; i < length; i++) {
writeUInt16(value.charAt(i));
}
}
public String readString() {
long lenLong = readUInt32();
if (lenLong > Integer.MAX_VALUE) {
throw new IllegalStateException("String length too large: " + lenLong);
}
int length = (int) lenLong;
long requiredBits = (long) length * 16L;
if (requiredBits > remainingBits()) {
throw new IllegalStateException("Not enough bits to read string");
}
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append((char) readUInt16());
}
return sb.toString();
}
/**
* byte[]: length(UInt32) + raw bytes.
*/
public void writeBytes(byte[] value) {
if (value == null) {
value = new byte[0];
}
writeUInt32(value.length);
if (value.length == 0) {
return;
}
reserveBits((long) value.length * 8L);
// fast path when byte-aligned
if ((writePointer & 7) == 0) {
int byteIndex = writePointer >> 3;
ensureCapacity(byteIndex + value.length - 1);
System.arraycopy(value, 0, stream, byteIndex, value.length);
writePointer += (value.length << 3);
return;
}
for (byte b : value) {
writeUInt8(b & 0xFF);
}
}
public byte[] readBytes() {
long lenLong = readUInt32();
if (lenLong == 0) {
return new byte[0];
}
if (lenLong > Integer.MAX_VALUE) {
throw new IllegalStateException("Byte array too large: " + lenLong);
}
int length = (int) lenLong;
long requiredBits = (long) length * 8L;
if (requiredBits > remainingBits()) {
return new byte[0];
}
byte[] out = new byte[length];
// fast path when byte-aligned
if ((readPointer & 7) == 0) {
int byteIndex = readPointer >> 3;
System.arraycopy(stream, byteIndex, out, 0, length);
readPointer += (length << 3);
return out;
}
for (int i = 0; i < length; i++) {
out[i] = (byte) readUInt8();
}
return out;
}
// ---------- internals ----------
private long remainingBits() {
return (long) writePointer - readPointer;
}
private void writeBits(long value, int bits) {
if (bits <= 0) {
return;
}
reserveBits(bits);
for (int i = bits - 1; i >= 0; i--) {
int bit = (int) ((value >>> i) & 1L);
int byteIndex = writePointer >> 3;
int shift = 7 - (writePointer & 7);
if (bit == 1) {
stream[byteIndex] = (byte) (stream[byteIndex] | (1 << shift));
} else {
stream[byteIndex] = (byte) (stream[byteIndex] & ~(1 << shift));
}
writePointer++;
}
}
private long readBits(int bits) {
if (bits <= 0) {
return 0;
}
if (remainingBits() < bits) {
throw new IllegalStateException("Not enough bits to read");
}
long value = 0;
for (int i = 0; i < bits; i++) {
int bit = (stream[readPointer >> 3] >> (7 - (readPointer & 7))) & 1;
value = (value << 1) | bit;
readPointer++;
}
return value;
}
private void reserveBits(long bitsToWrite) {
if (bitsToWrite <= 0) {
return;
}
long lastBitIndex = (long) writePointer + bitsToWrite - 1;
if (lastBitIndex < 0) {
throw new IllegalStateException("Bit index overflow");
}
long byteIndexLong = lastBitIndex >> 3;
if (byteIndexLong > Integer.MAX_VALUE) {
throw new IllegalStateException("Stream too large");
}
ensureCapacity((int) byteIndexLong);
}
/**
* Выделяет нужное количество места в массиве байтов для записи данных, если текущий размер массива недостаточен.
* @param byteIndex индекс байта, который нужно зарезервировать для записи данных
* @internal Этот метод используется внутри класса Stream для управления размером внутреннего массива байтов, который хранит данные потока.
*/
private void ensureCapacity(int byteIndex) {
int requiredSize = byteIndex + 1;
if (requiredSize <= stream.length) {
return;
}
int newSize = (stream.length == 0) ? 32 : stream.length;
while (newSize < requiredSize) {
if (newSize > (Integer.MAX_VALUE / 2)) {
newSize = requiredSize;
break;
}
newSize <<= 1;
}
stream = Arrays.copyOf(stream, newSize);
}
}

View File

@@ -0,0 +1,410 @@
package io.orprotocol.buffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* Low-level бинарный буфер.
*
* Формат:
* - Int/UInt: big-endian
* - String: raw UTF-8 bytes (без длины) (убрана длинна)
* - byte[]: raw bytes (без длины) (убрана длинна)
* - boolean: 1 бит (0 или 1)
* - битовые поля: могут быть не выровнены по байтам, пишутся бит за битом
* - при записи буфер автоматически расширяется, при чтении проверяется
*/
public class Buffer {
private static final int DEFAULT_CAPACITY = 32;
private byte[] buffer;
private int readBitPos; // позиция чтения в битах
private int writeBitPos; // позиция записи в битах
public Buffer() {
this.buffer = new byte[0];
this.readBitPos = 0;
this.writeBitPos = 0;
}
public Buffer(byte[] data) {
this.buffer = (data == null) ? new byte[0] : Arrays.copyOf(data, data.length);
this.readBitPos = 0;
this.writeBitPos = this.buffer.length << 3;
}
public static Buffer wrap(byte[] data) {
return new Buffer(data);
}
public static Buffer allocate(int capacity) {
Buffer buf = new Buffer();
buf.ensureCapacity(capacity);
return buf;
}
// ---------- state ----------
public boolean hasRemaining() {
return readBitPos < writeBitPos;
}
public int remaining() {
return (int) ((writeBitPos - readBitPos + 7) >>> 3);
}
public int length() {
return (writeBitPos + 7) >>> 3;
}
public boolean isEmpty() {
return writeBitPos == 0;
}
public byte[] toByteArray() {
return Arrays.copyOf(buffer, length());
}
public void resetRead() {
this.readBitPos = 0;
}
public void clear() {
this.buffer = new byte[0];
this.readBitPos = 0;
this.writeBitPos = 0;
}
// ---------- bit / boolean ----------
public void writeBit(int value) {
writeBits(value & 1L, 1);
}
public int readBit() {
return (int) readBits(1);
}
public void writeBoolean(boolean value) {
writeBit(value ? 1 : 0);
}
public boolean readBoolean() {
return readBit() != 0;
}
// ---------- byte ----------
public void writeByte(byte value) {
writeUInt8(value & 0xFF);
}
public byte readByte() {
return (byte) readUInt8();
}
// ---------- UInt8 / Int8 ----------
public void writeUInt8(int value) {
int v = value & 0xFF;
if (isWriteByteAligned()) {
ensureCapacityBits(8);
buffer[writeBitPos >>> 3] = (byte) v;
writeBitPos += 8;
return;
}
writeBits(v, 8);
}
public int readUInt8() {
ensureReadableBits(8);
if (isReadByteAligned()) {
int v = buffer[readBitPos >>> 3] & 0xFF;
readBitPos += 8;
return v;
}
return (int) readBits(8);
}
public void writeInt8(int value) {
writeUInt8(value);
}
public int readInt8() {
return (byte) readUInt8();
}
// ---------- UInt16 / Int16 ----------
public void writeUInt16(int value) {
writeUInt8((value >>> 8) & 0xFF);
writeUInt8(value & 0xFF);
}
public int readUInt16() {
int hi = readUInt8();
int lo = readUInt8();
return (hi << 8) | lo;
}
public void writeInt16(int value) {
writeUInt16(value);
}
public int readInt16() {
return (short) readUInt16();
}
// ---------- UInt32 / Int32 ----------
public void writeUInt32(long value) {
if (value < 0 || value > 0xFFFFFFFFL) {
throw new IllegalArgumentException("UInt32 out of range: " + value);
}
writeUInt8((int) ((value >>> 24) & 0xFF));
writeUInt8((int) ((value >>> 16) & 0xFF));
writeUInt8((int) ((value >>> 8) & 0xFF));
writeUInt8((int) (value & 0xFF));
}
public long readUInt32() {
long b1 = readUInt8() & 0xFFL;
long b2 = readUInt8() & 0xFFL;
long b3 = readUInt8() & 0xFFL;
long b4 = readUInt8() & 0xFFL;
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
}
public void writeInt32(int value) {
writeUInt32(value & 0xFFFFFFFFL);
}
public int readInt32() {
return (int) readUInt32();
}
// ---------- UInt64 / Int64 ----------
public void writeUInt64(long value) {
writeUInt8((int) ((value >>> 56) & 0xFF));
writeUInt8((int) ((value >>> 48) & 0xFF));
writeUInt8((int) ((value >>> 40) & 0xFF));
writeUInt8((int) ((value >>> 32) & 0xFF));
writeUInt8((int) ((value >>> 24) & 0xFF));
writeUInt8((int) ((value >>> 16) & 0xFF));
writeUInt8((int) ((value >>> 8) & 0xFF));
writeUInt8((int) (value & 0xFF));
}
public long readUInt64() {
long high = readUInt32() & 0xFFFFFFFFL;
long low = readUInt32() & 0xFFFFFFFFL;
return (high << 32) | low;
}
public void writeInt64(long value) {
writeUInt64(value);
}
public long readInt64() {
return readUInt64();
}
// ---------- floating point ----------
public void writeFloat(float value) {
writeInt32(Float.floatToIntBits(value));
}
public float readFloat() {
return Float.intBitsToFloat(readInt32());
}
public void writeDouble(double value) {
writeInt64(Double.doubleToLongBits(value));
}
public double readDouble() {
return Double.longBitsToDouble(readInt64());
}
// ---------- raw bytes (без префикса длины) ----------
/**
* Пишет ровно N байт без метаданных.
* Вызывающий код сам должен знать длину.
*/
public void writeBytes(byte[] data) {
if (data == null || data.length == 0) {
return;
}
if (isWriteByteAligned()) {
ensureCapacityBits((long) data.length * 8L);
int byteIndex = writeBitPos >>> 3;
System.arraycopy(data, 0, buffer, byteIndex, data.length);
writeBitPos += data.length << 3;
return;
}
for (byte b : data) {
writeUInt8(b & 0xFF);
}
}
/**
* Читает ровно N байт без метаданных.
* Вызывающий код сам должен знать длину.
*/
public byte[] readBytes(int length) {
if (length < 0) {
throw new IllegalArgumentException("length < 0");
}
if (length == 0) {
return new byte[0];
}
ensureReadableBits((long) length * 8L);
byte[] out = new byte[length];
if (isReadByteAligned()) {
int byteIndex = readBitPos >>> 3;
System.arraycopy(buffer, byteIndex, out, 0, length);
readBitPos += length << 3;
return out;
}
for (int i = 0; i < length; i++) {
out[i] = (byte) readUInt8();
}
return out;
}
// ---------- string (raw UTF-8, без префикса длины) ----------
/**
* Пишет строку как raw UTF-8 bytes.
* Вызывающий код отвечает за то, как это разбирается на другом конце.
*/
public void writeString(String value) {
if (value == null || value.isEmpty()) {
return;
}
byte[] utf8 = value.getBytes(StandardCharsets.UTF_8);
writeBytes(utf8);
}
/**
* Читает ровно N байт и интерпретирует как UTF-8 строку.
*/
public String readString(int length) {
byte[] bytes = readBytes(length);
return new String(bytes, StandardCharsets.UTF_8);
}
// ---------- internals ----------
private boolean isWriteByteAligned() {
return (writeBitPos & 7) == 0;
}
private boolean isReadByteAligned() {
return (readBitPos & 7) == 0;
}
private long remainingBits() {
return (long) writeBitPos - readBitPos;
}
private void ensureReadableBits(long bits) {
if (bits < 0) {
throw new IllegalArgumentException("bits < 0");
}
if (remainingBits() < bits) {
throw new IllegalStateException(
String.format("Not enough bits: need %d, remaining %d", bits, remainingBits())
);
}
}
private void writeBits(long value, int bits) {
if (bits <= 0) {
return;
}
ensureCapacityBits(bits);
for (int i = bits - 1; i >= 0; i--) {
int bit = (int) ((value >>> i) & 1L);
int byteIndex = writeBitPos >>> 3;
int bitShift = 7 - (writeBitPos & 7);
if (bit == 1) {
buffer[byteIndex] = (byte) (buffer[byteIndex] | (1 << bitShift));
} else {
buffer[byteIndex] = (byte) (buffer[byteIndex] & ~(1 << bitShift));
}
writeBitPos++;
}
}
private long readBits(int bits) {
if (bits <= 0) {
return 0L;
}
ensureReadableBits(bits);
long value = 0L;
for (int i = 0; i < bits; i++) {
int bit = (buffer[readBitPos >>> 3] >>> (7 - (readBitPos & 7))) & 1;
value = (value << 1) | bit;
readBitPos++;
}
return value;
}
private void ensureCapacityBits(long bitsToWrite) {
if (bitsToWrite <= 0) {
return;
}
long requiredBits = (long) writeBitPos + bitsToWrite;
if (requiredBits < 0) {
throw new IllegalStateException("Bit position overflow");
}
long requiredBytesLong = (requiredBits + 7) >>> 3;
if (requiredBytesLong > Integer.MAX_VALUE) {
throw new IllegalStateException("Buffer too large");
}
ensureCapacity((int) requiredBytesLong);
}
private void ensureCapacity(int requiredSize) {
if (requiredSize <= buffer.length) {
return;
}
int newSize = (buffer.length == 0) ? DEFAULT_CAPACITY : buffer.length;
while (newSize < requiredSize) {
if (newSize > (Integer.MAX_VALUE >>> 1)) {
newSize = requiredSize;
break;
}
newSize <<= 1;
}
buffer = Arrays.copyOf(buffer, newSize);
}
}

View File

@@ -8,7 +8,7 @@ import io.orprotocol.BaseFailures;
import io.orprotocol.ProtocolException; import io.orprotocol.ProtocolException;
import io.orprotocol.Server; import io.orprotocol.Server;
import io.orprotocol.ServerFailures; import io.orprotocol.ServerFailures;
import io.orprotocol.Stream; import io.orprotocol.frame.FrameEncoder;
import io.orprotocol.index.ClientIndexer; import io.orprotocol.index.ClientIndexer;
import io.orprotocol.packet.Packet; import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketManager; import io.orprotocol.packet.PacketManager;
@@ -41,6 +41,11 @@ public class Client {
private ClientIndexer clientIndexer; private ClientIndexer clientIndexer;
private PacketManager packetManager; private PacketManager packetManager;
/**
* Версия, которую поддерживает клиент
*/
private int version;
/** /**
* Создает нового клиента с указанным сокетом. * Создает нового клиента с указанным сокетом.
* Этот метод используется внутри протокола для управления подключениями клиентов. * Этот метод используется внутри протокола для управления подключениями клиентов.
@@ -57,6 +62,23 @@ public class Client {
this.packetManager = server.getPacketManager(); this.packetManager = server.getPacketManager();
} }
/**
* Устанавливает версию клиента
* @internal
* @param version
*/
public void setVersion(int version) {
this.version = version;
}
/**
* Получает версию клиента
* @return
*/
public int getVersion() {
return this.version;
}
/** /**
* Проверяет жив ли клиент на основе времени последнего heartbeat. * Проверяет жив ли клиент на основе времени последнего heartbeat.
* Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным. * Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным.
@@ -228,10 +250,15 @@ public class Client {
} }
packet.packetId = packetId; packet.packetId = packetId;
/** /**
* Записываем пакет в поток и отправляем его через сокет. * Кодируем пакет в байты для отправки клиенту, c версии сервера (latest) до версии клиента,
* для дальнейшей отправки клиенту
*/ */
Stream stream = packet.write(); FrameEncoder frameEncoder = new FrameEncoder(packet, this, this.packetManager);
this.socket.send(stream.getBuffer()); byte[] encodedData = frameEncoder.encode();
/**
* Отправляем закодированные данные клиенту (уже закодированы под его версию)
*/
this.socket.send(encodedData);
} }
/** /**

View File

@@ -0,0 +1,31 @@
package io.orprotocol.codec;
import io.orprotocol.ProtocolException;
import io.orprotocol.buffer.Buffer;
import io.orprotocol.packet.Packet;
/**
* Интерфейс для кодирования/декодирования пакетов
* именно этот интерфейс под капотом отвечает за версионирование
*/
public interface Codec<T extends Packet> {
/**
* Декодирует данные data от клиента у которого version версия,
* возвращает последнюю версию пакета для дальнейшей передачи в Executor
* @param data данные
* @param version версия клиента, от которого пришли данные
* @return декодированный пакет
* @throws ProtocolException если данные не могут быть декодированы в пакет или версия не поддерживается
*/
T decode(Buffer data, int version) throws ProtocolException;
/**
* Кодирует данные из последней версии в версию version для отправки клиенту,
* у клиента при этом должна быть версия version
* @param packet пакет для кодирования
* @param version версия, в которую нужно закодировать пакет
* @return закодированные данные
* @throws ProtocolException если пакет не может быть закодирован или версия не поддерживается
*/
byte[] encode(T packet, int version) throws ProtocolException;
}

View File

@@ -0,0 +1,40 @@
package io.orprotocol.frame;
import io.orprotocol.ProtocolException;
import io.orprotocol.buffer.Buffer;
import io.orprotocol.client.Client;
import io.orprotocol.codec.Codec;
import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketManager;
/**
* Декодирует пакет(frame) пришедший от клиента
* с его версии до версии сервера (latest), для дальнейшей передачи в Executor
*/
public class FrameDecoder {
private final byte[] frame;
private final Client sender;
private final PacketManager packetManager;
public FrameDecoder(byte[] frame, Client sender, PacketManager packetManager) {
this.frame = frame;
this.sender = sender;
this.packetManager = packetManager;
}
public Packet decode() throws ProtocolException {
int senderVersion = this.sender.getVersion();
Buffer buf = Buffer.wrap(this.frame);
int packetId = buf.readUInt16();
Codec<? extends Packet> codec = this.packetManager.getCodec(packetId);
Packet packet = codec.decode(buf, senderVersion);
packet.packetId = packetId;
/**
* Буфер уже смещен на 2 байта, так что декодирование в кодеке начинается с данных пакета, а не с ID
*/
return codec.decode(buf, senderVersion);
}
}

View File

@@ -0,0 +1,32 @@
package io.orprotocol.frame;
import io.orprotocol.ProtocolException;
import io.orprotocol.client.Client;
import io.orprotocol.codec.Codec;
import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketManager;
/**
* Кодирует пакет в байты для отправки клиенту, c версии сервера (latest) до версии клиента,
* для дальнейшей отправки клиенту
*/
public class FrameEncoder {
private final Packet packet;
private final Client recipient;
private final PacketManager packetManager;
public FrameEncoder(Packet packet, Client recipient, PacketManager packetManager) {
this.packet = packet;
this.recipient = recipient;
this.packetManager = packetManager;
}
public byte[] encode() throws ProtocolException {
int recipientVersion = this.recipient.getVersion();
@SuppressWarnings("unchecked")
Codec<Packet> codec = (Codec<Packet>) this.packetManager.getCodec(this.packet.packetId);
return codec.encode(this.packet, recipientVersion);
}
}

View File

@@ -73,9 +73,7 @@ public class ThreadLocker {
String fieldValue = (String) field.get(packet); String fieldValue = (String) field.get(packet);
String lockValue = packet.getClass().getName() + "_" + fieldValue; String lockValue = packet.getClass().getName() + "_" + fieldValue;
locks.remove(lockValue); locks.remove(lockValue);
}catch(Exception e) { }catch(Exception e) { }
// Игнорируем ошибки при разблокировке
}
} }
} }

View File

@@ -1,7 +1,5 @@
package io.orprotocol.packet; package io.orprotocol.packet;
import io.orprotocol.Stream;
/** /**
* Представляет собой абстрактный класс для всех пакетов, используемых в протоколе. * Представляет собой абстрактный класс для всех пакетов, используемых в протоколе.
*/ */
@@ -10,18 +8,4 @@ public abstract class Packet {
public int packetId; public int packetId;
public PacketManager packetManager; public PacketManager packetManager;
/**
* Читает данные пакета из потока. Используется при получении
*
* @param stream Поток с данными пакета.
*/
public abstract void read(Stream stream);
/**
* Записывает данные пакета в поток. Исползуется при отправке
*
* @return Поток с записанными данными пакета.
*/
public abstract Stream write();
} }

View File

@@ -1,52 +0,0 @@
package io.orprotocol.packet;
import io.orprotocol.ProtocolException;
import io.orprotocol.Stream;
/**
* Фабрика для создания пакетов из байтового массива. Используется для создания пакетов при получении данных от клиента,
* а так же может быть использована приложением
*/
public class PacketFactory {
private byte[] bytes;
private PacketManager packetManager;
/**
* Создать фабрику для создания пакетов из байтового массива
* @param bytes байтовый массив для создания пакета
* @param packetManager менеджер пакетов для получения класса пакета по его id
*/
public PacketFactory(byte[] bytes, PacketManager packetManager) {
this.bytes = bytes;
this.packetManager = packetManager;
}
/**
* Создает пакет из массива байт, сериализует и возвращает его. Если пакет с таким id не поддерживается, выбрасывает ProtocolException
* @return созданный пакет
* @throws ProtocolException
*/
public Packet createPacket() throws ProtocolException {
Stream stream = new Stream(this.bytes);
int packetId = stream.readInt16();
if(!this.packetManager.hasPacketSupported(packetId)){
throw new ProtocolException("Unsupported packet with id " + packetId);
}
Class<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
try {
Packet packet = packetClass.getConstructor().newInstance();
packet.packetId = packetId;
packet.read(stream);
return packet;
} catch (Exception e) {
throw new ProtocolException("Failed to create packet with id " + packetId);
}
}
public int getPacketId() {
Stream stream = new Stream(this.bytes);
return stream.readInt16();
}
}

View File

@@ -2,7 +2,7 @@ package io.orprotocol.packet;
import java.util.HashMap; import java.util.HashMap;
import io.orprotocol.ProtocolException; import io.orprotocol.codec.Codec;
/** /**
* Менеджер сетевых пакетов и их обработчиков. * Менеджер сетевых пакетов и их обработчиков.
@@ -11,10 +11,17 @@ public class PacketManager {
private HashMap<Integer, Class<? extends Packet>> packets; private HashMap<Integer, Class<? extends Packet>> packets;
private HashMap<Integer, PacketExecutor<?>> executors; private HashMap<Integer, PacketExecutor<?>> executors;
private HashMap<Integer, Codec<? extends Packet>> codecs;
/**
* Ключ - класс пакета, значение - ID пакета, используется для получения ID по классу
*/
private HashMap<Class<? extends Packet>, Integer> packetIds;
public PacketManager() { public PacketManager() {
this.packets = new HashMap<>(); this.packets = new HashMap<>();
this.executors = new HashMap<>(); this.executors = new HashMap<>();
this.codecs = new HashMap<>();
this.packetIds = new HashMap<>();
} }
/** /**
@@ -24,6 +31,7 @@ public class PacketManager {
*/ */
public void registerPacket(int packetId, Class<? extends Packet> packet) { public void registerPacket(int packetId, Class<? extends Packet> packet) {
this.packets.put(packetId, packet); this.packets.put(packetId, packet);
this.packetIds.put(packet, packetId);
} }
/** /**
@@ -35,6 +43,19 @@ public class PacketManager {
return this.executors.containsKey(packetId); return this.executors.containsKey(packetId);
} }
/**
* Проверяет, зарегистрирован ли обработчик для пакета с указанным классом.
* @param packet Экземпляр пакета
* @return true, если обработчик зарегистрирован, иначе false.
*/
public boolean hasExecutorDelegated(Packet packet) {
Integer packetId = this.getPacketIdByClass(packet.getClass());
if(packetId == null){
return false;
}
return true;
}
/** /**
* Проверяет, поддерживается ли пакет с указанным ID. * Проверяет, поддерживается ли пакет с указанным ID.
* @param packetId ID пакета * @param packetId ID пакета
@@ -58,12 +79,7 @@ public class PacketManager {
* @return ID пакета * @return ID пакета
*/ */
public Integer getPacketIdByClass(Class<? extends Packet> packetClass) { public Integer getPacketIdByClass(Class<? extends Packet> packetClass) {
for (var entry : this.packets.entrySet()) { return this.packetIds.get(packetClass);
if (entry.getValue().equals(packetClass)) {
return entry.getKey();
}
}
return null;
} }
public Class<? extends Packet> getPacketClass(int packetId) { public Class<? extends Packet> getPacketClass(int packetId) {
@@ -82,6 +98,35 @@ public class PacketManager {
this.executors.put(packetId, executor); this.executors.put(packetId, executor);
} }
public void registerExecutor(Class<? extends Packet> packetClass, PacketExecutor<?> executor) {
Integer packetId = this.getPacketIdByClass(packetClass);
if(packetId == null) {
throw new IllegalArgumentException("Packet class " + packetClass.getName() + " is not registered with a packet ID.");
}
this.registerExecutor(packetId, executor);
}
/**
* Регистрирует кодек для пакета с указанным ID.
* @param packetId ID пакета
* @param codec Кодек для пакета
*/
public void registerCodec(int packetId, Codec<? extends Packet> codec) {
if (this.codecs == null) {
this.codecs = new HashMap<>();
}
this.codecs.put(packetId, codec);
}
/**
* Получает кодек для пакета с указанным ID.
* @param packetId ID пакета
* @return Кодек для пакета, или null если кодек не найден.
*/
public Codec<? extends Packet> getCodec(int packetId) {
return this.codecs.get(packetId);
}
/** /**
* Возвращает общее количество зарегистрированных обработчиков пакетов. * Возвращает общее количество зарегистрированных обработчиков пакетов.
* @return Количество обработчиков пакетов. * @return Количество обработчиков пакетов.
@@ -98,24 +143,4 @@ public class PacketManager {
return this.packets.size(); return this.packets.size();
} }
/**
* Создает пакет из массива байт, сериализует и возвращает его. Если пакет с таким id не поддерживается, выбрасывает ProtocolException
* @param bytes байтовый массив для создания пакета
* @return созданный пакет
* @throws ProtocolException если пакет с таким id не поддерживается или произошла ошибка при создании пакета
*/
public Packet createPacket(byte[] bytes) throws ProtocolException {
PacketFactory packetFactory = new PacketFactory(bytes, this);
return packetFactory.createPacket();
}
/**
* Создает фабрику для создания пакетов из байтового массива
* @param bytes байтовый массив для создания пакета
* @return фабрика для создания пакетов из байтового массива
*/
public PacketFactory getPacketFactory(byte[] bytes) {
return new PacketFactory(bytes, this);
}
} }

View File

@@ -1,16 +0,0 @@
package io.orprotocol.serializer;
import io.orprotocol.Stream;
public interface Serializable {
/**
* Сериализует объект из потока данных. Используется при получении данных.
* @param stream Поток данных для сериализации
*/
void readFromStream(Stream stream);
/**
* Десериализует обьект в поток данных. Используется при отправке данных.
* @return Десериализованный объект
*/
void writeToStream(Stream stream);
}

View File

@@ -0,0 +1,130 @@
package io.orprotocol.tlv;
import java.util.List;
/**
* Интерфейс для чтения данных в формате TLV (Type-Length-Value).
* Предоставляет методы для извлечения значений различных типов данных по идентификатору поля.
*/
public interface IoTlvReader {
/**
* Получает строковое значение по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return строковое значение
*/
public String getString(int fieldId);
/**
* Получает список строковых значений по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return список строк
*/
public List<String> getStringList(int fieldId);
/**
* Получает массив байтов по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return массив байтов
*/
public byte[] getBytes(int fieldId);
/**
* Получает список массивов байтов по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return список массивов байтов
*/
public List<byte[]> getBytesList(int fieldId);
/**
* Получает значение 8-битного целого числа со знаком по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение int8
*/
public byte getInt8(int fieldId);
/**
* Получает значение 8-битного целого числа без знака по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение uint8
*/
public short getUInt8(int fieldId);
/**
* Получает значение 16-битного целого числа со знаком по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение int16
*/
public short getInt16(int fieldId);
/**
* Получает значение 16-битного целого числа без знака по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение uint16
*/
public int getUInt16(int fieldId);
/**
* Получает значение 32-битного целого числа со знаком по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение int32
*/
public int getInt32(int fieldId);
/**
* Получает значение 32-битного целого числа без знака по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение uint32
*/
public long getUInt32(int fieldId);
/**
* Получает значение 64-битного целого числа со знаком по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение int64
*/
public long getInt64(int fieldId);
/**
* Получает значение 64-битного целого числа без знака по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение uint64
*/
public long getUInt64(int fieldId);
/**
* Получает значение 32-битного числа с плавающей точкой по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение float32
*/
public float getFloat32(int fieldId);
/**
* Получает значение 64-битного числа с плавающей точкой по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return значение float64
*/
public double getFloat64(int fieldId);
/**
* Получает логическое значение (true/false) по идентификатору поля.
*
* @param fieldId идентификатор поля
* @return логическое значение
*/
public boolean getBoolean(int fieldId);
}

View File

@@ -0,0 +1,130 @@
package io.orprotocol.tlv;
import java.util.Collection;
/**
* Интерфейс для записи TLV (Tag-Length-Value) данных.
* Предоставляет методы для сериализации различных типов данных.
*/
public interface IoTlvWriter {
/**
* Записывает строковое значение.
*
* @param fieldId идентификатор поля
* @param value строковое значение для записи
*/
public void writeString(int fieldId, String value);
/**
* Записывает коллекцию строк.
*
* @param fieldId идентификатор поля
* @param value коллекция строк для записи
*/
public void writeStringList(int fieldId, Collection<String> value);
/**
* Записывает массив байтов.
*
* @param fieldId идентификатор поля
* @param value массив байтов для записи
*/
public void writeBytes(int fieldId, byte[] value);
/**
* Записывает коллекцию массивов байтов.
*
* @param fieldId идентификатор поля
* @param value коллекция массивов байтов для записи
*/
public void writeBytesList(int fieldId, Collection<byte[]> value);
/**
* Записывает целое число (8-бит со знаком).
*
* @param fieldId идентификатор поля
* @param value значение от -128 до 127
*/
public void writeInt8(int fieldId, byte value);
/**
* Записывает целое число (8-бит без знака).
*
* @param fieldId идентификатор поля
* @param value значение от 0 до 255
*/
public void writeUInt8(int fieldId, short value);
/**
* Записывает целое число (16-бит со знаком).
*
* @param fieldId идентификатор поля
* @param value значение от -32768 до 32767
*/
public void writeInt16(int fieldId, short value);
/**
* Записывает целое число (16-бит без знака).
*
* @param fieldId идентификатор поля
* @param value значение от 0 до 65535
*/
public void writeUInt16(int fieldId, int value);
/**
* Записывает целое число (32-бит со знаком).
*
* @param fieldId идентификатор поля
* @param value значение от -2147483648 до 2147483647
*/
public void writeInt32(int fieldId, int value);
/**
* Записывает целое число (32-бит без знака).
*
* @param fieldId идентификатор поля
* @param value значение от 0 до 4294967295
*/
public void writeUInt32(int fieldId, long value);
/**
* Записывает целое число (64-бит со знаком).
*
* @param fieldId идентификатор поля
* @param value значение от -9223372036854775808 до 9223372036854775807
*/
public void writeInt64(int fieldId, long value);
/**
* Записывает целое число (64-бит без знака).
*
* @param fieldId идентификатор поля
* @param value значение от 0 до 18446744073709551615
*/
public void writeUInt64(int fieldId, long value);
/**
* Записывает число с плавающей точкой (32-бит).
*
* @param fieldId идентификатор поля
* @param value значение типа float
*/
public void writeFloat32(int fieldId, float value);
/**
* Записывает число с плавающей точкой (64-бит).
*
* @param fieldId идентификатор поля
* @param value значение типа double
*/
public void writeFloat64(int fieldId, double value);
/**
* Записывает логическое значение.
*
* @param fieldId идентификатор поля
* @param value логическое значение (true или false)
*/
public void writeBoolean(int fieldId, boolean value);
}

View File

@@ -0,0 +1,24 @@
package io.orprotocol.tlv;
/**
* TLV Field
*/
public class TlvField {
private final int wireType;
private final Object value;
public TlvField(int wireType, Object value) {
this.wireType = wireType;
this.value = value;
}
public int getWireType() {
return wireType;
}
public Object getValue() {
return value;
}
}

View File

@@ -0,0 +1,21 @@
package io.orprotocol.tlv;
public class TlvHeader {
private final int fieldId;
private final int wireType;
public TlvHeader(int fieldId, int wireType) {
this.fieldId = fieldId;
this.wireType = wireType;
}
public int getFieldId() {
return fieldId;
}
public int getWireType() {
return wireType;
}
}

View File

@@ -0,0 +1,263 @@
package io.orprotocol.tlv.read;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.orprotocol.ProtocolException;
import io.orprotocol.buffer.Buffer;
import io.orprotocol.tlv.IoTlvReader;
import io.orprotocol.tlv.TlvField;
import io.orprotocol.tlv.TlvHeader;
import io.orprotocol.wire.WireType;
public class TlvReader implements IoTlvReader {
private final Map<Integer, List<TlvField>> fields = new HashMap<>();
public TlvReader(byte[] payload) throws ProtocolException {
this.read(payload);
}
private void read(byte[] payload) throws ProtocolException {
Buffer buf = Buffer.wrap(payload);
while(buf.hasRemaining()) {
TlvHeader header = this.readHeader(buf);
TlvField field = switch(header.getWireType()) {
case WireType.INT8 -> new TlvField(WireType.INT8, buf.readInt8());
case WireType.UINT8 -> new TlvField(WireType.UINT8, buf.readUInt8());
case WireType.INT16 -> new TlvField(WireType.INT16, buf.readInt16());
case WireType.UINT16 -> new TlvField(WireType.UINT16, buf.readUInt16());
case WireType.INT32 -> new TlvField(WireType.INT32, buf.readInt32());
case WireType.UINT32 -> new TlvField(WireType.UINT32, buf.readUInt32());
case WireType.INT64 -> new TlvField(WireType.INT64, buf.readInt64());
case WireType.UINT64 -> new TlvField(WireType.UINT64, buf.readUInt64());
case WireType.FLOAT32 -> new TlvField(WireType.FLOAT32, buf.readFloat());
case WireType.FLOAT64 -> new TlvField(WireType.FLOAT64, buf.readDouble());
case WireType.BOOL -> new TlvField(WireType.BOOL, buf.readBoolean());
case WireType.LENGTH_DELIMITED -> {
/**
* Строки, байты и другие данные которые могут содержать произвольную длинну
* в том числе другие TLV теги внутри себя, кодируются как length-delimited: сначала пишется длина данных в виде uint16, а затем сами данные
*/
int length = buf.readUInt16();
byte[] data = buf.readBytes(length);
yield new TlvField(WireType.LENGTH_DELIMITED, data);
}
default -> throw new ProtocolException("tlv header is bad");
};
fields.computeIfAbsent(header.getFieldId(), k -> new ArrayList<>()).add(field);
}
}
private TlvHeader readHeader(Buffer buf) {
int fieldId = buf.readUInt8();
int wireType = buf.readUInt8();
return new TlvHeader(fieldId, wireType);
}
public List<TlvField> getField(int fieldId) {
return fields.get(fieldId);
}
public boolean hasField(int fieldId) {
return fields.containsKey(fieldId);
}
public String getString(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
return null;
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.LENGTH_DELIMITED) {
throw new IllegalStateException("field is not length-delimited");
}
return new String((byte[]) field.getValue(), StandardCharsets.UTF_8);
}
public List<String> getStringList(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
return null;
}
List<String> result = new ArrayList<>();
for(TlvField field : fields) {
if(field.getWireType() != WireType.LENGTH_DELIMITED) {
throw new IllegalStateException("field is not length-delimited");
}
result.add(new String((byte[]) field.getValue(), StandardCharsets.UTF_8));
}
return result;
}
public byte[] getBytes(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
return null;
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.LENGTH_DELIMITED) {
throw new IllegalStateException("field is not length-delimited");
}
return (byte[]) field.getValue();
}
public List<byte[]> getBytesList(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
return null;
}
List<byte[]> result = new ArrayList<>();
for(TlvField field : fields) {
if(field.getWireType() != WireType.LENGTH_DELIMITED) {
throw new IllegalStateException("field is not length-delimited");
}
result.add((byte[]) field.getValue());
}
return result;
}
public byte getInt8(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.INT8) {
throw new IllegalStateException("field is not int8");
}
return (byte) field.getValue();
}
public short getUInt8(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.UINT8) {
throw new IllegalStateException("field is not uint8");
}
return (short) field.getValue();
}
public short getInt16(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.INT16) {
throw new IllegalStateException("field is not int16");
}
return (short) field.getValue();
}
public int getUInt16(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.UINT16) {
throw new IllegalStateException("field is not uint16");
}
return (int) field.getValue();
}
public int getInt32(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.INT32) {
throw new IllegalStateException("field is not int32");
}
return (int) field.getValue();
}
public long getUInt32(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.UINT32) {
throw new IllegalStateException("field is not uint32");
}
return (long) field.getValue();
}
public long getInt64(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.INT64) {
throw new IllegalStateException("field is not int64");
}
return (long) field.getValue();
}
public long getUInt64(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.UINT64) {
throw new IllegalStateException("field is not uint64");
}
return (long) field.getValue();
}
public boolean getBoolean(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.BOOL) {
throw new IllegalStateException("field is not bool");
}
return (boolean) field.getValue();
}
public float getFloat32(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.FLOAT32) {
throw new IllegalStateException("field is not float32");
}
return (float) field.getValue();
}
public double getFloat64(int fieldId) {
List<TlvField> fields = this.getField(fieldId);
if(fields == null || fields.isEmpty()) {
throw new IllegalStateException("field is not found");
}
TlvField field = fields.get(0);
if(field.getWireType() != WireType.FLOAT64) {
throw new IllegalStateException("field is not float64");
}
return (double) field.getValue();
}
public List<TlvField> getFields(int fieldId) {
return fields.get(fieldId);
}
public Map<Integer, List<TlvField>> getAllFields() {
return fields;
}
}

View File

@@ -0,0 +1,136 @@
package io.orprotocol.tlv.write;
import java.util.Collection;
import io.orprotocol.buffer.Buffer;
import io.orprotocol.tlv.IoTlvWriter;
import io.orprotocol.tlv.TlvHeader;
import io.orprotocol.wire.WireType;
public class TlvWriter implements IoTlvWriter {
private Buffer buffer;
public TlvWriter() {
this.buffer = new Buffer();
}
public TlvWriter(Buffer buffer) {
this.buffer = buffer;
}
public TlvWriter(byte[] data) {
this.buffer = Buffer.wrap(data);
}
public Buffer getBuffer() {
return buffer;
}
public byte[] toByteArray() {
return buffer.toByteArray();
}
public void writeBoolean(int fieldId, boolean value) {
TlvHeader header = new TlvHeader(fieldId, WireType.BOOL);
this.writeHeader(header);
this.buffer.writeBoolean(value);
}
public void writeInt8(int fieldId, byte value) {
TlvHeader header = new TlvHeader(fieldId, WireType.INT8);
this.writeHeader(header);
this.buffer.writeInt8(value);
}
public void writeUInt8(int fieldId, short value) {
TlvHeader header = new TlvHeader(fieldId, WireType.UINT8);
this.writeHeader(header);
this.buffer.writeUInt8(value);
}
public void writeInt16(int fieldId, short value) {
TlvHeader header = new TlvHeader(fieldId, WireType.INT16);
this.writeHeader(header);
this.buffer.writeInt16(value);
}
public void writeUInt16(int fieldId, int value) {
TlvHeader header = new TlvHeader(fieldId, WireType.UINT16);
this.writeHeader(header);
this.buffer.writeUInt16(value);
}
public void writeInt32(int fieldId, int value) {
TlvHeader header = new TlvHeader(fieldId, WireType.INT32);
this.writeHeader(header);
this.buffer.writeInt32(value);
}
public void writeUInt32(int fieldId, long value) {
TlvHeader header = new TlvHeader(fieldId, WireType.UINT32);
this.writeHeader(header);
this.buffer.writeUInt32(value);
}
public void writeInt64(int fieldId, long value) {
TlvHeader header = new TlvHeader(fieldId, WireType.INT64);
this.writeHeader(header);
this.buffer.writeInt64(value);
}
public void writeUInt64(int fieldId, long value) {
TlvHeader header = new TlvHeader(fieldId, WireType.UINT64);
this.writeHeader(header);
this.buffer.writeUInt64(value);
}
public void writeBytes(int fieldId, byte[] data) {
TlvHeader header = new TlvHeader(fieldId, WireType.LENGTH_DELIMITED);
this.writeHeader(header);
this.buffer.writeUInt16(data.length);
this.buffer.writeBytes(data);
}
public void writeFloat32(int fieldId, float value) {
TlvHeader header = new TlvHeader(fieldId, WireType.FLOAT32);
this.writeHeader(header);
this.buffer.writeFloat(value);
}
public void writeFloat64(int fieldId, double value) {
TlvHeader header = new TlvHeader(fieldId, WireType.FLOAT64);
this.writeHeader(header);
this.buffer.writeDouble(value);
}
public void writeBytesList(int fieldId, Collection<byte[]> dataList) {
for (byte[] data : dataList) {
this.writeBytes(fieldId, data);
}
}
/**
* Записывает список строк
* @param fieldId ID поля
* @param values список строк для записи
*/
public void writeStringList(int fieldId, Collection<String> values) {
for (String value : values) {
this.writeString(fieldId, value);
}
}
public void writeString(int fieldId, String value) {
TlvHeader header = new TlvHeader(fieldId, WireType.LENGTH_DELIMITED);
this.writeHeader(header);
this.buffer.writeUInt16(value.getBytes().length);
this.buffer.writeBytes(value.getBytes());
}
private void writeHeader(TlvHeader header) {
this.buffer.writeUInt8(header.getFieldId());
this.buffer.writeUInt8(header.getWireType());
}
}

View File

@@ -0,0 +1,23 @@
package io.orprotocol.wire;
public class WireType {
public static final int BOOL = 0;
public static final int INT8 = 1;
public static final int INT16 = 2;
public static final int INT32 = 3;
public static final int INT64 = 4;
public static final int UINT8 = 5;
public static final int UINT16 = 6;
public static final int UINT32 = 7;
public static final int UINT64 = 8;
public static final int FLOAT32 = 9;
public static final int FLOAT64 = 10;
/**
* int32 length + данные
* string, byte[]
*/
public static final int LENGTH_DELIMITED = 11;
private WireType() {}
}