Откат Stream к legacy-формату для совместимости с текущим сервером

This commit is contained in:
2026-03-27 14:29:49 +05:00
parent 454402938c
commit ec541a2c0c
2 changed files with 122 additions and 291 deletions

View File

@@ -32,7 +32,7 @@ class Protocol(
private const val TAG = "RosettaProtocol" private const val TAG = "RosettaProtocol"
private const val RECONNECT_INTERVAL = 5000L // 5 seconds (как в Архиве) private const val RECONNECT_INTERVAL = 5000L // 5 seconds (как в Архиве)
private const val HANDSHAKE_TIMEOUT = 10000L // 10 seconds private const val HANDSHAKE_TIMEOUT = 10000L // 10 seconds
private const val MIN_PACKET_ID_BITS = 16 // Stream.readInt16() reads exactly 16 bits private const val MIN_PACKET_ID_BITS = 18 // Stream.readInt16() = 2 * readInt8() (9 bits each)
private const val DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 15 private const val DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 15
private const val MIN_HEARTBEAT_SEND_INTERVAL_MS = 2_000L private const val MIN_HEARTBEAT_SEND_INTERVAL_MS = 2_000L
private const val HEARTBEAT_OK_LOG_THROTTLE_MS = 30_000L private const val HEARTBEAT_OK_LOG_THROTTLE_MS = 30_000L

View File

@@ -1,332 +1,163 @@
package com.rosetta.messenger.network package com.rosetta.messenger.network
/** /**
* Binary stream for protocol packets. * Binary stream for protocol packets
* Ported from desktop/dev stream.ts implementation. * Matches the React Native implementation exactly
*/ */
class Stream(stream: ByteArray = ByteArray(0)) { class Stream(stream: ByteArray = ByteArray(0)) {
private var stream: ByteArray private var _stream = mutableListOf<Int>()
private var readPointer = 0 // bits private var _readPointer = 0
private var writePointer = 0 // bits private var _writePointer = 0
init { init {
if (stream.isEmpty()) { _stream = stream.map { it.toInt() and 0xFF }.toMutableList()
this.stream = ByteArray(0)
} else {
this.stream = stream.copyOf()
this.writePointer = this.stream.size shl 3
}
} }
fun getStream(): ByteArray { fun getStream(): ByteArray {
return stream.copyOf(length()) return _stream.map { it.toByte() }.toByteArray()
} }
fun setStream(stream: ByteArray = ByteArray(0)) { fun getReadPointerBits(): Int = _readPointer
if (stream.isEmpty()) {
this.stream = ByteArray(0) fun getTotalBits(): Int = _stream.size * 8
this.readPointer = 0
this.writePointer = 0 fun getRemainingBits(): Int = getTotalBits() - _readPointer
return
} fun hasRemainingBits(): Boolean = _readPointer < getTotalBits()
this.stream = stream.copyOf()
this.readPointer = 0 fun setStream(stream: ByteArray) {
this.writePointer = this.stream.size shl 3 _stream = stream.map { it.toInt() and 0xFF }.toMutableList()
_readPointer = 0
} }
fun getBuffer(): ByteArray = getStream() fun writeInt8(value: Int) {
val negationBit = if (value < 0) 1 else 0
val int8Value = Math.abs(value) and 0xFF
fun isEmpty(): Boolean = writePointer == 0 ensureCapacity(_writePointer shr 3)
_stream[_writePointer shr 3] = _stream[_writePointer shr 3] or (negationBit shl (7 - (_writePointer and 7)))
_writePointer++
fun length(): Int = (writePointer + 7) shr 3 for (i in 0 until 8) {
val bit = (int8Value shr (7 - i)) and 1
ensureCapacity(_writePointer shr 3)
_stream[_writePointer shr 3] = _stream[_writePointer shr 3] or (bit shl (7 - (_writePointer and 7)))
_writePointer++
}
}
fun getReadPointerBits(): Int = readPointer fun readInt8(): Int {
var value = 0
val negationBit = (_stream[_readPointer shr 3] shr (7 - (_readPointer and 7))) and 1
_readPointer++
fun getTotalBits(): Int = writePointer for (i in 0 until 8) {
val bit = (_stream[_readPointer shr 3] shr (7 - (_readPointer and 7))) and 1
value = value or (bit shl (7 - i))
_readPointer++
}
fun getRemainingBits(): Int = writePointer - readPointer return if (negationBit == 1) -value else value
}
fun hasRemainingBits(): Boolean = readPointer < writePointer
fun writeBit(value: Int) { fun writeBit(value: Int) {
writeBits((value and 1).toULong(), 1) val bit = value and 1
ensureCapacity(_writePointer shr 3)
_stream[_writePointer shr 3] = _stream[_writePointer shr 3] or (bit shl (7 - (_writePointer and 7)))
_writePointer++
} }
fun readBit(): Int = readBits(1).toInt() fun readBit(): Int {
val bit = (_stream[_readPointer shr 3] shr (7 - (_readPointer and 7))) and 1
_readPointer++
return bit
}
fun writeBoolean(value: Boolean) { fun writeBoolean(value: Boolean) {
writeBit(if (value) 1 else 0) writeBit(if (value) 1 else 0)
} }
fun readBoolean(): Boolean = readBit() == 1 fun readBoolean(): Boolean {
return readBit() == 1
fun writeByte(value: Int) {
writeUInt8(value and 0xFF)
}
fun readByte(): Int {
val value = readUInt8()
return if (value >= 0x80) value - 0x100 else value
}
fun writeUInt8(value: Int) {
val v = value and 0xFF
if ((writePointer and 7) == 0) {
reserveBits(8)
stream[writePointer shr 3] = v.toByte()
writePointer += 8
return
}
writeBits(v.toULong(), 8)
}
fun readUInt8(): Int {
if (remainingBits() < 8L) {
throw IllegalStateException("Not enough bits to read UInt8")
}
if ((readPointer and 7) == 0) {
val value = stream[readPointer shr 3].toInt() and 0xFF
readPointer += 8
return value
}
return readBits(8).toInt()
}
fun writeInt8(value: Int) {
writeUInt8(value)
}
fun readInt8(): Int {
val value = readUInt8()
return if (value >= 0x80) value - 0x100 else value
}
fun writeUInt16(value: Int) {
val v = value and 0xFFFF
writeUInt8((v ushr 8) and 0xFF)
writeUInt8(v and 0xFF)
}
fun readUInt16(): Int {
val hi = readUInt8()
val lo = readUInt8()
return (hi shl 8) or lo
} }
fun writeInt16(value: Int) { fun writeInt16(value: Int) {
writeUInt16(value) writeInt8(value shr 8)
writeInt8(value and 0xFF)
} }
fun readInt16(): Int { fun readInt16(): Int {
val value = readUInt16() val high = readInt8() shl 8
return if (value >= 0x8000) value - 0x10000 else value return high or readInt8()
}
fun writeUInt32(value: Long) {
if (value < 0L || value > 0xFFFF_FFFFL) {
throw IllegalArgumentException("UInt32 out of range: $value")
}
writeUInt8(((value ushr 24) and 0xFF).toInt())
writeUInt8(((value ushr 16) and 0xFF).toInt())
writeUInt8(((value ushr 8) and 0xFF).toInt())
writeUInt8((value and 0xFF).toInt())
}
fun readUInt32(): Long {
val b1 = readUInt8().toLong()
val b2 = readUInt8().toLong()
val b3 = readUInt8().toLong()
val b4 = readUInt8().toLong()
return ((b1 shl 24) or (b2 shl 16) or (b3 shl 8) or b4) and 0xFFFF_FFFFL
} }
fun writeInt32(value: Int) { fun writeInt32(value: Int) {
writeUInt32(value.toLong() and 0xFFFF_FFFFL) writeInt16(value shr 16)
writeInt16(value and 0xFFFF)
} }
fun readInt32(): Int = readUInt32().toInt() fun readInt32(): Int {
val high = readInt16() shl 16
fun writeUInt64(value: ULong) { return high or readInt16()
writeUInt8(((value shr 56) and 0xFFu).toInt())
writeUInt8(((value shr 48) and 0xFFu).toInt())
writeUInt8(((value shr 40) and 0xFFu).toInt())
writeUInt8(((value shr 32) and 0xFFu).toInt())
writeUInt8(((value shr 24) and 0xFFu).toInt())
writeUInt8(((value shr 16) and 0xFFu).toInt())
writeUInt8(((value shr 8) and 0xFFu).toInt())
writeUInt8((value and 0xFFu).toInt())
}
fun readUInt64(): ULong {
val high = readUInt32().toULong()
val low = readUInt32().toULong()
return (high shl 32) or low
} }
fun writeInt64(value: Long) { fun writeInt64(value: Long) {
writeUInt64(value.toULong()) val high = (value shr 32).toInt()
val low = (value and 0xFFFFFFFF).toInt()
writeInt32(high)
writeInt32(low)
} }
fun readInt64(): Long = readUInt64().toLong() fun readInt64(): Long {
val high = readInt32().toLong()
fun writeFloat32(value: Float) { val low = (readInt32().toLong() and 0xFFFFFFFFL)
val bits = value.toRawBits().toLong() and 0xFFFF_FFFFL return (high shl 32) or low
writeUInt32(bits)
} }
fun readFloat32(): Float { fun writeString(value: String) {
val bits = readUInt32().toInt() writeInt32(value.length)
return Float.fromBits(bits) for (char in value) {
} writeInt16(char.code)
fun writeString(value: String?) {
val str = value ?: ""
writeUInt32(str.length.toLong())
if (str.isEmpty()) return
reserveBits(str.length.toLong() * 16L)
for (i in str.indices) {
writeUInt16(str[i].code and 0xFFFF)
} }
} }
fun readString(): String { fun readString(): String {
val len = readUInt32() val length = readInt32()
if (len > Int.MAX_VALUE.toLong()) { // Desktop parity + safety: don't trust malformed string length.
throw IllegalStateException("String length too large: $len") val bytesAvailable = _stream.size - (_readPointer shr 3)
if (length < 0 || (length.toLong() * 2L) > bytesAvailable.toLong()) {
android.util.Log.w(
"RosettaStream",
"readString invalid length=$length, bytesAvailable=$bytesAvailable, readPointer=$_readPointer"
)
return ""
}
val sb = StringBuilder()
for (i in 0 until length) {
sb.append(readInt16().toChar())
}
return sb.toString()
} }
val requiredBits = len * 16L fun writeBytes(value: ByteArray) {
if (requiredBits > remainingBits()) { writeInt32(value.size)
throw IllegalStateException("Not enough bits to read string") for (byte in value) {
} writeInt8(byte.toInt())
val chars = CharArray(len.toInt())
for (i in chars.indices) {
chars[i] = readUInt16().toChar()
}
return String(chars)
}
fun writeBytes(value: ByteArray?) {
val bytes = value ?: ByteArray(0)
writeUInt32(bytes.size.toLong())
if (bytes.isEmpty()) return
reserveBits(bytes.size.toLong() * 8L)
if ((writePointer and 7) == 0) {
val byteIndex = writePointer shr 3
ensureCapacity(byteIndex + bytes.size - 1)
System.arraycopy(bytes, 0, stream, byteIndex, bytes.size)
writePointer += bytes.size shl 3
return
}
for (b in bytes) {
writeUInt8(b.toInt() and 0xFF)
} }
} }
fun readBytes(): ByteArray { fun readBytes(): ByteArray {
val len = readUInt32() val length = readInt32()
if (len == 0L) return ByteArray(0) val bytes = ByteArray(length)
if (len > Int.MAX_VALUE.toLong()) return ByteArray(0) for (i in 0 until length) {
bytes[i] = readInt8().toByte()
val requiredBits = len * 8L
if (requiredBits > remainingBits()) {
return ByteArray(0)
} }
return bytes
val out = ByteArray(len.toInt())
if ((readPointer and 7) == 0) {
val byteIndex = readPointer shr 3
System.arraycopy(stream, byteIndex, out, 0, out.size)
readPointer += out.size shl 3
return out
}
for (i in out.indices) {
out[i] = readUInt8().toByte()
}
return out
}
private fun remainingBits(): Long = (writePointer - readPointer).toLong()
private fun writeBits(value: ULong, bits: Int) {
if (bits <= 0) return
reserveBits(bits.toLong())
for (i in bits - 1 downTo 0) {
val bit = ((value shr i) and 1u).toInt()
val byteIndex = writePointer shr 3
val shift = 7 - (writePointer and 7)
if (bit == 1) {
stream[byteIndex] = (stream[byteIndex].toInt() or (1 shl shift)).toByte()
} else {
stream[byteIndex] = (stream[byteIndex].toInt() and (1 shl shift).inv()).toByte()
}
writePointer++
}
}
private fun readBits(bits: Int): ULong {
if (bits <= 0) return 0u
if (remainingBits() < bits.toLong()) {
throw IllegalStateException("Not enough bits to read")
}
var value = 0uL
repeat(bits) {
val bit = (stream[readPointer shr 3].toInt() ushr (7 - (readPointer and 7))) and 1
value = (value shl 1) or bit.toULong()
readPointer++
}
return value
}
private fun reserveBits(bitsToWrite: Long) {
if (bitsToWrite <= 0L) return
val lastBitIndex = writePointer.toLong() + bitsToWrite - 1L
if (lastBitIndex < 0L) {
throw IllegalStateException("Bit index overflow")
}
val byteIndex = lastBitIndex ushr 3
if (byteIndex > Int.MAX_VALUE.toLong()) {
throw IllegalStateException("Stream too large")
}
ensureCapacity(byteIndex.toInt())
} }
private fun ensureCapacity(index: Int) { private fun ensureCapacity(index: Int) {
val requiredSize = index + 1 while (_stream.size <= index) {
if (requiredSize <= stream.size) return _stream.add(0)
}
var newSize = if (stream.isEmpty()) 32 else stream.size
while (newSize < requiredSize) {
if (newSize > (Int.MAX_VALUE shr 1)) {
newSize = requiredSize
break
}
newSize = newSize shl 1
}
val next = ByteArray(newSize)
System.arraycopy(stream, 0, next, 0, stream.size)
stream = next
} }
} }