4 changed files with 189 additions and 7 deletions
@ -0,0 +1,62 @@ |
|||||||
|
package com.utun.chat.data |
||||||
|
|
||||||
|
import java.nio.ByteBuffer |
||||||
|
import java.nio.ByteOrder |
||||||
|
|
||||||
|
/** Форматы событий общего ядра; все идентификаторы сохраняют 64 бита, включая старший бит. */ |
||||||
|
object AvatarProtocol { |
||||||
|
const val MAX_USER = 1024 * 1024 |
||||||
|
const val MAX_GROUP = 16 * 1024 |
||||||
|
private val filePattern = Regex("[0-9a-f]{64}\\.png") |
||||||
|
fun validFile(file: String): Boolean = filePattern.matches(file) |
||||||
|
fun node(id: String): Long = id.toULongOrNull(16)?.toLong() ?: 0 |
||||||
|
fun hex(id: Long): String = id.toULong().toString(16).padStart(16, '0') |
||||||
|
fun hash(bytes: ByteArray): String = bytes.joinToString("") { "%02x".format(it.toInt() and 255) } |
||||||
|
private fun buffer(data: ByteArray) = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN) |
||||||
|
|
||||||
|
data class Group(val id: Long, val group: Long, val version: Long, val hash: String, |
||||||
|
val canEdit: Boolean, val failed: Boolean, val image: ByteArray) |
||||||
|
fun group(data: ByteArray): Group { |
||||||
|
require(data.size in 60..60 + MAX_GROUP) { "group snapshot length=${data.size}" } |
||||||
|
val b = buffer(data) |
||||||
|
val id = b.long; val group = b.long; val version = b.long |
||||||
|
val hash = ByteArray(32); b.get(hash) |
||||||
|
val canEdit = b.get().toInt(); val failed = b.get().toInt() |
||||||
|
val size = b.short.toInt() and 65535 |
||||||
|
require(canEdit in 0..1 && failed in 0..1 && size == b.remaining()) { "invalid group snapshot header" } |
||||||
|
return Group(id, group, version, hash(hash), canEdit != 0, failed != 0, ByteArray(size).also { b.get(it) }) |
||||||
|
} |
||||||
|
|
||||||
|
data class Reply(val id: Long, val group: Long, val server: Long = 0, val owner: Long = 0, |
||||||
|
val command: Int = 0, val status: Int = 0, val size: Long = 0, val hash: String = "") |
||||||
|
fun response(data: ByteArray): Reply { |
||||||
|
require(data.size >= 141) { "file response length=${data.size}" } |
||||||
|
val b = buffer(data) |
||||||
|
val group = b.long; val server = b.long |
||||||
|
require(b.get().toInt() == 4 && b.get().toInt() == 2) { "invalid file response protocol/kind" } |
||||||
|
val command = b.get().toInt() and 255; val status = b.short.toInt() and 65535 |
||||||
|
val id = b.long; val owner = b.long |
||||||
|
b.long; val size = b.long |
||||||
|
b.position(16 + 77); val hash = ByteArray(32); b.get(hash) |
||||||
|
b.position(16 + 119) |
||||||
|
val path = b.short.toInt() and 65535; val target = b.short.toInt() and 65535; val payload = b.short.toInt() and 65535 |
||||||
|
require(path <= 1024 && target <= 1024 && payload <= 8192 && b.remaining() == path + target + payload) { |
||||||
|
"invalid file response body lengths" |
||||||
|
} |
||||||
|
return Reply(id, group, server, owner, command, status, size, hash(hash)) |
||||||
|
} |
||||||
|
fun transfer(data: ByteArray): Reply? { |
||||||
|
require(data.size == 51) { "transfer length=${data.size}" } |
||||||
|
val b = buffer(data) |
||||||
|
val id = b.long; val group = b.long; val server = b.long; val owner = b.long |
||||||
|
val done = b.long; val total = b.long; val status = b.short.toInt() and 65535 |
||||||
|
val state = b.get().toInt() |
||||||
|
require(state in 1..2 && done >= 0 && total >= 0 && done <= total) { "invalid transfer progress" } |
||||||
|
return if (state == 2) Reply(id, group, server, owner, status = status, size = total) else null |
||||||
|
} |
||||||
|
fun published(data: ByteArray): Reply { |
||||||
|
require(data.size == 17 && data[16].toInt() in 0..1) { "invalid publication result" } |
||||||
|
val b = buffer(data) |
||||||
|
return Reply(b.long, b.long, status = b.get().toInt()) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue