Изменения протокола по вложениям и новый Steram
This commit is contained in:
@@ -4,6 +4,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import im.rosetta.packet.base.PacketBaseDialog;
|
import im.rosetta.packet.base.PacketBaseDialog;
|
||||||
import im.rosetta.packet.runtime.Attachment;
|
import im.rosetta.packet.runtime.Attachment;
|
||||||
|
import im.rosetta.packet.runtime.AttachmentEncoding;
|
||||||
|
import im.rosetta.packet.runtime.AttachmentTransport;
|
||||||
import im.rosetta.packet.runtime.AttachmentType;
|
import im.rosetta.packet.runtime.AttachmentType;
|
||||||
|
|
||||||
import io.orprotocol.Stream;
|
import io.orprotocol.Stream;
|
||||||
@@ -58,7 +60,13 @@ public class Packet6Message extends PacketBaseDialog {
|
|||||||
String preview = stream.readString();
|
String preview = stream.readString();
|
||||||
String blob = stream.readString();
|
String blob = stream.readString();
|
||||||
AttachmentType type = AttachmentType.fromCode(stream.readInt8());
|
AttachmentType type = AttachmentType.fromCode(stream.readInt8());
|
||||||
this.attachments.add(new Attachment(id, blob, type, preview));
|
String transportTag = stream.readString();
|
||||||
|
String transportServer = stream.readString();
|
||||||
|
AttachmentTransport transport = new AttachmentTransport(transportTag, transportServer);
|
||||||
|
String encodedFor = stream.readString();
|
||||||
|
String encoder = stream.readString();
|
||||||
|
AttachmentEncoding encoding = new AttachmentEncoding(encoder, encodedFor);
|
||||||
|
this.attachments.add(new Attachment(id, blob, type, preview, encoding, transport));
|
||||||
}
|
}
|
||||||
this.aesChachaKey = stream.readString();
|
this.aesChachaKey = stream.readString();
|
||||||
}
|
}
|
||||||
@@ -80,6 +88,10 @@ public class Packet6Message extends PacketBaseDialog {
|
|||||||
stream.writeString(attachment.getPreview());
|
stream.writeString(attachment.getPreview());
|
||||||
stream.writeString(attachment.getBlob());
|
stream.writeString(attachment.getBlob());
|
||||||
stream.writeInt8((byte) attachment.getType().getCode());
|
stream.writeInt8((byte) attachment.getType().getCode());
|
||||||
|
stream.writeString(attachment.getTransport().getTransportTag());
|
||||||
|
stream.writeString(attachment.getTransport().getTransportServer());
|
||||||
|
stream.writeString(attachment.getEncoding().getEncoder());
|
||||||
|
stream.writeString(attachment.getEncoding().getEncodedFor());
|
||||||
}
|
}
|
||||||
stream.writeString(this.aesChachaKey);
|
stream.writeString(this.aesChachaKey);
|
||||||
return stream;
|
return stream;
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ public class Attachment {
|
|||||||
private String blob;
|
private String blob;
|
||||||
private AttachmentType type;
|
private AttachmentType type;
|
||||||
private String preview;
|
private String preview;
|
||||||
|
private AttachmentEncoding encoding;
|
||||||
|
private AttachmentTransport transport;
|
||||||
|
|
||||||
public Attachment(String id, String blob, AttachmentType type, String preview) {
|
public Attachment(String id, String blob, AttachmentType type, String preview, AttachmentEncoding encoding, AttachmentTransport transport) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.blob = blob;
|
this.blob = blob;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.preview = preview;
|
this.preview = preview;
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.transport = transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,4 +53,20 @@ public class Attachment {
|
|||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить информацию о том, как было закодировано вложение
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public AttachmentEncoding getEncoding() {
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить информацию о том, как доставлять вложение
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public AttachmentTransport getTransport() {
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package im.rosetta.packet.runtime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс для хранения информации о том, как было закодировано вложение в сообщении
|
||||||
|
*/
|
||||||
|
public class AttachmentEncoding {
|
||||||
|
|
||||||
|
private String encoder;
|
||||||
|
private String encodedFor;
|
||||||
|
|
||||||
|
public AttachmentEncoding(String encoder, String encodedFor) {
|
||||||
|
this.encoder = encoder;
|
||||||
|
this.encodedFor = encodedFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncoder() {
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncodedFor() {
|
||||||
|
return encodedFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package im.rosetta.packet.runtime;
|
||||||
|
|
||||||
|
public class AttachmentTransport {
|
||||||
|
|
||||||
|
private String transportTag;
|
||||||
|
private String transportServer;
|
||||||
|
|
||||||
|
public AttachmentTransport(String transportTag, String transportServer) {
|
||||||
|
this.transportTag = transportTag;
|
||||||
|
this.transportServer = transportServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTransportTag() {
|
||||||
|
return transportTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTransportServer() {
|
||||||
|
return transportServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,16 +2,14 @@ package io.orprotocol;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Это класс для чтения и записи данных в виде потока байтов.
|
* Поток побитовой/побайтовой записи и чтения.
|
||||||
* Он предоставляет методы для записи и чтения различных типов данных, таких как целые числа,
|
|
||||||
* строки и массивы байтов. Поток может быть расширен динамически при записи данных, и он обеспечивает
|
|
||||||
* эффективное управление битами для оптимального использования пространства. Этот класс используется в
|
|
||||||
* протоколе для сериализации и десериализации пакетов данных, которые передаются между клиентами и сервером.
|
|
||||||
*
|
*
|
||||||
* writeInt<> - это методы для записи signed чисел (с учетом знака), которые используют дополнительный бит для хранения информации о знаке числа.
|
* Поддержка:
|
||||||
* writeUInt<> - это методы для записи unsigned чисел (без учета знака), которые используют все биты для хранения значения числа.
|
* - signed: Int8/16/32/64 (классический two's complement)
|
||||||
|
* - unsigned: UInt8/16/32/64
|
||||||
|
* - String: длина UInt32 + символы UInt16
|
||||||
|
* - byte[]: длина UInt32 + сырые байты
|
||||||
*/
|
*/
|
||||||
public class Stream {
|
public class Stream {
|
||||||
|
|
||||||
@@ -39,98 +37,27 @@ public class Stream {
|
|||||||
this.writePointer = this.stream.length << 3;
|
this.writePointer = this.stream.length << 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeByte(byte b){
|
public byte[] getBuffer() {
|
||||||
writeUInt8(b);
|
return getStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte readByte() {
|
public boolean isEmpty() {
|
||||||
return (byte) readUInt8();
|
return writePointer == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeUInt8(int value) {
|
/** Количество реально записанных байт (без резервной емкости). */
|
||||||
reserveBits(8);
|
public int length() {
|
||||||
int byteIndex = writePointer >> 3;
|
return (writePointer + 7) >> 3;
|
||||||
stream[byteIndex] = (byte) (value & 0xFF);
|
|
||||||
writePointer += 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readUInt8() {
|
// ---------- bit / boolean ----------
|
||||||
if (remainingBits() < 8L) {
|
|
||||||
throw new IllegalStateException("Not enough bits to read UInt8");
|
|
||||||
}
|
|
||||||
int byteIndex = readPointer >> 3;
|
|
||||||
int value = stream[byteIndex] & 0xFF;
|
|
||||||
readPointer += 8;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUInt16(int value) {
|
|
||||||
reserveBits(16);
|
|
||||||
writeUInt8(value >> 8);
|
|
||||||
writeUInt8(value & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readUInt16() {
|
|
||||||
int high = readUInt8();
|
|
||||||
int low = readUInt8();
|
|
||||||
return (high << 8) | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUInt32(int value) {
|
|
||||||
reserveBits(32);
|
|
||||||
writeUInt16((int) (value >> 16));
|
|
||||||
writeUInt16((int) (value & 0xFFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readUInt32() {
|
|
||||||
int high = readUInt16();
|
|
||||||
int low = readUInt16();
|
|
||||||
return (high << 16) | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUInt64(long value) {
|
|
||||||
reserveBits(64);
|
|
||||||
writeUInt32((int) (value >> 32));
|
|
||||||
writeUInt32((int) (value & 0xFFFFFFFFL));
|
|
||||||
}
|
|
||||||
|
|
||||||
public long readUInt64() {
|
|
||||||
long high = readUInt32();
|
|
||||||
long low = readUInt32();
|
|
||||||
return (high << 32) | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Записывает signed 8-битное целое в классическом two's complement (ровно 8 бит).
|
|
||||||
*/
|
|
||||||
public void writeInt8(int value) {
|
|
||||||
writeUInt8(value); // берутся младшие 8 бит
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Читает signed 8-битное целое в классическом two's complement.
|
|
||||||
*/
|
|
||||||
public int readInt8() {
|
|
||||||
/**
|
|
||||||
* Приводим к byte чтобы сохранить знак
|
|
||||||
*/
|
|
||||||
return (byte) readUInt8();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeBit(int value) {
|
public void writeBit(int value) {
|
||||||
reserveBits(1);
|
writeBits(value & 1L, 1);
|
||||||
int bit = value & 1;
|
|
||||||
stream[writePointer >> 3] |= (byte) (bit << (7 - (writePointer & 7)));
|
|
||||||
writePointer++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readBit() {
|
public int readBit() {
|
||||||
if (remainingBits() < 1L) {
|
return (int) readBits(1);
|
||||||
throw new IllegalStateException("Not enough bits to read bit");
|
|
||||||
}
|
|
||||||
int bit = (stream[readPointer >> 3] >> (7 - (readPointer & 7))) & 1;
|
|
||||||
readPointer++;
|
|
||||||
return bit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeBoolean(boolean value) {
|
public void writeBoolean(boolean value) {
|
||||||
@@ -141,20 +68,129 @@ public class Stream {
|
|||||||
return readBit() == 1;
|
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) {
|
public void writeInt16(int value) {
|
||||||
writeUInt16(value);
|
writeUInt16(value); // lower 16 bits (two's complement)
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt16() {
|
public int readInt16() {
|
||||||
return (short) readUInt16();
|
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) {
|
public void writeInt32(int value) {
|
||||||
writeUInt32(value);
|
writeUInt32(value & 0xFFFFFFFFL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt32() {
|
public int readInt32() {
|
||||||
return readUInt32();
|
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) {
|
public void writeInt64(long value) {
|
||||||
@@ -165,28 +201,26 @@ public class Stream {
|
|||||||
return readUInt64();
|
return readUInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- float ----------
|
||||||
|
|
||||||
public void writeFloat32(float value) {
|
public void writeFloat32(float value) {
|
||||||
int floatValue = Float.floatToIntBits(value);
|
writeInt32(Float.floatToIntBits(value));
|
||||||
writeInt32(floatValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public float readFloat32() {
|
public float readFloat32() {
|
||||||
int floatValue = readInt32();
|
return Float.intBitsToFloat(readInt32());
|
||||||
return Float.intBitsToFloat(floatValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- string / bytes ----------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записывает строку в поток. Сначала записывается длина строки (как unsigned 32-битное целое), а затем записываются символы строки, которые кодируются как 16-битные значения.
|
* String: length(UInt32) + chars(UInt16).
|
||||||
* @param value строка для записи в поток. Если строка null, она будет записана как пустая строка. Длина строки ограничена максимальным значением unsigned 32-битного целого.
|
|
||||||
*/
|
*/
|
||||||
public void writeString(String value) {
|
public void writeString(String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = "";
|
value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Пишем длину строки как unsigned 32-битное целое, чтобы при чтении знать, сколько символов нужно прочитать для восстановления строки
|
|
||||||
*/
|
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
writeUInt32(length);
|
writeUInt32(length);
|
||||||
|
|
||||||
@@ -194,137 +228,137 @@ public class Stream {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Резервируем по 16 бит на каждый символ строки, так как каждый символ кодируется как 16-битное значение
|
|
||||||
*/
|
|
||||||
reserveBits((long) length * 16L);
|
reserveBits((long) length * 16L);
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
writeUInt16(value.charAt(i));
|
writeUInt16(value.charAt(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Читает строку из потока. Сначала читается длина строки (как unsigned 32-битное целое), а затем читаются символы строки, которые кодируются как 16-битные значения.
|
|
||||||
* Если длина отрицательная или если в потоке недостаточно битов для чтения указанного количества символов, возвращается пустая строка.
|
|
||||||
* @return строка, прочитанная из потока
|
|
||||||
*/
|
|
||||||
public String readString() {
|
public String readString() {
|
||||||
/**
|
long lenLong = readUInt32();
|
||||||
* Читаем длину как UInt32
|
if (lenLong > Integer.MAX_VALUE) {
|
||||||
*/
|
throw new IllegalStateException("String length too large: " + lenLong);
|
||||||
int length = readUInt32();
|
|
||||||
|
|
||||||
if (length < 0) {
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int length = (int) lenLong;
|
||||||
long requiredBits = (long) length * 16L;
|
long requiredBits = (long) length * 16L;
|
||||||
if (requiredBits > remainingBits()) {
|
if (requiredBits > remainingBits()) {
|
||||||
throw new IllegalStateException("Not enough bits to read string");
|
throw new IllegalStateException("Not enough bits to read string");
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder value = new StringBuilder(length);
|
StringBuilder sb = new StringBuilder(length);
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
/**
|
sb.append((char) readUInt16());
|
||||||
* Читаем символы строки как UInt16 и добавляем их в StringBuilder. Каждый символ кодируется 16 битами,
|
|
||||||
* что позволяет поддерживать широкий диапазон символов Unicode.
|
|
||||||
*/
|
|
||||||
value.append((char) readUInt16());
|
|
||||||
}
|
}
|
||||||
return value.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записывает массив байтов в поток.
|
* byte[]: length(UInt32) + raw bytes.
|
||||||
* Сначала записывается длина массива (как unsigned 32-битное целое),
|
|
||||||
* а затем записываются сами байты. Если массив null, он будет записан как массив нулевой длины.
|
|
||||||
* @param value массив байтов для записи в поток
|
|
||||||
*/
|
*/
|
||||||
public void writeBytes(byte[] value) {
|
public void writeBytes(byte[] value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = new byte[0];
|
value = new byte[0];
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Пишем длинну массива как unsigned 32-битное целое, чтобы при чтении знать, сколько байтов нужно прочитать для восстановления массива.
|
|
||||||
*/
|
|
||||||
writeUInt32(value.length);
|
writeUInt32(value.length);
|
||||||
|
|
||||||
if (value.length > 0) {
|
if (value.length == 0) {
|
||||||
/**
|
return;
|
||||||
* Резервируем нужное количество бит для записи массива байт. На один байт нужно 8 бит, поэтому умножаем длину
|
}
|
||||||
* массива на 8, чтобы получить общее количество бит для резервирования.
|
|
||||||
*/
|
reserveBits((long) value.length * 8L);
|
||||||
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) {
|
for (byte b : value) {
|
||||||
writeByte(b);
|
writeUInt8(b & 0xFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Читает массив байтов из потока. Сначала читается длина массива (как unsigned 32-битное целое),
|
|
||||||
* а затем читаются сами байты. Если длина отрицательная или если в потоке недостаточно битов для чтения указанного количества байтов, возвращается пустой массив.
|
|
||||||
* @return массив байтов, прочитанный из потока
|
|
||||||
*/
|
|
||||||
public byte[] readBytes() {
|
public byte[] readBytes() {
|
||||||
int length = readUInt32();
|
long lenLong = readUInt32();
|
||||||
|
if (lenLong == 0) {
|
||||||
if (length <= 0) {
|
|
||||||
return new byte[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;
|
long requiredBits = (long) length * 8L;
|
||||||
if (requiredBits > remainingBits()) {
|
if (requiredBits > remainingBits()) {
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] value = new byte[length];
|
byte[] out = new byte[length];
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
value[i] = readByte();
|
// 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;
|
||||||
}
|
}
|
||||||
return value;
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
out[i] = (byte) readUInt8();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
// ---------- internals ----------
|
||||||
return writePointer == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Возвращает количество реально использованного места (то есть только используемые байты, а не весь буфер включая аллокации)
|
|
||||||
* @return количество реально использованных байтов
|
|
||||||
*/
|
|
||||||
public int length() {
|
|
||||||
/**
|
|
||||||
* Так как writePointer - это индекс в битах, то для получения количества байтов нужно разделить его на 8.
|
|
||||||
* Но так как writePointer может быть не кратен 8, то нужно округлить его в большую сторону, чтобы учесть все использованные биты.
|
|
||||||
*/
|
|
||||||
return (writePointer + 7) >> 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBuffer() {
|
|
||||||
return getStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Возвращает количество оставшихся битов для чтения. Это разница между указателем записи и указателем чтения,
|
|
||||||
* которая показывает, сколько битов еще доступно для чтения в потоке.
|
|
||||||
* @return количество оставшихся битов для чтения
|
|
||||||
*/
|
|
||||||
private long remainingBits() {
|
private long remainingBits() {
|
||||||
return (long) writePointer - readPointer;
|
return (long) writePointer - readPointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void writeBits(long value, int bits) {
|
||||||
* Резервирует место в потоке для записи указанного количества битов. Этот метод проверяет, достаточно ли места в текущем буфере для записи новых данных,
|
if (bits <= 0) {
|
||||||
* и если нет, то расширяет буфер до необходимого размера.
|
return;
|
||||||
* Это позволяет избежать переполнения, а вызывающему коду не беспокоится о дополнительных аллокациях при записи данных в поток.
|
}
|
||||||
* @param bitsToWrite количество битов, которые нужно зарезервировать для записи
|
|
||||||
* @internal Этот метод используется внутри класса Stream для управления размером внутреннего массива байтов,
|
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) {
|
private void reserveBits(long bitsToWrite) {
|
||||||
if (bitsToWrite <= 0) {
|
if (bitsToWrite <= 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user