diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Proto.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Proto.kt index c0167c517..b2f3d9005 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Proto.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Proto.kt @@ -1,4 +1,4 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.utils @@ -8,10 +8,14 @@ import kotlinx.io.core.readUInt import kotlinx.io.core.readULong import net.mamoe.mirai.utils.io.UVarInt import net.mamoe.mirai.utils.io.readUVarInt +import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toUHexString +import kotlin.jvm.JvmStatic // ProtoBuf utilities + +@Suppress("FunctionName", "SpellCheckingInspection") /* * Type Meaning Used For * 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum @@ -23,8 +27,6 @@ import net.mamoe.mirai.utils.io.toUHexString * * https://www.jianshu.com/p/f888907adaeb */ - -@Suppress("FunctionName") fun ProtoFieldId(serializedId: UInt): ProtoFieldId = ProtoFieldId(protoFieldNumber(serializedId), protoType(serializedId)) data class ProtoFieldId( @@ -34,7 +36,8 @@ data class ProtoFieldId( override fun toString(): String = "$type $fieldNumber" } -enum class ProtoType(val value: Byte, val typeName: String) { +@Suppress("SpellCheckingInspection") +enum class ProtoType(val value: Byte, private val typeName: String) { /** * int32, int64, uint32, uint64, sint32, sint64, bool, enum */ @@ -63,7 +66,8 @@ enum class ProtoType(val value: Byte, val typeName: String) { /** * fixed32, sfixed32, float */ - BIT_32(0x05, " 32bit"), ; + BIT_32(0x05, " 32bit"), + ; override fun toString(): String = this.typeName @@ -88,12 +92,22 @@ fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3) class ProtoMap(map: MutableMap<ProtoFieldId, Any>) : MutableMap<ProtoFieldId, Any> by map { + companion object { + @JvmStatic + val indent: String = " " + } + override fun toString(): String { - return this.entries.joinToString(prefix = "ProtoMap(\n ", postfix = "\n)", separator = "\n ") { - "${it.key}=" + it.value.contentToString().replace("\n", """\n""") + return this.entries.joinToString(prefix = "ProtoMap(size=$size){\n$indent", postfix = "\n}", separator = "\n$indent") { + "${it.key}=" + it.value.contentToString() } } + fun toStringPrefixed(prefix: String): String { + return this.entries.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent") { + "${it.key}=" + it.value.contentToString(prefix) + } + } /* override fun put(key: ProtoFieldId, value: Any): Any? { println("${key}=" + value.contentToString()) @@ -101,7 +115,7 @@ class ProtoMap(map: MutableMap<ProtoFieldId, Any>) : MutableMap<ProtoFieldId, An }*/ } -fun Any.contentToString(): String = when (this) { +fun Any.contentToString(prefix: String = ""): String = when (this) { is UInt -> "0x" + this.toUHexString("") + "($this)" is UByte -> "0x" + this.toUHexString() + "($this)" is UShort -> "0x" + this.toUHexString("") + "($this)" @@ -116,6 +130,8 @@ fun Any.contentToString(): String = when (this) { is Boolean -> if (this) "true" else "false" is ByteArray -> this.toUHexString()// + " (${this.encodeToString()})" + + is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}" else -> this.toString() } @@ -125,16 +141,27 @@ fun ByteReadPacket.readProtoMap(length: Long = this.remaining): ProtoMap { val expectingRemaining = this.remaining - length while (this.remaining != expectingRemaining) { + require(this.remaining > expectingRemaining) { "Expecting to read $length bytes, but read ${expectingRemaining + length - this.remaining}" } + val id = ProtoFieldId(readUVarInt()) map[id] = when (id.type) { ProtoType.VAR_INT -> UVarInt(readUVarInt()) ProtoType.BIT_32 -> readUInt() ProtoType.BIT_64 -> readULong() - ProtoType.LENGTH_DELIMI -> readBytes(readUVarInt().toInt()) + ProtoType.LENGTH_DELIMI -> tryReadProtoMapOrByteArray(readUVarInt().toInt()) - ProtoType.START_GROUP -> error("unsupported") - ProtoType.END_GROUP -> error("unsupported") + ProtoType.START_GROUP -> Unit + ProtoType.END_GROUP -> Unit } } return map +} + +private fun ByteReadPacket.tryReadProtoMapOrByteArray(length: Int): Any { + val bytes = this.readBytes(length) + return try { + bytes.toReadPacket().readProtoMap().apply { require(none { it.key.type == ProtoType.START_GROUP || it.key.type == ProtoType.END_GROUP }) } + } catch (e: Exception) { + bytes + } } \ No newline at end of file