diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt index e32531d14..53ac4289e 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt @@ -35,8 +35,8 @@ internal class JceDecoder( } private fun SerialDescriptor.getJceTagId(index: Int): Int { - //println("getTag: ${getElementName(index)}") - return getElementAnnotations(index).filterIsInstance().singleOrNull()?.id + // higher performance, don't use filterIsInstance + return (getElementAnnotations(index).first { it is JceId } as? JceId)?.id ?: error("missing @JceId for ${getElementName(index)} in ${this.serialName}") } @@ -119,11 +119,15 @@ internal class JceDecoder( this@JceDecoder.endStructure(descriptor) } - private var state: Boolean = true - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - this@JceDecoder.pushTag(if (jce.currentHead.tag == 0) JceTagMapEntryKey else JceTagMapEntryValue) - state = !state + println { "MapReader.beginStructure: ${jce.currentHead}" } + this@JceDecoder.pushTag( + when (jce.currentHead.tag) { + 0 -> JceTagMapEntryKey + 1 -> JceTagMapEntryValue + else -> error("illegal map entry head: ${jce.currentHead.tag}") + } + ) return this@JceDecoder.beginStructure(descriptor, *typeParams) } @@ -140,7 +144,7 @@ internal class JceDecoder( override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) } override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - //println("decodeCollectionSize in MapReader: ${descriptor.serialName}") + println { "decodeCollectionSize in MapReader: ${descriptor.serialName}" } // 不读下一个 head return jce.useHead { jce.readJceIntValue(it) } } @@ -148,7 +152,8 @@ internal class JceDecoder( override fun endStructure(descriptor: SerialDescriptor) { - //println("endStructure: $descriptor") + structureHierarchy-- + println { "endStructure: ${descriptor.serialName}" } if (currentTagOrNull?.isSimpleByteArray == true) { jce.prepareNextHead() // read to next head } @@ -173,9 +178,31 @@ internal class JceDecoder( } } + + companion object { + @Suppress("MemberVisibilityCanBePrivate") + val debuggingMode: Boolean by lazy { false } + + private var structureHierarchy: Int = 0 + + inline fun println(value: () -> String) { + if (debuggingMode) { + kotlin.io.println(" ".repeat(structureHierarchy) + value()) + } + } + + @Suppress("NOTHING_TO_INLINE") + inline fun println(value: Any? = "") { + if (debuggingMode) { + kotlin.io.println(" ".repeat(structureHierarchy) + value) + } + } + } + override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - //println() - //println("beginStructure: ${descriptor.serialName}") + println() + println { "beginStructure: ${descriptor.serialName}" } + structureHierarchy++ return when (descriptor.kind) { is PrimitiveKind -> this@JceDecoder @@ -198,7 +225,7 @@ internal class JceDecoder( when (it.type) { Jce.SIMPLE_LIST -> { currentTag.isSimpleByteArray = true - jce.prepareNextHead() // 无用的元素类型 + jce.nextHead() // 无用的元素类型 SimpleByteArrayReader } Jce.LIST -> ListReader @@ -232,23 +259,29 @@ internal class JceDecoder( override fun decodeSequentially(): Boolean = false override fun decodeElementIndex(descriptor: SerialDescriptor): Int { var jceHead = jce.currentHeadOrNull ?: return CompositeDecoder.READ_DONE - if (jceHead.type == Jce.STRUCT_END) { - return CompositeDecoder.READ_DONE - } - while (!jce.input.endOfInput){ + println { "decodeElementIndex: ${jce.currentHead}" } + while (!jce.input.endOfInput) { + if (jceHead.type == Jce.STRUCT_END) { + println { "decodeElementIndex: ${jce.currentHead}" } + return CompositeDecoder.READ_DONE + } + repeat(descriptor.elementsCount) { val tag = descriptor.getJceTagId(it) if (tag == jceHead.tag) { + println { "name=" + descriptor.getElementName(it) } return it } } jce.skipField(jceHead.type) if (!jce.prepareNextHead()) { + println { "decodeElementIndex EOF" } break } jceHead = jce.currentHead + println { "next! $jceHead" } } return CompositeDecoder.READ_DONE // optional support diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt index daf15274a..1a3334fe7 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt @@ -126,6 +126,7 @@ internal class JceInput( Jce.STRING1 -> this.input.discardExact(this.input.readUByte().toInt()) Jce.STRING4 -> this.input.discardExact(this.input.readInt()) Jce.MAP -> { // map + //println("skip map!") nextHead() repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping map" }) { readJceIntValue(it) @@ -136,6 +137,7 @@ internal class JceInput( } } Jce.LIST -> { // list + JceDecoder.println {"skip list!"} nextHead() repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping list" }) { readJceIntValue(it) @@ -146,26 +148,28 @@ internal class JceInput( } } Jce.STRUCT_BEGIN -> { + JceDecoder.println {"skip struct!"} fun skipToStructEnd() { var head: JceHead do { head = nextHead() skipField(head.type) - } while (head.type.toInt() != 11) + } while (head.type != Jce.STRUCT_END) } skipToStructEnd() } Jce.STRUCT_END, Jce.ZERO_TYPE -> { - + Unit } Jce.SIMPLE_LIST -> { - val head = nextHead() - check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type } - this.input.discardExact( - skipToHeadAndUseIfPossibleOrFail(0) { - readJceIntValue(it) - } - ) + JceDecoder.println { "skip simple list!" } + var head = nextHead() + check(head.type == Jce.BYTE) { "bad simple list element type: " + head.type } + check(head.tag == 0) { "simple list element tag must be 0, but was ${head.tag}" } + + head = nextHead() + check(head.tag == 0) { "tag for size for simple list must be 0, but was ${head.tag}" } + this.input.discardExact(readJceIntValue(head)) } else -> error("invalid type: $type") } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt index bb079399e..df38da503 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt @@ -114,7 +114,7 @@ internal class FriendInfo( @JceId(16) val vecIMGroupID: ByteArray? = null, @JceId(17) val vecMSFGroupID: ByteArray? = null, @JceId(18) val iTermType: Int? = null, - @JceId(19) val oVipInfo: VipBaseInfo? = null, + @JceId(19) val oVipInfo: VipBaseInfo? = null, //? bad @JceId(20) val network: Byte? = null, @JceId(21) val vecRing: ByteArray? = null, @JceId(22) val uAbiFlag: Long? = null, @@ -156,12 +156,12 @@ internal class FriendInfo( @Serializable internal class VipBaseInfo( - @JceId(0) val mOpenInfo: Map + @JceId(0) val mOpenInfo: Map? = null // 原本是 0 ) : JceStruct @Serializable internal class VipOpenInfo( - @JceId(0) val open: Boolean, + @JceId(0) val open: Boolean? = false, @JceId(1) val iVipType: Int = -1, @JceId(2) val iVipLevel: Int = -1, @JceId(3) val iVipFlag: Int? = null, diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt index 3733a3b2a..f00ac3cfc 100644 --- a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt +++ b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt @@ -7,6 +7,7 @@ import kotlinx.io.core.toByteArray import kotlinx.io.core.writeFully import kotlinx.serialization.MissingFieldException import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId import net.mamoe.mirai.qqandroid.io.serialization.jce.JceInput @@ -42,6 +43,48 @@ internal const val ZERO_TYPE: Byte = 12 @Suppress("INVISIBLE_MEMBER") // bug internal class JceInputTest { + @Test + fun testIntToStructMap() { + @Serializable + data class VipOpenInfo( + @JceId(0) val open: Boolean? = false, + @JceId(1) val iVipType: Int = -1, + @JceId(2) val iVipLevel: Int = -1, + @JceId(3) val iVipFlag: Int? = null, + @JceId(4) val nameplateId: Long? = null + ) : JceStruct + + @Serializable + data class VipBaseInfo( + @JceId(0) val mOpenInfo: Map? = null + ) : JceStruct + + + @Serializable + data class FriendInfo( + @JceId(0) val friendUin: Long, + @JceId(14) val nick: String = "", + @JceId(19) val oVipInfo: VipBaseInfo? = null, //? bad + @JceId(20) val network: Byte? = null + ) : JceStruct + + val value = FriendInfo( + friendUin = 123, + nick = "h", + oVipInfo = VipBaseInfo( + mapOf( + 1 to VipOpenInfo(true, -1), + 999999999 to VipOpenInfo(true, -1) + ) + ), + network = 1 + ) + assertEquals( + value.toString(), + Jce.UTF_8.load(FriendInfo.serializer(), value.toByteArray(FriendInfo.serializer())).toString() + ) + } + @Test fun testSkippingMap() { @Serializable