Обновленеи протокола, убран legacy код, переход на более практичную схему TLV
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
package io.orprotocol;
|
||||
|
||||
public class Context extends Object {}
|
||||
@@ -13,11 +13,11 @@ import org.java_websocket.handshake.ClientHandshake;
|
||||
import org.java_websocket.server.WebSocketServer;
|
||||
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.frame.FrameDecoder;
|
||||
import io.orprotocol.index.ClientIndexer;
|
||||
import io.orprotocol.lock.ThreadLocker;
|
||||
import io.orprotocol.packet.Packet;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
import io.orprotocol.packet.PacketFactory;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
|
||||
public class Server extends WebSocketServer {
|
||||
@@ -104,9 +104,9 @@ public class Server extends WebSocketServer {
|
||||
/**
|
||||
* Создаем пакет из полученных байтов.
|
||||
*/
|
||||
PacketFactory packetFactory = new PacketFactory(bytes, this.packetManager);
|
||||
Packet packet = packetFactory.createPacket();
|
||||
int packetId = packetFactory.getPacketId();
|
||||
FrameDecoder frameDecoder = new FrameDecoder(bytes, client, this.packetManager);
|
||||
Packet packet = frameDecoder.decode();
|
||||
int packetId = packet.packetId;
|
||||
/**
|
||||
* Получаем обработчик пакета и вызываем его метод обработки.
|
||||
*
|
||||
@@ -127,7 +127,7 @@ public class Server extends WebSocketServer {
|
||||
if(listener != null && !listener.onPacketReceived(this, client, packet)) {
|
||||
/**
|
||||
* Если слушатель сервера вернул false, пакет не обрабатываем.
|
||||
*/
|
||||
*/
|
||||
return;
|
||||
}
|
||||
/**
|
||||
@@ -162,6 +162,13 @@ public class Server extends WebSocketServer {
|
||||
* Если клиент не отправляет heartbeat в указанный интервал, его можно отключить.
|
||||
*/
|
||||
Client client = new Client(socket, this.settings.heartbeatInterval, this);
|
||||
/**
|
||||
* Устанавливаем версию клиента
|
||||
*/
|
||||
client.setVersion(this.extractVersionFromHandshake(handshake));
|
||||
/**
|
||||
* Получаем адрес клиента
|
||||
*/
|
||||
String ipAddress = handshake.getFieldValue("X-Forwarded-For");
|
||||
if (ipAddress == null || ipAddress.isEmpty()) {
|
||||
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
|
||||
public void onStart() {
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
410
src/main/java/io/orprotocol/buffer/Buffer.java
Normal file
410
src/main/java/io/orprotocol/buffer/Buffer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import io.orprotocol.BaseFailures;
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.Server;
|
||||
import io.orprotocol.ServerFailures;
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.frame.FrameEncoder;
|
||||
import io.orprotocol.index.ClientIndexer;
|
||||
import io.orprotocol.packet.Packet;
|
||||
import io.orprotocol.packet.PacketManager;
|
||||
@@ -41,6 +41,11 @@ public class Client {
|
||||
private ClientIndexer clientIndexer;
|
||||
private PacketManager packetManager;
|
||||
|
||||
/**
|
||||
* Версия, которую поддерживает клиент
|
||||
*/
|
||||
private int version;
|
||||
|
||||
/**
|
||||
* Создает нового клиента с указанным сокетом.
|
||||
* Этот метод используется внутри протокола для управления подключениями клиентов.
|
||||
@@ -57,6 +62,23 @@ public class Client {
|
||||
this.packetManager = server.getPacketManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает версию клиента
|
||||
* @internal
|
||||
* @param version
|
||||
*/
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает версию клиента
|
||||
* @return
|
||||
*/
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет жив ли клиент на основе времени последнего heartbeat.
|
||||
* Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным.
|
||||
@@ -228,10 +250,15 @@ public class Client {
|
||||
}
|
||||
packet.packetId = packetId;
|
||||
/**
|
||||
* Записываем пакет в поток и отправляем его через сокет.
|
||||
* Кодируем пакет в байты для отправки клиенту, c версии сервера (latest) до версии клиента,
|
||||
* для дальнейшей отправки клиенту
|
||||
*/
|
||||
Stream stream = packet.write();
|
||||
this.socket.send(stream.getBuffer());
|
||||
FrameEncoder frameEncoder = new FrameEncoder(packet, this, this.packetManager);
|
||||
byte[] encodedData = frameEncoder.encode();
|
||||
/**
|
||||
* Отправляем закодированные данные клиенту (уже закодированы под его версию)
|
||||
*/
|
||||
this.socket.send(encodedData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
31
src/main/java/io/orprotocol/codec/Codec.java
Normal file
31
src/main/java/io/orprotocol/codec/Codec.java
Normal 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;
|
||||
}
|
||||
40
src/main/java/io/orprotocol/frame/FrameDecoder.java
Normal file
40
src/main/java/io/orprotocol/frame/FrameDecoder.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
32
src/main/java/io/orprotocol/frame/FrameEncoder.java
Normal file
32
src/main/java/io/orprotocol/frame/FrameEncoder.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,9 +73,7 @@ public class ThreadLocker {
|
||||
String fieldValue = (String) field.get(packet);
|
||||
String lockValue = packet.getClass().getName() + "_" + fieldValue;
|
||||
locks.remove(lockValue);
|
||||
}catch(Exception e) {
|
||||
// Игнорируем ошибки при разблокировке
|
||||
}
|
||||
}catch(Exception e) { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.orprotocol.packet;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
|
||||
/**
|
||||
* Представляет собой абстрактный класс для всех пакетов, используемых в протоколе.
|
||||
*/
|
||||
@@ -10,18 +8,4 @@ public abstract class Packet {
|
||||
public int packetId;
|
||||
public PacketManager packetManager;
|
||||
|
||||
/**
|
||||
* Читает данные пакета из потока. Используется при получении
|
||||
*
|
||||
* @param stream Поток с данными пакета.
|
||||
*/
|
||||
public abstract void read(Stream stream);
|
||||
|
||||
/**
|
||||
* Записывает данные пакета в поток. Исползуется при отправке
|
||||
*
|
||||
* @return Поток с записанными данными пакета.
|
||||
*/
|
||||
public abstract Stream write();
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package io.orprotocol.packet;
|
||||
|
||||
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, PacketExecutor<?>> executors;
|
||||
private HashMap<Integer, Codec<? extends Packet>> codecs;
|
||||
/**
|
||||
* Ключ - класс пакета, значение - ID пакета, используется для получения ID по классу
|
||||
*/
|
||||
private HashMap<Class<? extends Packet>, Integer> packetIds;
|
||||
|
||||
public PacketManager() {
|
||||
this.packets = 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) {
|
||||
this.packets.put(packetId, packet);
|
||||
this.packetIds.put(packet, packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,6 +43,19 @@ public class PacketManager {
|
||||
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.
|
||||
* @param packetId ID пакета
|
||||
@@ -58,12 +79,7 @@ public class PacketManager {
|
||||
* @return ID пакета
|
||||
*/
|
||||
public Integer getPacketIdByClass(Class<? extends Packet> packetClass) {
|
||||
for (var entry : this.packets.entrySet()) {
|
||||
if (entry.getValue().equals(packetClass)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return this.packetIds.get(packetClass);
|
||||
}
|
||||
|
||||
public Class<? extends Packet> getPacketClass(int packetId) {
|
||||
@@ -82,6 +98,35 @@ public class PacketManager {
|
||||
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 Количество обработчиков пакетов.
|
||||
@@ -98,24 +143,4 @@ public class PacketManager {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
130
src/main/java/io/orprotocol/tlv/IoTlvReader.java
Normal file
130
src/main/java/io/orprotocol/tlv/IoTlvReader.java
Normal 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);
|
||||
}
|
||||
130
src/main/java/io/orprotocol/tlv/IoTlvWriter.java
Normal file
130
src/main/java/io/orprotocol/tlv/IoTlvWriter.java
Normal 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);
|
||||
}
|
||||
24
src/main/java/io/orprotocol/tlv/TlvField.java
Normal file
24
src/main/java/io/orprotocol/tlv/TlvField.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
21
src/main/java/io/orprotocol/tlv/TlvHeader.java
Normal file
21
src/main/java/io/orprotocol/tlv/TlvHeader.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
263
src/main/java/io/orprotocol/tlv/read/TlvReader.java
Normal file
263
src/main/java/io/orprotocol/tlv/read/TlvReader.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
136
src/main/java/io/orprotocol/tlv/write/TlvWriter.java
Normal file
136
src/main/java/io/orprotocol/tlv/write/TlvWriter.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
23
src/main/java/io/orprotocol/wire/WireType.java
Normal file
23
src/main/java/io/orprotocol/wire/WireType.java
Normal 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() {}
|
||||
}
|
||||
Reference in New Issue
Block a user