Add network packet classes and enums for messaging functionality
- Introduced `AttachmentType` enum to define various attachment types. - Added `DeliveryStatus` enum to represent message delivery statuses. - Created `MessageAttachment` data class to encapsulate message attachments. - Implemented `OnlineState` enum to manage user online statuses. - Developed various packet classes: `Packet`, `PacketHandshake`, `PacketResult`, `PacketSearch`, `PacketUserInfo`, `PacketOnlineState`, `PacketOnlineSubscribe`, `PacketMessage`, `PacketRead`, `PacketDelivery`, `PacketTyping`, `PacketChunk`, `PacketPushNotification`, `PacketRequestTransport`, and `PacketPushToken` for handling different messaging operations. - Removed the old `Packets.kt` file to streamline the codebase. - Added `SearchUser` data class for user search results.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Типы вложений
|
||||
*/
|
||||
enum class AttachmentType(val value: Int) {
|
||||
IMAGE(0), // Изображение
|
||||
MESSAGES(1), // Reply (цитата сообщения)
|
||||
FILE(2), // Файл
|
||||
AVATAR(3); // Аватар пользователя
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: IMAGE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Статус доставки сообщения
|
||||
*/
|
||||
enum class DeliveryStatus(val value: Int) {
|
||||
WAITING(0), // Ожидает отправки
|
||||
DELIVERED(1), // Доставлено
|
||||
ERROR(2), // Ошибка
|
||||
READ(3); // Прочитано
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: WAITING
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Вложение к сообщению
|
||||
*/
|
||||
data class MessageAttachment(
|
||||
val id: String,
|
||||
val blob: String, // Base64 данные или пусто для CDN
|
||||
val type: AttachmentType,
|
||||
val preview: String = "", // Метаданные: "UUID::metadata" или "filesize::filename"
|
||||
val width: Int = 0,
|
||||
val height: Int = 0,
|
||||
val localUri: String = "" // 🚀 Локальный URI для мгновенного отображения (optimistic UI)
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Online State enum
|
||||
*/
|
||||
enum class OnlineState(val value: Int) {
|
||||
ONLINE(0),
|
||||
OFFLINE(1);
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(isOnline: Boolean) = if (isOnline) ONLINE else OFFLINE
|
||||
}
|
||||
}
|
||||
10
app/src/main/java/com/rosetta/messenger/network/Packet.kt
Normal file
10
app/src/main/java/com/rosetta/messenger/network/Packet.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Base class for all protocol packets
|
||||
*/
|
||||
abstract class Packet {
|
||||
abstract fun getPacketId(): Int
|
||||
abstract fun receive(stream: Stream)
|
||||
abstract fun send(): Stream
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Chunk packet (ID: 0x09)
|
||||
* Для разбиения больших пакетов на части (как в Desktop)
|
||||
* ВАЖНО: chunkIndex и totalChunks - Int16, не Int32!
|
||||
*/
|
||||
class PacketChunk : Packet() {
|
||||
var chunkId: String = ""
|
||||
var chunkIndex: Int = 0
|
||||
var totalChunks: Int = 0
|
||||
var data: ByteArray = ByteArray(0)
|
||||
|
||||
override fun getPacketId(): Int = 0x09
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// В Desktop: readInt16 для index и total
|
||||
chunkIndex = stream.readInt16()
|
||||
totalChunks = stream.readInt16()
|
||||
chunkId = stream.readString()
|
||||
data = stream.readBytes()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
// В Desktop: writeInt16 для index и total
|
||||
stream.writeInt16(chunkIndex)
|
||||
stream.writeInt16(totalChunks)
|
||||
stream.writeString(chunkId)
|
||||
stream.writeBytes(data)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Delivery packet (ID: 0x08)
|
||||
* Уведомление о доставке сообщения
|
||||
* Порядок полей как в React Native: toPublicKey, messageId
|
||||
*/
|
||||
class PacketDelivery : Packet() {
|
||||
var messageId: String = ""
|
||||
var toPublicKey: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x08
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// React Native читает: toPublicKey, messageId
|
||||
toPublicKey = stream.readString()
|
||||
messageId = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
// React Native пишет: toPublicKey, messageId
|
||||
stream.writeString(toPublicKey)
|
||||
stream.writeString(messageId)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Handshake packet (ID: 0x00)
|
||||
* First packet sent by client to authenticate with the server
|
||||
*/
|
||||
class PacketHandshake : Packet() {
|
||||
var privateKey: String = ""
|
||||
var publicKey: String = ""
|
||||
var protocolVersion: Int = 1
|
||||
var heartbeatInterval: Int = 15
|
||||
|
||||
override fun getPacketId(): Int = 0x00
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
publicKey = stream.readString()
|
||||
protocolVersion = stream.readInt8()
|
||||
heartbeatInterval = stream.readInt8()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(publicKey)
|
||||
stream.writeInt8(protocolVersion)
|
||||
stream.writeInt8(heartbeatInterval)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Message packet (ID: 0x06)
|
||||
* Отправка и получение сообщений
|
||||
*/
|
||||
class PacketMessage : Packet() {
|
||||
var fromPublicKey: String = ""
|
||||
var toPublicKey: String = ""
|
||||
var content: String = "" // Зашифрованный текст
|
||||
var chachaKey: String = "" // RSA зашифрованный ключ
|
||||
var timestamp: Long = 0
|
||||
var privateKey: String = "" // Hash приватного ключа (для авторизации)
|
||||
var messageId: String = ""
|
||||
var attachments: List<MessageAttachment> = emptyList()
|
||||
|
||||
override fun getPacketId(): Int = 0x06
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
fromPublicKey = stream.readString()
|
||||
toPublicKey = stream.readString()
|
||||
content = stream.readString()
|
||||
chachaKey = stream.readString()
|
||||
timestamp = stream.readInt64()
|
||||
privateKey = stream.readString()
|
||||
messageId = stream.readString()
|
||||
|
||||
val attachmentCount = stream.readInt8()
|
||||
val attachmentsList = mutableListOf<MessageAttachment>()
|
||||
for (i in 0 until attachmentCount) {
|
||||
attachmentsList.add(MessageAttachment(
|
||||
id = stream.readString(),
|
||||
preview = stream.readString(),
|
||||
blob = stream.readString(),
|
||||
type = AttachmentType.fromInt(stream.readInt8())
|
||||
))
|
||||
}
|
||||
attachments = attachmentsList
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(fromPublicKey)
|
||||
stream.writeString(toPublicKey)
|
||||
stream.writeString(content)
|
||||
stream.writeString(chachaKey)
|
||||
stream.writeInt64(timestamp)
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(messageId)
|
||||
stream.writeInt8(attachments.size)
|
||||
|
||||
for (attachment in attachments) {
|
||||
stream.writeString(attachment.id)
|
||||
stream.writeString(attachment.preview)
|
||||
stream.writeString(attachment.blob)
|
||||
stream.writeInt8(attachment.type.value)
|
||||
}
|
||||
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Public key with online state
|
||||
*/
|
||||
data class PublicKeyOnlineState(
|
||||
val publicKey: String,
|
||||
val state: OnlineState
|
||||
)
|
||||
|
||||
/**
|
||||
* Online State packet (ID: 0x05)
|
||||
* Notify about user online status
|
||||
* Формат как в React Native: массив {publicKey, state}
|
||||
*/
|
||||
class PacketOnlineState : Packet() {
|
||||
var publicKeysState: MutableList<PublicKeyOnlineState> = mutableListOf()
|
||||
|
||||
override fun getPacketId(): Int = 0x05
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
val count = stream.readInt8()
|
||||
publicKeysState.clear()
|
||||
repeat(count) {
|
||||
val publicKey = stream.readString()
|
||||
val isOnline = stream.readBoolean()
|
||||
publicKeysState.add(PublicKeyOnlineState(publicKey, OnlineState.fromBoolean(isOnline)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeInt8(publicKeysState.size)
|
||||
publicKeysState.forEach { item ->
|
||||
stream.writeString(item.publicKey)
|
||||
stream.writeBoolean(item.state == OnlineState.ONLINE)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Online Subscribe packet (ID: 0x04)
|
||||
* Subscribe to user online status updates
|
||||
* Формат как в React Native: privateKey + array of publicKeys
|
||||
*/
|
||||
class PacketOnlineSubscribe : Packet() {
|
||||
var privateKey: String = ""
|
||||
var publicKeys: MutableList<String> = mutableListOf()
|
||||
|
||||
override fun getPacketId(): Int = 0x04
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
val keysCount = stream.readInt16()
|
||||
publicKeys.clear()
|
||||
repeat(keysCount) {
|
||||
publicKeys.add(stream.readString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeInt16(publicKeys.size)
|
||||
publicKeys.forEach { key ->
|
||||
stream.writeString(key)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
fun addPublicKey(publicKey: String) {
|
||||
publicKeys.add(publicKey)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Push Notification Action
|
||||
*/
|
||||
enum class PushNotificationAction(val value: Int) {
|
||||
SUBSCRIBE(0),
|
||||
UNSUBSCRIBE(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Push Notification packet (ID: 0x10)
|
||||
* Отправка FCM/APNS токена на сервер для push-уведомлений (новый формат)
|
||||
* Совместим с React Native версией
|
||||
*/
|
||||
class PacketPushNotification : Packet() {
|
||||
var notificationsToken: String = ""
|
||||
var action: PushNotificationAction = PushNotificationAction.SUBSCRIBE
|
||||
|
||||
override fun getPacketId(): Int = 0x10
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
notificationsToken = stream.readString()
|
||||
action = when (stream.readInt8()) {
|
||||
1 -> PushNotificationAction.UNSUBSCRIBE
|
||||
else -> PushNotificationAction.SUBSCRIBE
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(notificationsToken)
|
||||
stream.writeInt8(action.value)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Push Token packet (ID: 0x0A) - DEPRECATED
|
||||
* Старый формат, заменен на PacketPushNotification (0x10)
|
||||
*/
|
||||
class PacketPushToken : Packet() {
|
||||
var privateKey: String = ""
|
||||
var publicKey: String = ""
|
||||
var pushToken: String = ""
|
||||
var platform: String = "android" // "android" или "ios"
|
||||
|
||||
override fun getPacketId(): Int = 0x0A
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
publicKey = stream.readString()
|
||||
pushToken = stream.readString()
|
||||
platform = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(publicKey)
|
||||
stream.writeString(pushToken)
|
||||
stream.writeString(platform)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Read packet (ID: 0x07)
|
||||
* Уведомление о прочтении сообщения
|
||||
* Порядок полей как в Desktop: privateKey, fromPublicKey, toPublicKey
|
||||
*/
|
||||
class PacketRead : Packet() {
|
||||
var privateKey: String = ""
|
||||
var fromPublicKey: String = ""
|
||||
var toPublicKey: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x07
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// Desktop: privateKey, fromPublicKey, toPublicKey
|
||||
privateKey = stream.readString()
|
||||
fromPublicKey = stream.readString()
|
||||
toPublicKey = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
// Desktop: privateKey, fromPublicKey, toPublicKey
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(fromPublicKey)
|
||||
stream.writeString(toPublicKey)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Request Transport packet (ID: 0x0F)
|
||||
* Запрос адреса транспортного сервера для загрузки/скачивания файлов
|
||||
*/
|
||||
class PacketRequestTransport : Packet() {
|
||||
var transportServer: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x0F
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
transportServer = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(transportServer)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Result packet (ID: 0x02)
|
||||
* Server response for various operations
|
||||
* Desktop uses: readInt16() for resultCode only
|
||||
*/
|
||||
class PacketResult : Packet() {
|
||||
var resultCode: Int = 0
|
||||
|
||||
override fun getPacketId(): Int = 0x02
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// Desktop: this.resultCode = stream.readInt16();
|
||||
resultCode = stream.readInt16()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeInt16(resultCode)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Search packet (ID: 0x03)
|
||||
* Search for users by username or public key
|
||||
*/
|
||||
class PacketSearch : Packet() {
|
||||
var privateKey: String = ""
|
||||
var search: String = ""
|
||||
var users: List<SearchUser> = emptyList()
|
||||
|
||||
override fun getPacketId(): Int = 0x03
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
search = stream.readString()
|
||||
val userCount = stream.readInt16() // Int16, not Int32!
|
||||
val usersList = mutableListOf<SearchUser>()
|
||||
for (i in 0 until userCount) {
|
||||
// Order: username, title, publicKey, verified, online (matching React Native)
|
||||
val user = SearchUser(
|
||||
username = stream.readString(),
|
||||
title = stream.readString(),
|
||||
publicKey = stream.readString(),
|
||||
verified = stream.readInt8(),
|
||||
online = stream.readInt8()
|
||||
)
|
||||
usersList.add(user)
|
||||
}
|
||||
users = usersList
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(search)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Typing packet (ID: 0x0B)
|
||||
* Порядок полей как в React Native: privateKey, fromPublicKey, toPublicKey
|
||||
*/
|
||||
class PacketTyping : Packet() {
|
||||
var privateKey: String = ""
|
||||
var fromPublicKey: String = ""
|
||||
var toPublicKey: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x0B
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
fromPublicKey = stream.readString()
|
||||
toPublicKey = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(fromPublicKey)
|
||||
stream.writeString(toPublicKey)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* User Info packet (ID: 0x01)
|
||||
* Get/Set user information
|
||||
* Protocol order: username, title, privateKey (matching TypeScript version)
|
||||
*/
|
||||
class PacketUserInfo : Packet() {
|
||||
var privateKey: String = ""
|
||||
var username: String = ""
|
||||
var title: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x01
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
username = stream.readString()
|
||||
title = stream.readString()
|
||||
privateKey = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(username)
|
||||
stream.writeString(title)
|
||||
stream.writeString(privateKey)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -1,540 +0,0 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
/**
|
||||
* Base class for all protocol packets
|
||||
*/
|
||||
abstract class Packet {
|
||||
abstract fun getPacketId(): Int
|
||||
abstract fun receive(stream: Stream)
|
||||
abstract fun send(): Stream
|
||||
}
|
||||
|
||||
/**
|
||||
* Handshake packet (ID: 0x00)
|
||||
* First packet sent by client to authenticate with the server
|
||||
*/
|
||||
class PacketHandshake : Packet() {
|
||||
var privateKey: String = ""
|
||||
var publicKey: String = ""
|
||||
var protocolVersion: Int = 1
|
||||
var heartbeatInterval: Int = 15
|
||||
|
||||
override fun getPacketId(): Int = 0x00
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
publicKey = stream.readString()
|
||||
protocolVersion = stream.readInt8()
|
||||
heartbeatInterval = stream.readInt8()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(publicKey)
|
||||
stream.writeInt8(protocolVersion)
|
||||
stream.writeInt8(heartbeatInterval)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Result packet (ID: 0x02)
|
||||
* Server response for various operations
|
||||
* Desktop uses: readInt16() for resultCode only
|
||||
*/
|
||||
class PacketResult : Packet() {
|
||||
var resultCode: Int = 0
|
||||
|
||||
override fun getPacketId(): Int = 0x02
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// Desktop: this.resultCode = stream.readInt16();
|
||||
resultCode = stream.readInt16()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeInt16(resultCode)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search packet (ID: 0x03)
|
||||
* Search for users by username or public key
|
||||
*/
|
||||
class PacketSearch : Packet() {
|
||||
var privateKey: String = ""
|
||||
var search: String = ""
|
||||
var users: List<SearchUser> = emptyList()
|
||||
|
||||
override fun getPacketId(): Int = 0x03
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
search = stream.readString()
|
||||
val userCount = stream.readInt16() // Int16, not Int32!
|
||||
val usersList = mutableListOf<SearchUser>()
|
||||
for (i in 0 until userCount) {
|
||||
// Order: username, title, publicKey, verified, online (matching React Native)
|
||||
val user = SearchUser(
|
||||
username = stream.readString(),
|
||||
title = stream.readString(),
|
||||
publicKey = stream.readString(),
|
||||
verified = stream.readInt8(),
|
||||
online = stream.readInt8()
|
||||
)
|
||||
usersList.add(user)
|
||||
}
|
||||
users = usersList
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(search)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
data class SearchUser(
|
||||
val publicKey: String,
|
||||
val title: String,
|
||||
val username: String,
|
||||
val verified: Int,
|
||||
val online: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* User Info packet (ID: 0x01)
|
||||
* Get/Set user information
|
||||
* Protocol order: username, title, privateKey (matching TypeScript version)
|
||||
*/
|
||||
class PacketUserInfo : Packet() {
|
||||
var privateKey: String = ""
|
||||
var username: String = ""
|
||||
var title: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x01
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
username = stream.readString()
|
||||
title = stream.readString()
|
||||
privateKey = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(username)
|
||||
stream.writeString(title)
|
||||
stream.writeString(privateKey)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Online State enum
|
||||
*/
|
||||
enum class OnlineState(val value: Int) {
|
||||
ONLINE(0),
|
||||
OFFLINE(1);
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(isOnline: Boolean) = if (isOnline) ONLINE else OFFLINE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public key with online state
|
||||
*/
|
||||
data class PublicKeyOnlineState(
|
||||
val publicKey: String,
|
||||
val state: OnlineState
|
||||
)
|
||||
|
||||
/**
|
||||
* Online State packet (ID: 0x05)
|
||||
* Notify about user online status
|
||||
* Формат как в React Native: массив {publicKey, state}
|
||||
*/
|
||||
class PacketOnlineState : Packet() {
|
||||
var publicKeysState: MutableList<PublicKeyOnlineState> = mutableListOf()
|
||||
|
||||
override fun getPacketId(): Int = 0x05
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
val count = stream.readInt8()
|
||||
publicKeysState.clear()
|
||||
repeat(count) {
|
||||
val publicKey = stream.readString()
|
||||
val isOnline = stream.readBoolean()
|
||||
publicKeysState.add(PublicKeyOnlineState(publicKey, OnlineState.fromBoolean(isOnline)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeInt8(publicKeysState.size)
|
||||
publicKeysState.forEach { item ->
|
||||
stream.writeString(item.publicKey)
|
||||
stream.writeBoolean(item.state == OnlineState.ONLINE)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Online Subscribe packet (ID: 0x04)
|
||||
* Subscribe to user online status updates
|
||||
* Формат как в React Native: privateKey + array of publicKeys
|
||||
*/
|
||||
class PacketOnlineSubscribe : Packet() {
|
||||
var privateKey: String = ""
|
||||
var publicKeys: MutableList<String> = mutableListOf()
|
||||
|
||||
override fun getPacketId(): Int = 0x04
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
val keysCount = stream.readInt16()
|
||||
publicKeys.clear()
|
||||
repeat(keysCount) {
|
||||
publicKeys.add(stream.readString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeInt16(publicKeys.size)
|
||||
publicKeys.forEach { key ->
|
||||
stream.writeString(key)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
fun addPublicKey(publicKey: String) {
|
||||
publicKeys.add(publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MESSAGE PACKETS - Как в React Native версии
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Типы вложений
|
||||
*/
|
||||
enum class AttachmentType(val value: Int) {
|
||||
IMAGE(0), // Изображение
|
||||
MESSAGES(1), // Reply (цитата сообщения)
|
||||
FILE(2), // Файл
|
||||
AVATAR(3); // Аватар пользователя
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: IMAGE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Статус доставки сообщения
|
||||
*/
|
||||
enum class DeliveryStatus(val value: Int) {
|
||||
WAITING(0), // Ожидает отправки
|
||||
DELIVERED(1), // Доставлено
|
||||
ERROR(2), // Ошибка
|
||||
READ(3); // Прочитано
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: WAITING
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Вложение к сообщению
|
||||
*/
|
||||
data class MessageAttachment(
|
||||
val id: String,
|
||||
val blob: String, // Base64 данные или пусто для CDN
|
||||
val type: AttachmentType,
|
||||
val preview: String = "", // Метаданные: "UUID::metadata" или "filesize::filename"
|
||||
val width: Int = 0,
|
||||
val height: Int = 0,
|
||||
val localUri: String = "" // 🚀 Локальный URI для мгновенного отображения (optimistic UI)
|
||||
)
|
||||
|
||||
/**
|
||||
* Message packet (ID: 0x06)
|
||||
* Отправка и получение сообщений
|
||||
*/
|
||||
class PacketMessage : Packet() {
|
||||
var fromPublicKey: String = ""
|
||||
var toPublicKey: String = ""
|
||||
var content: String = "" // Зашифрованный текст
|
||||
var chachaKey: String = "" // RSA зашифрованный ключ
|
||||
var timestamp: Long = 0
|
||||
var privateKey: String = "" // Hash приватного ключа (для авторизации)
|
||||
var messageId: String = ""
|
||||
var attachments: List<MessageAttachment> = emptyList()
|
||||
|
||||
override fun getPacketId(): Int = 0x06
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
fromPublicKey = stream.readString()
|
||||
toPublicKey = stream.readString()
|
||||
content = stream.readString()
|
||||
chachaKey = stream.readString()
|
||||
timestamp = stream.readInt64()
|
||||
privateKey = stream.readString()
|
||||
messageId = stream.readString()
|
||||
|
||||
val attachmentCount = stream.readInt8()
|
||||
val attachmentsList = mutableListOf<MessageAttachment>()
|
||||
for (i in 0 until attachmentCount) {
|
||||
attachmentsList.add(MessageAttachment(
|
||||
id = stream.readString(),
|
||||
preview = stream.readString(),
|
||||
blob = stream.readString(),
|
||||
type = AttachmentType.fromInt(stream.readInt8())
|
||||
))
|
||||
}
|
||||
attachments = attachmentsList
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(fromPublicKey)
|
||||
stream.writeString(toPublicKey)
|
||||
stream.writeString(content)
|
||||
stream.writeString(chachaKey)
|
||||
stream.writeInt64(timestamp)
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(messageId)
|
||||
stream.writeInt8(attachments.size)
|
||||
|
||||
for (attachment in attachments) {
|
||||
stream.writeString(attachment.id)
|
||||
stream.writeString(attachment.preview)
|
||||
stream.writeString(attachment.blob)
|
||||
stream.writeInt8(attachment.type.value)
|
||||
}
|
||||
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read packet (ID: 0x07)
|
||||
* Уведомление о прочтении сообщения
|
||||
* Порядок полей как в Desktop: privateKey, fromPublicKey, toPublicKey
|
||||
*/
|
||||
class PacketRead : Packet() {
|
||||
var privateKey: String = ""
|
||||
var fromPublicKey: String = ""
|
||||
var toPublicKey: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x07
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// Desktop: privateKey, fromPublicKey, toPublicKey
|
||||
privateKey = stream.readString()
|
||||
fromPublicKey = stream.readString()
|
||||
toPublicKey = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
// Desktop: privateKey, fromPublicKey, toPublicKey
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(fromPublicKey)
|
||||
stream.writeString(toPublicKey)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivery packet (ID: 0x08)
|
||||
* Уведомление о доставке сообщения
|
||||
* Порядок полей как в React Native: toPublicKey, messageId
|
||||
*/
|
||||
class PacketDelivery : Packet() {
|
||||
var messageId: String = ""
|
||||
var toPublicKey: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x08
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// React Native читает: toPublicKey, messageId
|
||||
toPublicKey = stream.readString()
|
||||
messageId = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
// React Native пишет: toPublicKey, messageId
|
||||
stream.writeString(toPublicKey)
|
||||
stream.writeString(messageId)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Typing packet (ID: 0x0B)
|
||||
* Уведомление "печатает..."
|
||||
*/
|
||||
/**
|
||||
* Typing packet (ID: 0x0B)
|
||||
* Порядок полей как в React Native: privateKey, fromPublicKey, toPublicKey
|
||||
*/
|
||||
class PacketTyping : Packet() {
|
||||
var privateKey: String = ""
|
||||
var fromPublicKey: String = ""
|
||||
var toPublicKey: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x0B
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
fromPublicKey = stream.readString()
|
||||
toPublicKey = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(fromPublicKey)
|
||||
stream.writeString(toPublicKey)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk packet (ID: 0x09)
|
||||
* Для разбиения больших пакетов на части (как в Desktop)
|
||||
* ВАЖНО: chunkIndex и totalChunks - Int16, не Int32!
|
||||
*/
|
||||
class PacketChunk : Packet() {
|
||||
var chunkId: String = ""
|
||||
var chunkIndex: Int = 0
|
||||
var totalChunks: Int = 0
|
||||
var data: ByteArray = ByteArray(0)
|
||||
|
||||
override fun getPacketId(): Int = 0x09
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
// В Desktop: readInt16 для index и total
|
||||
chunkIndex = stream.readInt16()
|
||||
totalChunks = stream.readInt16()
|
||||
chunkId = stream.readString()
|
||||
data = stream.readBytes()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
// В Desktop: writeInt16 для index и total
|
||||
stream.writeInt16(chunkIndex)
|
||||
stream.writeInt16(totalChunks)
|
||||
stream.writeString(chunkId)
|
||||
stream.writeBytes(data)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push Token packet (ID: 0x0A) - DEPRECATED
|
||||
* Старый формат, заменен на PacketPushNotification (0x10)
|
||||
*/
|
||||
class PacketPushToken : Packet() {
|
||||
var privateKey: String = ""
|
||||
var publicKey: String = ""
|
||||
var pushToken: String = ""
|
||||
var platform: String = "android" // "android" или "ios"
|
||||
|
||||
override fun getPacketId(): Int = 0x0A
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
privateKey = stream.readString()
|
||||
publicKey = stream.readString()
|
||||
pushToken = stream.readString()
|
||||
platform = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(privateKey)
|
||||
stream.writeString(publicKey)
|
||||
stream.writeString(pushToken)
|
||||
stream.writeString(platform)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push Notification Action
|
||||
*/
|
||||
enum class PushNotificationAction(val value: Int) {
|
||||
SUBSCRIBE(0),
|
||||
UNSUBSCRIBE(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Push Notification packet (ID: 0x10)
|
||||
* Отправка FCM/APNS токена на сервер для push-уведомлений (новый формат)
|
||||
* Совместим с React Native версией
|
||||
*/
|
||||
class PacketPushNotification : Packet() {
|
||||
var notificationsToken: String = ""
|
||||
var action: PushNotificationAction = PushNotificationAction.SUBSCRIBE
|
||||
|
||||
override fun getPacketId(): Int = 0x10
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
notificationsToken = stream.readString()
|
||||
action = when (stream.readInt8()) {
|
||||
1 -> PushNotificationAction.UNSUBSCRIBE
|
||||
else -> PushNotificationAction.SUBSCRIBE
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(notificationsToken)
|
||||
stream.writeInt8(action.value)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request Transport packet (ID: 0x0F)
|
||||
* Запрос адреса транспортного сервера для загрузки/скачивания файлов
|
||||
*/
|
||||
class PacketRequestTransport : Packet() {
|
||||
var transportServer: String = ""
|
||||
|
||||
override fun getPacketId(): Int = 0x0F
|
||||
|
||||
override fun receive(stream: Stream) {
|
||||
transportServer = stream.readString()
|
||||
}
|
||||
|
||||
override fun send(): Stream {
|
||||
val stream = Stream()
|
||||
stream.writeInt16(getPacketId())
|
||||
stream.writeString(transportServer)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.rosetta.messenger.network
|
||||
|
||||
data class SearchUser(
|
||||
val publicKey: String,
|
||||
val title: String,
|
||||
val username: String,
|
||||
val verified: Int,
|
||||
val online: Int
|
||||
)
|
||||
Reference in New Issue
Block a user