diff --git a/build.gradle.kts b/build.gradle.kts index 41260dc15..8e78ba4f2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,8 @@ buildscript { classpath("com.android.tools.build:gradle:3.5.3") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") classpath("com.github.jengelman.gradle.plugins:shadow:5.2.0") - classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") + @kotlin.Suppress("GradleDependency") // 1.3.70 有 bug, 无法编译添加 SerialInfo + classpath("org.jetbrains.kotlin:kotlin-serialization:1.3.61") // 不要用 $kotlinVersion classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFuVersion") } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt index a59013888..8f3f2a359 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.SerializationStrategy interface IOFormat : SerialFormat { - fun dump(serializer: SerializationStrategy, input: Input): ByteArray + fun dump(serializer: SerializationStrategy, output: Output): ByteArray - fun load(deserializer: DeserializationStrategy, output: Output): T + fun load(deserializer: DeserializationStrategy, input: Input): T } 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 ab84bc36d..05fd2529a 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 @@ -10,13 +10,14 @@ package net.mamoe.mirai.qqandroid.io.serialization.jce import kotlinx.serialization.* +import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.internal.TaggedDecoder import kotlinx.serialization.modules.SerialModule -import kotlinx.serialization.protobuf.ProtoId +import net.mamoe.mirai.qqandroid.io.serialization.Jce @OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai. -private class JceDecoder( +internal class JceDecoder( val jce: JceInput, override val context: SerialModule ) : TaggedDecoder() { override val updateMode: UpdateMode @@ -25,23 +26,103 @@ private class JceDecoder( override fun SerialDescriptor.getTag(index: Int): JceTag { val annotations = this.getElementAnnotations(index) - val id = (annotations.asSequence().filterIsInstance().firstOrNull()?.id - ?: annotations.asSequence().filterIsInstance().firstOrNull()?.id) // 旧版本兼容 - ?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}") + val id = annotations.filterIsInstance().single().id + // ?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}") + println("getTag: ${this.getElementName(index)}=$id") return JceTag( id, this.getElementDescriptor(index).isNullable ) } - - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - TODO("Not yet implemented") + fun SerialDescriptor.getJceTagId(index: Int): Int { + return getElementAnnotations(index).filterIsInstance().single().id } + + private val ByteArraySerializer = ByteArraySerializer() + + // TODO: 2020/3/6 can be object + private inner class SimpleByteArrayReader() : CompositeDecoder by this { + override fun decodeSequentially(): Boolean = true + override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte { + return jce.input.readByte() + } + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + return 0 + } + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { + return jce.useHead { jce.readJceIntValue(it) } + } + } + + // TODO: 2020/3/6 can be object + private inner class ListReader() : CompositeDecoder by this { + override fun decodeSequentially(): Boolean = false + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + return 0 + } + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { + return jce.useHead { jce.readJceIntValue(it) } + } + } + + override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { + if (descriptor == ByteArraySerializer.descriptor) { + return jce.skipToHeadAndUseIfPossibleOrFail(popTag().id) { + when (it.type) { + Jce.SIMPLE_LIST -> SimpleByteArrayReader() + Jce.LIST -> ListReader() + else -> error("type mismatch. Expected SIMPLE_LIST or LIST, got ${it.type} instead") + } + } + } + return when (descriptor.kind) { + StructureKind.MAP -> { + error("map") + } + StructureKind.LIST -> ListReader() + + else -> this + } + } + + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { + println("decodeSerializableValue: ${deserializer.descriptor}") + return super.decodeSerializableValue(deserializer) + } + + private var currentIndex = 0 + + + override fun decodeSequentially(): Boolean = false override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - TODO("Not yet implemented") + val jceHead = jce.currentHeadOrNull ?: return CompositeDecoder.READ_DONE + repeat(descriptor.elementsCount){ + val tag = descriptor.getJceTagId(it) + if (tag == jceHead.tag) { + return it + } + } + + return CompositeDecoder.READ_DONE // optional support + } + + override fun decodeTaggedNull(tag: JceTag): Nothing? { + println("decodeTaggedNull") + return super.decodeTaggedNull(tag) + } + + override fun decodeNullableSerializableValue(deserializer: DeserializationStrategy): T? { + return super.decodeNullableSerializableValue(deserializer) + } + + override fun decodeTaggedValue(tag: JceTag): Any { + println("decodeTaggedValue") + return super.decodeTaggedValue(tag) } override fun decodeTaggedInt(tag: JceTag): Int = @@ -77,7 +158,6 @@ private class JceDecoder( } override fun decodeTaggedNotNullMark(tag: JceTag): Boolean { - println("!! decodeTaggedNotNullMark: $tag") - return super.decodeTaggedNotNullMark(tag) + return jce.skipToHeadOrNull(tag.id) != null } } 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 9e6edcc11..637551dac 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 @@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.readString /** * Jce Input. 需要手动管理 head. */ -class JceInput( +internal class JceInput( val input: Input, val charset: JceCharset ) { private var _head: JceHead? = null @@ -95,7 +95,7 @@ class JceInput( } tailrec fun skipToHeadOrNull(tag: Int): JceHead? { - val current: JceHead = currentHead // no backing field + val current: JceHead = currentHeadOrNull ?: return null // no backing field return when { current.tag > tag -> null // tag 大了,即找不到 @@ -167,6 +167,7 @@ class JceInput( // region readers fun readJceIntValue(head: JceHead): Int { + println("readJceIntValue: $head") return when (head.type) { Jce.ZERO_TYPE -> 0 Jce.BYTE -> input.readByte().toInt() @@ -197,6 +198,7 @@ class JceInput( } fun readJceByteValue(head: JceHead): Byte { + println("readJceByteValue: $head") return when (head.type) { Jce.ZERO_TYPE -> 0 Jce.BYTE -> input.readByte() diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt index 44169bf37..410b23c3b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt @@ -9,30 +9,15 @@ package net.mamoe.mirai.qqandroid.io.serialization.jce -import io.ktor.utils.io.core.* -import kotlinx.serialization.* -import kotlinx.serialization.internal.TaggedDecoder +import io.ktor.utils.io.core.Input +import io.ktor.utils.io.core.Output +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerialFormat +import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptyModule import kotlinx.serialization.modules.SerialModule -import kotlinx.serialization.protobuf.ProtoId import net.mamoe.mirai.qqandroid.io.serialization.IOFormat -import net.mamoe.mirai.qqandroid.io.serialization.Jce -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.BYTE -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.DOUBLE -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.FLOAT -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.INT -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LIST -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LONG -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.MAP -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SHORT -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SIMPLE_LIST -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING1 -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING4 -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_BEGIN -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_END -import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.ZERO_TYPE import net.mamoe.mirai.qqandroid.io.serialization.JceCharset -import net.mamoe.mirai.utils.io.readString /** * Jce 数据结构序列化和反序列化器. @@ -40,18 +25,19 @@ import net.mamoe.mirai.utils.io.readString * @author Him188 */ class JceNew( - override val context: SerialModule + override val context: SerialModule, + val charset: JceCharset ) : SerialFormat, IOFormat { - companion object Default : IOFormat by JceNew( - EmptyModule - ) - - override fun dump(serializer: SerializationStrategy, input: Input): ByteArray { + override fun dump(serializer: SerializationStrategy, output: Output): ByteArray { TODO("Not yet implemented") } - override fun load(deserializer: DeserializationStrategy, output: Output): T { - TODO("Not yet implemented") + override fun load(deserializer: DeserializationStrategy, input: Input): T { + return JceDecoder(JceInput(input, charset), context).decodeSerializableValue(deserializer) } + companion object { + val UTF_8 = JceNew(EmptyModule, JceCharset.UTF8) + val GBK = JceNew(EmptyModule, JceCharset.GBK) + } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt index eefd29041..a1f39126d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt @@ -18,6 +18,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.Jce * 标注 JCE 序列化时使用的 ID */ @SerialInfo +@Target(AnnotationTarget.PROPERTY) annotation class JceId(val id: Int) /** @@ -31,6 +32,9 @@ internal data class JceTag( val isNullable: Boolean ) +fun JceHead.checkType(type: Byte) { + check(this.type == type) {"type mismatch. Expected $type, actual ${this.type}"} +} @PublishedApi internal fun Output.writeJceHead(type: Byte, tag: Int) { 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 6cc28b32c..54a438c73 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 @@ -1,9 +1,12 @@ -@file:Suppress("unused") +@file:Suppress("unused", "DEPRECATION_ERROR") package net.mamoe.mirai.qqandroid.io.serialization import io.ktor.utils.io.core.* +import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId import net.mamoe.mirai.qqandroid.io.serialization.jce.JceInput +import net.mamoe.mirai.qqandroid.io.serialization.jce.JceNew import net.mamoe.mirai.qqandroid.io.serialization.jce.writeJceHead import kotlin.test.Test import kotlin.test.assertEquals @@ -35,6 +38,39 @@ internal const val ZERO_TYPE: Byte = 12 */ @Suppress("INVISIBLE_MEMBER") // bug internal class JceInputTest { + @Serializable + data class TestSerializableClassA( + @JceId(0) val byte: Byte = 66, + @JceId(1) val short: Short = 123, + @JceId(3) val int: Int = 123456, + @JceId(8) val float: Float = 123f, + @JceId(15) val long: Long = 123456789123456789L, + @JceId(16) val double: Double = 123456.0, + @JceId(17) val boolean: Boolean = true, + @JceId(11111) val nullable: Int? = null + ) + + @Test + fun testSerializableClassA() { + val input = buildPacket { + writeJceHead(BYTE, 0) + writeByte(66) + writeJceHead(SHORT, 1) + writeShort(123) + writeJceHead(INT, 3) + writeInt(123456) + writeJceHead(FLOAT, 8) + writeFloat(123f) + writeJceHead(LONG, 15) + writeLong(123456789123456789L) + writeJceHead(DOUBLE, 16) + writeDouble(123456.0) + writeJceHead(BYTE, 17) + writeByte(1) // boolean + } + + assertEquals(TestSerializableClassA(), JceNew.UTF_8.load(TestSerializableClassA.serializer(), input)) + } @Test fun testHeadSkip() {