From ec541a2c0cedfb41768fa3531a333add53591d0a Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Fri, 27 Mar 2026 14:29:49 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D0=BA=D0=B0=D1=82=20Stream=20?= =?UTF-8?q?=D0=BA=20legacy-=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D1=83=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D0=B2=D0=BC=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20=D1=81=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D1=83=D1=89=D0=B8=D0=BC=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/rosetta/messenger/network/Protocol.kt | 2 +- .../com/rosetta/messenger/network/Stream.kt | 411 ++++++------------ 2 files changed, 122 insertions(+), 291 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt index b305993..9605f41 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt @@ -32,7 +32,7 @@ class Protocol( private const val TAG = "RosettaProtocol" private const val RECONNECT_INTERVAL = 5000L // 5 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 MIN_HEARTBEAT_SEND_INTERVAL_MS = 2_000L private const val HEARTBEAT_OK_LOG_THROTTLE_MS = 30_000L diff --git a/app/src/main/java/com/rosetta/messenger/network/Stream.kt b/app/src/main/java/com/rosetta/messenger/network/Stream.kt index bc898b2..c2e825f 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Stream.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Stream.kt @@ -1,332 +1,163 @@ package com.rosetta.messenger.network /** - * Binary stream for protocol packets. - * Ported from desktop/dev stream.ts implementation. + * Binary stream for protocol packets + * Matches the React Native implementation exactly */ class Stream(stream: ByteArray = ByteArray(0)) { - private var stream: ByteArray - private var readPointer = 0 // bits - private var writePointer = 0 // bits - + private var _stream = mutableListOf() + private var _readPointer = 0 + private var _writePointer = 0 + init { - if (stream.isEmpty()) { - this.stream = ByteArray(0) - } else { - this.stream = stream.copyOf() - this.writePointer = this.stream.size shl 3 - } + _stream = stream.map { it.toInt() and 0xFF }.toMutableList() } - + fun getStream(): ByteArray { - return stream.copyOf(length()) + return _stream.map { it.toByte() }.toByteArray() } - fun setStream(stream: ByteArray = ByteArray(0)) { - if (stream.isEmpty()) { - this.stream = ByteArray(0) - this.readPointer = 0 - this.writePointer = 0 - return + fun getReadPointerBits(): Int = _readPointer + + fun getTotalBits(): Int = _stream.size * 8 + + fun getRemainingBits(): Int = getTotalBits() - _readPointer + + fun hasRemainingBits(): Boolean = _readPointer < getTotalBits() + + fun setStream(stream: ByteArray) { + _stream = stream.map { it.toInt() and 0xFF }.toMutableList() + _readPointer = 0 + } + + fun writeInt8(value: Int) { + val negationBit = if (value < 0) 1 else 0 + val int8Value = Math.abs(value) and 0xFF + + ensureCapacity(_writePointer shr 3) + _stream[_writePointer shr 3] = _stream[_writePointer shr 3] or (negationBit shl (7 - (_writePointer and 7))) + _writePointer++ + + 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++ } - this.stream = stream.copyOf() - this.readPointer = 0 - this.writePointer = this.stream.size shl 3 } - - fun getBuffer(): ByteArray = getStream() - - fun isEmpty(): Boolean = writePointer == 0 - - fun length(): Int = (writePointer + 7) shr 3 - - fun getReadPointerBits(): Int = readPointer - - fun getTotalBits(): Int = writePointer - - fun getRemainingBits(): Int = writePointer - readPointer - - fun hasRemainingBits(): Boolean = readPointer < writePointer - + + fun readInt8(): Int { + var value = 0 + val negationBit = (_stream[_readPointer shr 3] shr (7 - (_readPointer and 7))) and 1 + _readPointer++ + + 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++ + } + + return if (negationBit == 1) -value else value + } + 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) { writeBit(if (value) 1 else 0) } - - fun readBoolean(): Boolean = readBit() == 1 - - fun writeByte(value: Int) { - writeUInt8(value and 0xFF) + + fun readBoolean(): Boolean { + return readBit() == 1 } - - 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) { - writeUInt16(value) + writeInt8(value shr 8) + writeInt8(value and 0xFF) } - + fun readInt16(): Int { - val value = readUInt16() - return if (value >= 0x8000) value - 0x10000 else value + val high = readInt8() shl 8 + 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) { - writeUInt32(value.toLong() and 0xFFFF_FFFFL) + writeInt16(value shr 16) + writeInt16(value and 0xFFFF) } - - fun readInt32(): Int = readUInt32().toInt() - - fun writeUInt64(value: ULong) { - 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 readInt32(): Int { + val high = readInt16() shl 16 + return high or readInt16() } - - fun readUInt64(): ULong { - val high = readUInt32().toULong() - val low = readUInt32().toULong() + + fun writeInt64(value: Long) { + val high = (value shr 32).toInt() + val low = (value and 0xFFFFFFFF).toInt() + writeInt32(high) + writeInt32(low) + } + + fun readInt64(): Long { + val high = readInt32().toLong() + val low = (readInt32().toLong() and 0xFFFFFFFFL) return (high shl 32) or low } - - fun writeInt64(value: Long) { - writeUInt64(value.toULong()) - } - - fun readInt64(): Long = readUInt64().toLong() - - fun writeFloat32(value: Float) { - val bits = value.toRawBits().toLong() and 0xFFFF_FFFFL - writeUInt32(bits) - } - - fun readFloat32(): Float { - val bits = readUInt32().toInt() - return Float.fromBits(bits) - } - - 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 writeString(value: String) { + writeInt32(value.length) + for (char in value) { + writeInt16(char.code) } } - + fun readString(): String { - val len = readUInt32() - if (len > Int.MAX_VALUE.toLong()) { - throw IllegalStateException("String length too large: $len") + val length = readInt32() + // Desktop parity + safety: don't trust malformed string length. + 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 requiredBits = len * 16L - if (requiredBits > remainingBits()) { - throw IllegalStateException("Not enough bits to read string") + val sb = StringBuilder() + for (i in 0 until length) { + sb.append(readInt16().toChar()) } - - val chars = CharArray(len.toInt()) - for (i in chars.indices) { - chars[i] = readUInt16().toChar() - } - return String(chars) + return sb.toString() } - - 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 writeBytes(value: ByteArray) { + writeInt32(value.size) + for (byte in value) { + writeInt8(byte.toInt()) } } - + fun readBytes(): ByteArray { - val len = readUInt32() - if (len == 0L) return ByteArray(0) - if (len > Int.MAX_VALUE.toLong()) return ByteArray(0) - - val requiredBits = len * 8L - if (requiredBits > remainingBits()) { - return ByteArray(0) + val length = readInt32() + val bytes = ByteArray(length) + for (i in 0 until length) { + bytes[i] = readInt8().toByte() } - - 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 + return bytes } - - 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) { - val requiredSize = index + 1 - if (requiredSize <= stream.size) return - - 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 + while (_stream.size <= index) { + _stream.add(0) } - - val next = ByteArray(newSize) - System.arraycopy(stream, 0, next, 0, stream.size) - stream = next } }