diff --git a/build.gradle.kts b/build.gradle.kts index 7d2ef354c..415386428 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,7 @@ allprojects { repositories { maven(url = "https://mirrors.huaweicloud.com/repository/maven") maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") + maven(url = "https://dl.bintray.com/him188moe/jcekt") jcenter() google() } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index d48b87a6b..5d39b82fa 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -24,6 +24,8 @@ object Versions { const val dokka = "0.10.1" } + const val jcekt = "1.0.0" + object Android { const val androidGradlePlugin = "3.5.3" } diff --git a/mirai-core-qqandroid/build.gradle.kts b/mirai-core-qqandroid/build.gradle.kts index e579b4b46..2778938e5 100644 --- a/mirai-core-qqandroid/build.gradle.kts +++ b/mirai-core-qqandroid/build.gradle.kts @@ -51,6 +51,7 @@ kotlin { api(kotlin("stdlib", Versions.Kotlin.stdlib)) api(kotlinx("serialization-runtime-common", Versions.Kotlin.serialization)) api(kotlinx("serialization-protobuf-common", Versions.Kotlin.serialization)) + api("moe.him188:jcekt-common:${Versions.jcekt}") api("org.jetbrains.kotlinx:atomicfu:${Versions.Kotlin.atomicFU}") api(kotlinx("io", Versions.Kotlin.io)) api(kotlinx("coroutines-io", Versions.Kotlin.coroutinesIo)) @@ -86,6 +87,7 @@ kotlin { dependencies { runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE // api(kotlinx("coroutines-debug", "1.3.5")) + api("moe.him188:jcekt:${Versions.jcekt}") api(kotlinx("serialization-runtime", Versions.Kotlin.serialization)) //api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization)) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt index c1f86c96d..296a697d2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt @@ -10,9 +10,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable 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 b78e6b4d0..58d2267e4 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 @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt index 73def5748..aaf198b5a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt @@ -1,8 +1,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt index 7f62e4ad7..72cba5272 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt @@ -1,9 +1,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt index 5359dc44a..6dad2bb9d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField internal class OnlinePushPack { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt index 2b827da3b..dec460165 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt @@ -10,10 +10,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Suppress("ArrayInDataClass") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt index 474ecffe1..e9ebbf658 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt @@ -1,8 +1,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt index bb8d0b2b3..eba2a751c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt @@ -10,9 +10,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField private val EMPTY_MAP = mapOf() diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt index a8c31cefe..4a3f05ea3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt index fb44f55e1..24147c7a2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt index ed692d803..c87f3f9e6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt index 8fddfddbc..52733ddf2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login import kotlinx.io.core.ByteReadPacket +import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.Packet @@ -151,7 +152,7 @@ internal class StatSvc { var44.strVendorName = ROMUtil.getRomName(); var44.strVendorOSName = ROMUtil.getRomVersion(20); */ - bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump( + bytes_0x769_reqbody = ProtoBuf.dump( Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody( rpt_config_list = listOf( Oidb0x769.ConfigSeq( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/IOFormat.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/IOFormat.kt deleted file mode 100644 index 642cad758..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/IOFormat.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.mamoe.mirai.qqandroid.utils.io.serialization - -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerialFormat -import kotlinx.serialization.SerializationStrategy - -internal interface IOFormat : SerialFormat { - - fun dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) - - fun load(deserializer: DeserializationStrategy, input: Input): T -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/JceOld.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/JceOld.kt deleted file mode 100644 index b850ffa56..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/JceOld.kt +++ /dev/null @@ -1,850 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.qqandroid.utils.io.serialization - -import kotlinx.io.charsets.Charset -import kotlinx.io.core.* -import kotlinx.serialization.* -import kotlinx.serialization.builtins.ByteArraySerializer -import kotlinx.serialization.builtins.MapEntrySerializer -import kotlinx.serialization.builtins.SetSerializer -import kotlinx.serialization.internal.* -import kotlinx.serialization.modules.EmptyModule -import kotlinx.serialization.modules.SerialModule -import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf -import net.mamoe.mirai.qqandroid.utils.io.readString -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.BYTE -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.DOUBLE -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.FLOAT -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.INT -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.JCE_MAX_STRING_LENGTH -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.LIST -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.LONG -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.MAP -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.SHORT -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.SIMPLE_LIST -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRING1 -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRING4 -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRUCT_BEGIN -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRUCT_END -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.ZERO_TYPE -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceHead -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId -import net.mamoe.mirai.qqandroid.utils.toReadPacket - -@PublishedApi -internal val CharsetGBK = Charset.forName("GBK") - -@PublishedApi -internal val CharsetUTF8 = Charset.forName("UTF8") - -internal enum class JceCharset(val kotlinCharset: Charset) { - GBK(Charset.forName("GBK")), - UTF8(Charset.forName("UTF8")) -} - -internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation(index)?.id - -/** - * Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray] - */ -@Suppress("DEPRECATION_ERROR") -@OptIn(InternalSerializationApi::class) -internal class JceOld private constructor(private val charset: JceCharset, override val context: SerialModule = EmptyModule) : - SerialFormat, BinaryFormat { - - private inner class ListWriter( - private val count: Int, - private val tag: Int, - private val parentEncoder: JceEncoder - ) : JceEncoder(BytePacketBuilder()) { - override fun SerialDescriptor.getTag(index: Int): Int { - return 0 - } - - override fun endEncode(descriptor: SerialDescriptor) { - parentEncoder.writeHead(LIST, this.tag) - parentEncoder.encodeTaggedInt(0, count) - parentEncoder.output.writePacket(this.output.build()) - } - } - - private inner class JceMapWriter( - output: BytePacketBuilder - ) : JceEncoder(output) { - override fun SerialDescriptor.getTag(index: Int): Int { - return if (index % 2 == 0) 0 else 1 - } - - /* - override fun endEncode(desc: SerialDescriptor) { - parentEncoder.writeHead(MAP, this.tag) - parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count) - // println(this.output.toByteArray().toUHexString()) - parentEncoder.output.write(this.output.toByteArray()) - }*/ - - override fun beginCollection( - descriptor: SerialDescriptor, - collectionSize: Int, - vararg typeSerializers: KSerializer<*> - ): CompositeEncoder { - return this - } - - override fun beginStructure( - descriptor: SerialDescriptor, - vararg typeSerializers: KSerializer<*> - ): CompositeEncoder { - return this - } - } - - /** - * From: com.qq.taf.jce.JceOutputStream - */ - @Suppress("unused", "MemberVisibilityCanBePrivate") - @OptIn(ExperimentalIoApi::class) - private open inner class JceEncoder( - internal val output: BytePacketBuilder - ) : TaggedEncoder() { - override val context get() = this@JceOld.context - - override fun SerialDescriptor.getTag(index: Int): Int { - return getSerialId(this, index) ?: error("cannot find @SerialId") - } - - /** - * 序列化最开始的时候的 - */ - override fun beginStructure( - descriptor: SerialDescriptor, - vararg typeSerializers: KSerializer<*> - ): CompositeEncoder = - when (descriptor.kind) { - StructureKind.LIST -> this - StructureKind.MAP -> this - StructureKind.CLASS, StructureKind.OBJECT -> this - is PolymorphicKind -> this - else -> throw SerializationException("Primitives are not supported at top-level") - } - - @OptIn(ImplicitReflectionSerializer::class) - @Suppress("UNCHECKED_CAST", "NAME_SHADOWING") - override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) = when { - serializer.descriptor.kind == StructureKind.MAP -> { - try { - val entries = (value as Map<*, *>).entries - val serializer = (serializer as MapLikeSerializer) - val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) - - this.writeHead(MAP, currentTag) - this.encodeTaggedInt(0, entries.count()) - SetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries) - } catch (e: Exception) { - super.encodeSerializableValue(serializer, value) - } - } - serializer.descriptor.kind == StructureKind.LIST - && value is ByteArray -> encodeTaggedByteArray(popTag(), value as ByteArray) - serializer.descriptor.kind == StructureKind.LIST - && serializer.descriptor.getElementDescriptor(0) is PrimitiveKind -> { - serializer.serialize( - ListWriter( - when (value) { - is ShortArray -> value.size - is IntArray -> value.size - is LongArray -> value.size - is FloatArray -> value.size - is DoubleArray -> value.size - is CharArray -> value.size - is ByteArray -> value.size - is BooleanArray -> value.size - else -> error("unknown array type: ${value.getClassName()}") - }, popTag(), this - ), - value - ) - } - serializer.descriptor.kind == StructureKind.LIST && value is Array<*> -> { - if (serializer.descriptor.getElementDescriptor(0).kind is PrimitiveKind.BYTE) { - encodeTaggedByteArray(popTag(), (value as Array).toByteArray()) - } else - serializer.serialize( - ListWriter((value as Array<*>).size, popTag(), this), - value - ) - } - serializer.descriptor.kind == StructureKind.LIST -> { - serializer.serialize( - ListWriter((value as Collection<*>).size, popTag(), this), - value - ) - } - else -> { - if (value is JceStruct) { - if (currentTagOrNull == null) { - serializer.serialize(this, value) - } else { - this.writeHead(STRUCT_BEGIN, popTag()) - serializer.serialize(JceEncoder(this.output), value) - this.writeHead(STRUCT_END, 0) - } - } else if (value is ProtoBuf) { - this.encodeTaggedByteArray(popTag(), ProtoBufWithNullableSupport.dump(value)) - } else { - serializer.serialize(this, value) - } - } - } - - override fun encodeTaggedByte(tag: Int, value: Byte) { - if (value.toInt() == 0) { - writeHead(ZERO_TYPE, tag) - } else { - writeHead(BYTE, tag) - output.writeByte(value) - } - } - - override fun encodeTaggedShort(tag: Int, value: Short) { - if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) { - encodeTaggedByte(tag, value.toByte()) - } else { - writeHead(SHORT, tag) - output.writeShort(value) - } - } - - override fun encodeTaggedInt(tag: Int, value: Int) { - if (value in Short.MIN_VALUE..Short.MAX_VALUE) { - encodeTaggedShort(tag, value.toShort()) - } else { - writeHead(INT, tag) - output.writeInt(value) - } - } - - override fun encodeTaggedFloat(tag: Int, value: Float) { - writeHead(FLOAT, tag) - output.writeFloat(value) - } - - override fun encodeTaggedDouble(tag: Int, value: Double) { - writeHead(DOUBLE, tag) - output.writeDouble(value) - } - - override fun encodeTaggedLong(tag: Int, value: Long) { - if (value in Int.MIN_VALUE..Int.MAX_VALUE) { - encodeTaggedInt(tag, value.toInt()) - } else { - writeHead(LONG, tag) - output.writeLong(value) - } - } - - override fun encodeTaggedBoolean(tag: Int, value: Boolean) { - encodeTaggedByte(tag, if (value) 1 else 0) - } - - override fun encodeTaggedChar(tag: Int, value: Char) { - encodeTaggedByte(tag, value.toByte()) - } - - override fun encodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor, ordinal: Int) { - encodeTaggedInt(tag, ordinal) - } - - override fun encodeTaggedNull(tag: Int) { - } - - override fun encodeTaggedUnit(tag: Int) { - encodeTaggedNull(tag) - } - - fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) { - writeHead(SIMPLE_LIST, tag) - writeHead(BYTE, 0) - encodeTaggedInt(0, bytes.size) - output.writeFully(bytes) - } - - override fun encodeTaggedString(tag: Int, value: String) { - require(value.length <= JCE_MAX_STRING_LENGTH) { "string is too long for tag $tag" } - val array = value.toByteArray(charset.kotlinCharset) - if (array.size > 255) { - writeHead(STRING4, tag) - output.writeInt(array.size) - output.writeFully(array) - } else { - writeHead(STRING1, tag) - output.writeByte(array.size.toByte()) // one byte - output.writeFully(array) - } - } - - override fun encodeTaggedValue(tag: Int, value: Any) { - when (value) { - is Byte -> encodeTaggedByte(tag, value) - is Short -> encodeTaggedShort(tag, value) - is Int -> encodeTaggedInt(tag, value) - is Long -> encodeTaggedLong(tag, value) - is Float -> encodeTaggedFloat(tag, value) - is Double -> encodeTaggedDouble(tag, value) - is Boolean -> encodeTaggedBoolean(tag, value) - is String -> encodeTaggedString(tag, value) - is Unit -> { - } - else -> error("unsupported type: ${value.getClassName()}") - } - } - - @PublishedApi - internal fun writeHead(type: Byte, tag: Int) { - if (tag < 15) { - this.output.writeByte(((tag shl 4) or type.toInt()).toByte()) - return - } - if (tag < 256) { - this.output.writeByte((type.toInt() or 0xF0).toByte()) - this.output.writeByte(tag.toByte()) - return - } - error("tag is too large: $tag") - } - } - - private open inner class JceMapReader( - val size: Int, - input: JceInput - ) : JceDecoder(input) { - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - return size - } - - override fun SerialDescriptor.getTag(index: Int): Int { - // 奇数 0, 即 key; 偶数 1, 即 value - return if (index % 2 == 0) 0 else 1 - } - } - - private open inner class JceListReader( - val size: Int, - input: JceInput - ) : JceDecoder(input) { - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - return size - } - - override fun SerialDescriptor.getTag(index: Int): Int { - return 0 - } - } - - private open inner class JceStructReader( - input: JceInput - ) : JceDecoder(input) { - override fun endStructure(descriptor: SerialDescriptor) { - - } - } - - private open inner class NullReader( - input: JceInput - ) : JceDecoder(input) - - private open inner class JceDecoder( - internal val input: JceInput - ) : TaggedDecoder() { - override fun SerialDescriptor.getTag(index: Int): Int { - return getSerialId(this, index) ?: error("cannot find tag with index $index") - } - - override fun decodeTaggedByte(tag: Int): Byte = input.readByte(tag) - override fun decodeTaggedShort(tag: Int): Short = input.readShort(tag) - override fun decodeTaggedInt(tag: Int): Int = input.readInt(tag) - override fun decodeTaggedLong(tag: Int): Long = input.readLong(tag) - override fun decodeTaggedFloat(tag: Int): Float = input.readFloat(tag) - override fun decodeTaggedDouble(tag: Int): Double = input.readDouble(tag) - override fun decodeTaggedChar(tag: Int): Char = input.readByte(tag).toChar() - override fun decodeTaggedString(tag: Int): String = input.readString(tag) - override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag) - - override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int { - return input.readInt(tag) - } - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - return 0 - } - - /** - * 在 [KSerializer.serialize] 前 - */ - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - //// println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}") - when { - // 由于 Byte 的数组有两种方式写入, 需特定读取器 - descriptor.kind == StructureKind.LIST - && descriptor.getElementDescriptor(0).kind == PrimitiveKind.BYTE -> { - // ByteArray, 交给 decodeSerializableValue 进行处理 - return this - } - descriptor.kind == StructureKind.LIST -> { - // if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) { - // // Array - // return this // 交给 decodeSerializableValue - // } - - val tag = currentTagOrNull - @Suppress("SENSELESS_COMPARISON") // 推断 bug - if (tag != null && input.skipToTagOrNull(tag) { - popTag() - if (it.type == SIMPLE_LIST) { - input.readHead() // list 里面元素类型, 没必要知道 - } - return when (it.type) { - SIMPLE_LIST, LIST -> JceListReader(input.readInt(0), this.input) - MAP -> JceMapReader(input.readInt(0), this.input) - else -> error("type mismatch") - } - } == null && descriptor.isNullable) { - return NullReader(this.input) - } - } - - descriptor.kind == StructureKind.MAP -> { - val tag = currentTagOrNull - if (tag != null) { - popTag() - } - return JceMapReader(input.readInt(0), this.input) - } - } - - val tag = currentTagOrNull - val jceHead = input.peakHeadOrNull() - if (tag != null && (jceHead == null || jceHead.tag > tag)) { - return NullReader(this.input) - } - - return super.beginStructure(descriptor, *typeParams) - } - - override fun decodeTaggedNull(tag: Int): Nothing? { - return null - } - - override fun decodeTaggedNotNullMark(tag: Int): Boolean { - return !isTagMissing(tag) - } - - fun isTagMissing(tag: Int): Boolean { - val head = input.peakHeadOrNull() - return input.isEndOfInput || head == null || head.tag > tag - } - - @Suppress("UNCHECKED_CAST") - override fun decodeNullableSerializableValue(deserializer: DeserializationStrategy): T? { - // - println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}") - if (deserializer is NullReader) { - return null - } - currentTagOrNull?.let { - if (this.isTagMissing(it)) { - return null - } - } - when { - deserializer.descriptor == ByteArraySerializer().descriptor -> { - val tag = popTag() - return if (isTagMissing(tag)) input.readByteArrayOrNull(tag) as? T - else input.readByteArray(tag) as T - } - deserializer.descriptor.kind == StructureKind.LIST -> { - if (deserializer is ReferenceArraySerializer<*, *> - && (deserializer as ListLikeSerializer).typeParams.isNotEmpty() - && (deserializer as ListLikeSerializer).typeParams[0] is ByteSerializer - ) { - val tag = popTag() - return if (isTagMissing(tag)) input.readByteArrayOrNull(tag)?.toTypedArray() as? T - else input.readByteArray(tag).toTypedArray() as T - } else if (deserializer is ArrayListSerializer<*> - && (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty() - && (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer - ) { - val tag = popTag() - return if (isTagMissing(tag)) input.readByteArrayOrNull(tag)?.toMutableList() as? T - else input.readByteArray(tag).toMutableList() as T - } - val tag = currentTag -// // println(tag) - @Suppress("SENSELESS_COMPARISON") // false positive - if (input.skipToTagOrNull(tag) { - return deserializer.deserialize(JceListReader(input.readInt(0), input)) - } == null) { - if (isTagMissing(tag)) { - return null - } else error("property is notnull but cannot find tag $tag") - } - error("UNREACHABLE CODE") - } - deserializer.descriptor.kind == StructureKind.MAP -> { - val tag = popTag() - @Suppress("SENSELESS_COMPARISON") - if (input.skipToTagOrNull(tag) { head -> - check(head.type == MAP) { "type mismatch: ${head.type}" } - // 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入. - val serializer = (deserializer as MapLikeSerializer) - val mapEntrySerial = - MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) - val setOfEntries = - SetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input)) - return setOfEntries.associateBy({ it.key }, { it.value }) as T - } == null) { - if (isTagMissing(tag)) { - return null - } else error("property is notnull but cannot find tag $tag") - } - error("UNREACHABLE CODE") - } - } - - if (deserializer.descriptor.kind == StructureKind.CLASS || deserializer.descriptor.kind == StructureKind.OBJECT) { - val tag = currentTagOrNull - if (tag != null) { - @Suppress("SENSELESS_COMPARISON") // 推断 bug - if (input.skipToTagOrNull(tag) { - check(it.type == STRUCT_BEGIN) { "type mismatch: ${it.type}" } - //popTag() - return deserializer.deserialize(JceStructReader(input)).also { - while (input.input.canRead() && input.peakHeadOrNull()?.type != STRUCT_END) { - input.readHeadOrNull() ?: return@also - } - input.readHeadOrNull() - } - } == null && isTagMissing(tag)) { - return null - } else error("cannot find tag $tag") - } - - return deserializer.deserialize(JceDecoder(this.input)) - } - - val tag = currentTagOrNull ?: return deserializer.deserialize(JceDecoder(this.input)) - return if (!this.isTagMissing(tag)) { - try { - deserializer.deserialize(this) - } catch (e: Exception) { - println("exception when tag=$tag") - throw e - } - } else { - // popTag() - null - } - } - - @Suppress("UNCHECKED_CAST") - override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { - return decodeNullableSerializableValue(deserializer as DeserializationStrategy) as? T - ?: error("value with tag $currentTagOrNull(by ${deserializer.getClassName()}) is not optional but cannot find. currentJceHead = ${input.currentJceHead}") - } - } - - - @OptIn(ExperimentalUnsignedTypes::class) - internal inner class JceInput( - @PublishedApi - internal val input: ByteReadPacket, - maxReadSize: Long = input.remaining - ) : Closeable { - private val leastRemaining = input.remaining - maxReadSize - internal val isEndOfInput: Boolean get() = input.remaining <= leastRemaining - - internal var currentJceHead: JceHead? = input.doReadHead() - - override fun close() = input.close() - - internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() - - @PublishedApi - internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") - - @PublishedApi - internal fun readHeadOrNull(): JceHead? = input.doReadHead() - - /** - * 读取下一个 head 存储到 [currentJceHead] - */ - private fun ByteReadPacket.doReadHead(): JceHead? { - if (isEndOfInput) { - currentJceHead = null - // println("doReadHead: endOfInput") - return null - } - val var2 = readUByte() - val type = var2 and 15u - var tag = var2.toUInt() shr 4 - if (tag == 15u) { - if (isEndOfInput) { - currentJceHead = null - // println("doReadHead: endOfInput2") - return null - } - tag = readUByte().toUInt() - } - currentJceHead = JceHead( - tag = tag.toInt(), - type = type.toByte() - ) - // println("doReadHead: $currentJceHead") - return currentJceHead - } - - fun readBoolean(tag: Int): Boolean = - readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readByte(tag: Int): Byte = - readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readShort(tag: Int): Short = - readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - fun readLong(tag: Int): Long = - readLongOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readFloat(tag: Int): Float = - readFloatOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readDouble(tag: Int): Double = - readDoubleOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readString(tag: Int): String = - readStringOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readByteArray(tag: Int): ByteArray = - readByteArrayOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) { - when (it.type) { - LIST -> ByteArray(readInt(0)) { readByte(0) } - SIMPLE_LIST -> { - val head = readHead() - readHead() - check(head.type.toInt() == 0) { "type mismatch, expected=0(Byte), got=${head.type}" } - input.readBytes(readInt(0)) - } - else -> error("type mismatch, expected=9(List), got=${it.type}") - } - } - - private fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head -> - return when (head.type) { - STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset) - STRING4 -> input.readString( - input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, - charset = charset.kotlinCharset - ) - else -> error("type mismatch: ${head.type}, expecting 6 or 7 (for string)") - } - } - - private fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) { - return when (it.type) { - ZERO_TYPE -> 0 - BYTE -> input.readByte().toLong() - SHORT -> input.readShort().toLong() - INT -> input.readInt().toLong() - LONG -> input.readLong() - else -> error("type mismatch ${it.type} when reading tag $tag") - } - } - - private fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0 - 0 -> input.readByte().toShort() - 1 -> input.readShort() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0 - 0 -> input.readByte().toInt() - 1 -> input.readShort().toInt() - 2 -> input.readInt() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0 - 0 -> input.readByte() - else -> error("type mismatch") - } - } - - private fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0f - 4 -> input.readFloat() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0.0 - 4 -> input.readFloat().toDouble() - 5 -> input.readDouble() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 } - - - private fun skipField() { - skipField(readHead().type) - } - - private fun skipToStructEnd() { - var head: JceHead - do { - head = readHead() - skipField(head.type) - } while (head.type.toInt() != 11) - } - - @OptIn(ExperimentalUnsignedTypes::class) - @PublishedApi - internal fun skipField(type: Byte) = when (type.toInt()) { - 0 -> this.input.discardExact(1) - 1 -> this.input.discardExact(2) - 2 -> this.input.discardExact(4) - 3 -> this.input.discardExact(8) - 4 -> this.input.discardExact(4) - 5 -> this.input.discardExact(8) - 6 -> this.input.discardExact(this.input.readUByte().toInt()) - 7 -> this.input.discardExact(this.input.readInt()) - 8 -> { // map - repeat(this.readInt(0) * 2) { - skipField() - } - } - 9 -> { // list - repeat(this.readInt(0)) { - skipField() - } - } - 10 -> this.skipToStructEnd() - 11, 12 -> { - - } - 13 -> { - val head = readHead() - check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type } - this.input.discardExact(this.readInt(0)) - } - else -> error("invalid type: $type") - } - - } - - @Suppress("MemberVisibilityCanBePrivate") - companion object { - val UTF8 = - JceOld(JceCharset.UTF8) - val GBK = - JceOld(JceCharset.GBK) - - fun byCharSet(c: JceCharset): JceOld { - return if (c == JceCharset.UTF8) { - UTF8 - } else { - GBK - } - } - - private fun Any?.getClassName(): String = - (if (this == null) Unit::class else this::class).qualifiedName?.split(".")?.takeLast(2)?.joinToString(".") - ?: "" - } - - fun dumpAsPacket(serializer: SerializationStrategy, obj: T): ByteReadPacket { - val encoder = BytePacketBuilder() - val dumper = JceEncoder(encoder) - dumper.encode(serializer, obj) - return encoder.build() - } - - override fun dump(serializer: SerializationStrategy, value: T): ByteArray { - return dumpAsPacket(serializer, value).readBytes() - } - - /** - * 注意 close [packet]!! - */ - fun load( - deserializer: DeserializationStrategy, - packet: ByteReadPacket, - length: Int = packet.remaining.toInt() - ): T { - return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer) - } - - override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T { - return bytes.toReadPacket().use { - val decoder = JceDecoder(JceInput(it)) - decoder.decode(deserializer) - } - } -} - -internal inline fun JceOld.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { - // println("skipping to $tag start") - while (true) { - if (isEndOfInput) { // 读不了了 - currentJceHead = null - // println("skipping to $tag: endOfInput") - return null - } - - var head = currentJceHead - if (head == null) { // 没有新的 head 了 - head = readHeadOrNull() ?: return null - } - - if (head.tag > tag) { - // println("skipping to $tag: head.tag > tag") - return null - } - // readHead() - if (head.tag == tag) { - // readHeadOrNull() - currentJceHead = null - // println("skipping to $tag: run block") - return block(head) - } - - // println("skipping to $tag: tag not matching") - // println("skipping to $tag: skipField") - this.skipField(head.type) - currentJceHead = readHeadOrNull() - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt index 66d45fad8..565030251 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt @@ -23,10 +23,13 @@ import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoType +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint internal typealias ProtoDesc = Pair +internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation(index)?.id + internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefault: Boolean = false): ProtoDesc { val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1) val format = desc.findAnnotation(index)?.type diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/README.md b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/README.md deleted file mode 100644 index c82a41a5c..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# io.serialization - -**序列化支持** - -包含: -- QQ 的 JceStruct 相关的全自动序列化和反序列化: [Jce.kt](jce/JceNew.kt) -- Protocol Buffers 的 optional 支持: [ProtoBufWithNullableSupport.kt](ProtoBufWithNullableSupport.kt) - -其中, `ProtoBufWithNullableSupport` 的绝大部分源码来自 `kotlinx.serialization`. 原著权归该项目作者所有. -Mirai 所做的修改已经标记上了 `MIRAI MODIFY START` \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceDecoder.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceDecoder.kt deleted file mode 100644 index 58c2b7dbd..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceDecoder.kt +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("PrivatePropertyName") - -package net.mamoe.mirai.qqandroid.utils.io.serialization.jce - -import kotlinx.serialization.* -import kotlinx.serialization.builtins.AbstractDecoder -import kotlinx.serialization.internal.TaggedDecoder -import kotlinx.serialization.modules.SerialModule - - -@OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai. -internal class JceDecoder( - val jce: JceInput, override val context: SerialModule -) : TaggedDecoder() { - override val updateMode: UpdateMode - get() = UpdateMode.BANNED - - override fun SerialDescriptor.getTag(index: Int): JceTag { - val annotations = this.getElementAnnotations(index) - - 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 JceTagCommon(id) - } - - private fun SerialDescriptor.getJceTagId(index: Int): Int { - // higher performance, don't use filterIsInstance - val annotation = getElementAnnotations(index).firstOrNull { it is JceId } - ?: error("missing @JceId for ${getElementName(index)} in ${this.serialName}") - return (annotation as JceId).id - - } - - private val SimpleByteArrayReader: SimpleByteArrayReaderImpl = SimpleByteArrayReaderImpl() - - private inner class SimpleByteArrayReaderImpl : AbstractDecoder() { - override fun decodeSequentially(): Boolean = true - - override fun endStructure(descriptor: SerialDescriptor) { - this@JceDecoder.endStructure(descriptor) - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - this@JceDecoder.pushTag(JceTagListElement) - return this@JceDecoder.beginStructure(descriptor, *typeParams) - } - - override fun decodeByte(): Byte = jce.input.readByte() - override fun decodeShort(): Short = error("illegal access") - override fun decodeInt(): Int = error("illegal access") - override fun decodeLong(): Long = error("illegal access") - override fun decodeFloat(): Float = error("illegal access") - override fun decodeDouble(): Double = error("illegal access") - override fun decodeBoolean(): Boolean = error("illegal access") - override fun decodeChar(): Char = error("illegal access") - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = error("illegal access") - override fun decodeString(): String = error("illegal access") - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - error("should not be reached") - } - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - // 不要读下一个 head - return jce.currentHead.let { jce.readJceIntValue(it) } - } - } - - private val ListReader: ListReaderImpl = ListReaderImpl() - - private inner class ListReaderImpl : AbstractDecoder() { - override fun decodeSequentially(): Boolean = true - override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("should not be reached") - override fun endStructure(descriptor: SerialDescriptor) { - this@JceDecoder.endStructure(descriptor) - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - this@JceDecoder.pushTag(JceTagListElement) - - return this@JceDecoder.beginStructure(descriptor, *typeParams) - } - - override fun decodeByte(): Byte = jce.useHead { jce.readJceByteValue(it) } - override fun decodeShort(): Short = jce.useHead { jce.readJceShortValue(it) } - override fun decodeInt(): Int = jce.useHead { jce.readJceIntValue(it) } - override fun decodeLong(): Long = jce.useHead { jce.readJceLongValue(it) } - override fun decodeFloat(): Float = jce.useHead { jce.readJceFloatValue(it) } - override fun decodeDouble(): Double = jce.useHead { jce.readJceDoubleValue(it) } - override fun decodeBoolean(): Boolean = jce.useHead { jce.readJceBooleanValue(it) } - override fun decodeChar(): Char = decodeByte().toChar() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() - override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) } - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - //println("decodeCollectionSize: ${descriptor.serialName}") - // 不读下一个 head - return jce.useHead { jce.readJceIntValue(it) } - } - } - - - private val MapReader: MapReaderImpl = MapReaderImpl() - - private inner class MapReaderImpl : AbstractDecoder() { - override fun decodeSequentially(): Boolean = true - override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("stub") - - override fun endStructure(descriptor: SerialDescriptor) { - this@JceDecoder.endStructure(descriptor) - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - 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) - } - - override fun decodeByte(): Byte = jce.useHead { jce.readJceByteValue(it) } - override fun decodeShort(): Short = jce.useHead { jce.readJceShortValue(it) } - override fun decodeInt(): Int = jce.useHead { jce.readJceIntValue(it) } - override fun decodeLong(): Long = jce.useHead { jce.readJceLongValue(it) } - override fun decodeFloat(): Float = jce.useHead { jce.readJceFloatValue(it) } - override fun decodeDouble(): Double = jce.useHead { jce.readJceDoubleValue(it) } - - override fun decodeBoolean(): Boolean = jce.useHead { jce.readJceBooleanValue(it) } - override fun decodeChar(): Char = decodeByte().toChar() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() - override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) } - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - println { "decodeCollectionSize in MapReader: ${descriptor.serialName}" } - // 不读下一个 head - return jce.useHead { jce.readJceIntValue(it) } - } - } - - - override fun endStructure(descriptor: SerialDescriptor) { - structureHierarchy-- - println { "endStructure: ${descriptor.serialName}" } - if (currentTagOrNull?.isSimpleByteArray == true) { - jce.prepareNextHead() // read to next head - } - if (descriptor.kind == StructureKind.CLASS) { - if (currentTagOrNull == null) { - return - } - while (true) { - val currentHead = jce.currentHeadOrNull ?: return - if (currentHead.type == Jce.STRUCT_END) { - jce.prepareNextHead() - //println("current end") - break - } - //println("current $currentHead") - jce.skipField(currentHead.type) - jce.prepareNextHead() - } - // pushTag(JceTag(0, true)) - // skip STRUCT_END - // popTag() - } - } - - - companion object { - @Suppress("MemberVisibilityCanBePrivate") - var debuggingMode: Boolean = false - - 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}" } - structureHierarchy++ - return when (descriptor.kind) { - is PrimitiveKind -> this@JceDecoder - - StructureKind.MAP -> { - //println("!! MAP") - val tag = popTag() - return jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { - it.checkType(Jce.MAP, "beginStructure", tag, descriptor) - MapReader - } - } - StructureKind.LIST -> { - //println("!! ByteArray") - //println("decoderTag: $currentTagOrNull") - //println("jceHead: " + jce.currentHeadOrNull) - return jce.skipToHeadAndUseIfPossibleOrFail(currentTag.id) { - // don't check type. it's polymorphic - - //println("listHead: $it") - when (it.type) { - Jce.SIMPLE_LIST -> { - currentTag.isSimpleByteArray = true - jce.nextHead() // 无用的元素类型 - SimpleByteArrayReader - } - Jce.LIST -> ListReader - else -> error("type mismatch. Expected SIMPLE_LIST or LIST, got $it instead") - } - } - } - StructureKind.CLASS -> { - currentTagOrNull ?: return this@JceDecoder // outermost - - //println("!! CLASS") - //println("decoderTag: $currentTag") - //println("jceHead: " + jce.currentHeadOrNull) - val tag = popTag() - return jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jceHead -> - jceHead.checkType(Jce.STRUCT_BEGIN, "beginStructure", tag, descriptor) - - repeat(descriptor.elementsCount) { - pushTag(descriptor.getTag(descriptor.elementsCount - it - 1)) // better performance - } - this // independent tag stack - } - } - - StructureKind.OBJECT -> error("unsupported StructureKind.OBJECT: ${descriptor.serialName}") - is UnionKind -> error("unsupported UnionKind: ${descriptor.serialName}") - is PolymorphicKind -> error("unsupported PolymorphicKind: ${descriptor.serialName}") - } - } - - override fun decodeSequentially(): Boolean = false - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - var jceHead = jce.currentHeadOrNull ?: kotlin.run { - println("decodeElementIndex: currentHead == null") - return CompositeDecoder.READ_DONE - } - - 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 - } - - override fun decodeTaggedInt(tag: JceTag): Int = - kotlin.runCatching { jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceIntValue(it) } }.getOrElse { - throw IllegalStateException("$tag", it) - } - - override fun decodeTaggedByte(tag: JceTag): Byte = - kotlin.runCatching { jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceByteValue(it) } }.getOrElse { - throw IllegalStateException("$tag", it) - } - - override fun decodeTaggedBoolean(tag: JceTag): Boolean = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceBooleanValue(it) } - - override fun decodeTaggedFloat(tag: JceTag): Float = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceFloatValue(it) } - - override fun decodeTaggedDouble(tag: JceTag): Double = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceDoubleValue(it) } - - override fun decodeTaggedShort(tag: JceTag): Short = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceShortValue(it) } - - override fun decodeTaggedLong(tag: JceTag): Long = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceLongValue(it) } - - override fun decodeTaggedString(tag: JceTag): String = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceStringValue(it) } - - override fun decodeTaggedNotNullMark(tag: JceTag): Boolean { - return jce.skipToHeadOrNull(tag.id) != null - } -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceInput.kt deleted file mode 100644 index ffdd2e54a..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceInput.kt +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.qqandroid.utils.io.serialization.jce - -import kotlinx.io.core.* -import net.mamoe.mirai.qqandroid.utils.io.readString -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset -import net.mamoe.mirai.qqandroid.utils.toUHexString - - -/** - * Jce Input. 需要手动管理 head. - */ -internal class JceInput( - val input: Input, val charset: JceCharset -) { - private var _head: JceHead? = null - - val currentHead: JceHead get() = _head ?: throw EOFException("No current JceHead available") - val currentHeadOrNull: JceHead? get() = _head - - init { - prepareNextHead() - } - - /** - * 读取下一个 [JceHead] 并保存. 可通过 [currentHead] 获取这个 [JceHead]. - * - * @return 是否成功读取. 返回 `false` 时代表 [Input.endOfInput] - */ - fun prepareNextHead(): Boolean { - return readNextHeadButDoNotAssignTo_Head().also { _head = it; } != null - } - - fun nextHead(): JceHead { - if (!prepareNextHead()) { - throw EOFException("No more JceHead available") - } - return currentHead - } - - /** - * 直接读取下一个 [JceHead] 并返回. - * 返回 `null` 则代表 [Input.endOfInput] - */ - @Suppress("FunctionName") - @OptIn(ExperimentalUnsignedTypes::class) - private fun readNextHeadButDoNotAssignTo_Head(): JceHead? { - if (input.endOfInput) { - return null - } - val var2 = input.readUByte() - val type = var2 and 15u - var tag = var2.toUInt() shr 4 - if (tag == 15u) { - tag = input.readUByte().toUInt() - } - return JceHead( - tag = tag.toInt(), - type = type.toByte() - ) - } - - /** - * 使用这个 [JceHead]. - * [block] 结束后将会 [准备下一个 [JceHead]][prepareNextHead] - */ - inline fun useHead(crossinline block: (JceHead) -> R): R { - return currentHead.let(block).also { prepareNextHead() } - } - - /** - * 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null` - */ - inline fun skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? { - return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() } - } - - /** - * 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常 - */ - inline fun skipToHeadAndUseIfPossibleOrFail( - tag: Int, - crossinline message: () -> String = { "tag not found: $tag" }, - crossinline block: (JceHead) -> R - ): R { - return checkNotNull(skipToHeadAndUseIfPossibleOrNull(tag, block), message) - } - - tailrec fun skipToHeadOrNull(tag: Int): JceHead? { - val current: JceHead = currentHeadOrNull ?: return null // no backing field - - return when { - current.tag > tag -> null // tag 大了,即找不到 - current.tag == tag -> current // 满足需要. - else -> { // tag 小了 - skipField(current.type) - check(prepareNextHead()) { "cannot skip to tag $tag, early EOF" } - skipToHeadOrNull(tag) - } - } - } - - inline fun skipToHeadOrFail( - tag: Int, - message: () -> String = { "head not found: $tag" } - ): JceHead { - return checkNotNull(skipToHeadOrNull(tag), message) - } - - @OptIn(ExperimentalUnsignedTypes::class) - @PublishedApi - internal fun skipField(type: Byte): Unit { - JceDecoder.println { - "skipping ${JceHead.findJceTypeName( - type - )}" - } - when (type) { - Jce.BYTE -> this.input.discardExact(1) - Jce.SHORT -> this.input.discardExact(2) - Jce.INT -> this.input.discardExact(4) - Jce.LONG -> this.input.discardExact(8) - Jce.FLOAT -> this.input.discardExact(4) - Jce.DOUBLE -> this.input.discardExact(8) - Jce.STRING1 -> this.input.discardExact(this.input.readUByte().toInt()) - Jce.STRING4 -> this.input.discardExact(this.input.readInt()) - Jce.MAP -> { // map - JceDecoder.structureHierarchy++ - var count: Int = 0 - nextHead() // avoid shadowing, don't remove - repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping map" }) { - readJceIntValue(it).also { count = it * 2 } - } * 2) { - skipField(currentHead.type) - if (it != count - 1) { // don't read last head - nextHead() - } - } - JceDecoder.structureHierarchy-- - } - Jce.LIST -> { // list - JceDecoder.structureHierarchy++ - var count: Int = 0 - nextHead() // avoid shadowing, don't remove - repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping list" }) { head -> - readJceIntValue(head).also { count = it } - }) { - skipField(currentHead.type) - if (it != count - 1) { // don't read last head - nextHead() - } - } - JceDecoder.structureHierarchy-- - } - Jce.STRUCT_BEGIN -> { - JceDecoder.structureHierarchy++ - var head: JceHead - do { - head = nextHead() - skipField(head.type) - } while (head.type != Jce.STRUCT_END) - JceDecoder.structureHierarchy-- - } - Jce.STRUCT_END, Jce.ZERO_TYPE -> { - } - Jce.SIMPLE_LIST -> { - JceDecoder.structureHierarchy++ - 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)) - JceDecoder.structureHierarchy-- - } - else -> error("invalid type: $type") - } - } - - // region readers - fun readJceIntValue(head: JceHead): Int { - //println("readJceIntValue: $head") - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte().toInt() - Jce.SHORT -> input.readShort().toInt() - Jce.INT -> input.readInt() - else -> error("type mismatch: $head, remaining=${input.readBytes().toUHexString()}") - } - } - - fun readJceShortValue(head: JceHead): Short { - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte().toShort() - Jce.SHORT -> input.readShort() - else -> error("type mismatch: $head") - } - } - - fun readJceLongValue(head: JceHead): Long { - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte().toLong() - Jce.SHORT -> input.readShort().toLong() - Jce.INT -> input.readInt().toLong() - Jce.LONG -> input.readLong() - else -> error("type mismatch ${head.type}") - } - } - - fun readJceByteValue(head: JceHead): Byte { - //println("readJceByteValue: $head") - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte() - else -> error("type mismatch: $head") - } - } - - fun readJceFloatValue(head: JceHead): Float { - return when (head.type) { - Jce.ZERO_TYPE -> 0f - Jce.FLOAT -> input.readFloat() - else -> error("type mismatch: $head") - } - } - - @OptIn(ExperimentalUnsignedTypes::class) - fun readJceStringValue(head: JceHead): String { - //println("readJceStringValue: $head") - return when (head.type) { - Jce.STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset) - Jce.STRING4 -> input.readString( - input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, - charset = charset.kotlinCharset - ) - else -> error("type mismatch: $head, expecting 6 or 7 (for string)") - } - } - - fun readJceDoubleValue(head: JceHead): Double { - return when (head.type.toInt()) { - 12 -> 0.0 - 4 -> input.readFloat().toDouble() - 5 -> input.readDouble() - else -> error("type mismatch: $head") - } - } - - fun readJceBooleanValue(head: JceHead): Boolean { - return readJceByteValue(head) == 1.toByte() - } -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceNew.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceNew.kt deleted file mode 100644 index 994a8c304..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceNew.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.qqandroid.utils.io.serialization.jce - -import kotlinx.io.core.* -import kotlinx.serialization.BinaryFormat -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerialFormat -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptyModule -import kotlinx.serialization.modules.SerialModule -import net.mamoe.mirai.qqandroid.utils.io.serialization.IOFormat -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceOld -import net.mamoe.mirai.qqandroid.utils.toReadPacket - -/** - * Jce 数据结构序列化和反序列化器. - * - * @author Him188 - */ -internal class Jce( - override val context: SerialModule, - val charset: JceCharset -) : SerialFormat, IOFormat, BinaryFormat { - override fun dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) { - output.writePacket(JceOld.byCharSet(this.charset).dumpAsPacket(serializer, ojb)) - } - - override fun load(deserializer: DeserializationStrategy, input: Input): T { - return JceDecoder( - JceInput( - input, - charset - ), context - ).decodeSerializableValue(deserializer) - } - - override fun dump(serializer: SerializationStrategy, value: T): ByteArray { - return buildPacket { dumpTo(serializer, value, this) }.readBytes() - } - - override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T { - return load(deserializer, bytes.toReadPacket()) - } - - companion object { - val UTF_8 = Jce( - EmptyModule, - JceCharset.UTF8 - ) - val GBK = Jce( - EmptyModule, - JceCharset.GBK - ) - - fun byCharSet(c: JceCharset): Jce { - return if (c == JceCharset.UTF8) UTF_8 else GBK - } - - internal const val BYTE: Byte = 0 - internal const val DOUBLE: Byte = 5 - internal const val FLOAT: Byte = 4 - internal const val INT: Byte = 2 - internal const val JCE_MAX_STRING_LENGTH = 104857600 - internal const val LIST: Byte = 9 - internal const val LONG: Byte = 3 - internal const val MAP: Byte = 8 - internal const val SHORT: Byte = 1 - internal const val SIMPLE_LIST: Byte = 13 - internal const val STRING1: Byte = 6 - internal const val STRING4: Byte = 7 - internal const val STRUCT_BEGIN: Byte = 10 - internal const val STRUCT_END: Byte = 11 - internal const val ZERO_TYPE: Byte = 12 - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/common.kt deleted file mode 100644 index 86bfee2bd..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/common.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.qqandroid.utils.io.serialization.jce - -import kotlinx.io.core.Output -import kotlinx.serialization.SerialDescriptor -import kotlinx.serialization.SerialInfo - - -/** - * 标注 JCE 序列化时使用的 ID - */ -@SerialInfo -@Target(AnnotationTarget.PROPERTY) -internal annotation class JceId(val id: Int) - -/** - * 类中元素的 tag - * - * 保留这个结构, 为将来增加功能的兼容性. - */ -@PublishedApi -internal abstract class JceTag { - abstract val id: Int - - internal var isSimpleByteArray: Boolean = false -} - -internal object JceTagListElement : JceTag() { - override val id: Int get() = 0 - override fun toString(): String { - return "JceTagListElement" - } -} - -internal object JceTagMapEntryKey : JceTag() { - override val id: Int get() = 0 - override fun toString(): String { - return "JceTagMapEntryKey" - } -} - -internal object JceTagMapEntryValue : JceTag() { - override val id: Int get() = 1 - override fun toString(): String { - return "JceTagMapEntryValue" - } -} - -internal data class JceTagCommon( - override val id: Int -) : JceTag() - -internal fun JceHead.checkType(type: Byte, message: String, tag: JceTag, descriptor: SerialDescriptor) { - check(this.type == type) { - "type mismatch. " + - "Expected ${JceHead.findJceTypeName(type)}, " + - "actual ${JceHead.findJceTypeName(this.type)} for $message. " + - "Tag info: " + - "id=${tag.id}, " + - "name=${descriptor.getElementName(tag.id)} " + - "in ${descriptor.serialName}" } -} - -@PublishedApi -internal fun Output.writeJceHead(type: Byte, tag: Int) { - if (tag < 15) { - writeByte(((tag shl 4) or type.toInt()).toByte()) - return - } - if (tag < 256) { - writeByte((type.toInt() or 0xF0).toByte()) - writeByte(tag.toByte()) - return - } - error("tag is too large: $tag") -} - -@OptIn(ExperimentalUnsignedTypes::class) -inline class JceHead(private val value: Long) { - constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong()) - - val tag: Int get() = (value ushr 32).toInt() - val type: Byte get() = value.toUInt().toByte() - - override fun toString(): String { - return "JceHead(tag=$tag, type=$type(${findJceTypeName(type)}))" - } - - companion object { - fun findJceTypeName(type: Byte): String { - return when (type) { - Jce.BYTE -> "Byte" - Jce.DOUBLE -> "Double" - Jce.FLOAT -> "Float" - Jce.INT -> "Int" - Jce.LIST -> "List" - Jce.LONG -> "Long" - Jce.MAP -> "Map" - Jce.SHORT -> "Short" - Jce.SIMPLE_LIST -> "SimpleList" - Jce.STRING1 -> "String1" - Jce.STRING4 -> "String4" - Jce.STRUCT_BEGIN -> "StructBegin" - Jce.STRUCT_END -> "StructEnd" - Jce.ZERO_TYPE -> "Zero" - else -> error("illegal jce type: $type") - } - } - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt index ad04fd662..5ea7bf77c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt @@ -16,15 +16,14 @@ import kotlinx.io.core.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialDescriptor import kotlinx.serialization.SerializationStrategy +import moe.him188.jcekt.Jce import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf import net.mamoe.mirai.qqandroid.utils.io.readPacketExact -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce import net.mamoe.mirai.qqandroid.utils.read -import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -34,25 +33,21 @@ internal fun ByteArray.loadWithUniPacket( ): T = this.read { readUniPacket(deserializer, name) } internal fun ByteArray.loadAs( - deserializer: DeserializationStrategy, - c: JceCharset = JceCharset.UTF8 -): T = this.read { Jce.byCharSet(c).load(deserializer, this) } + deserializer: DeserializationStrategy +): T = this.read { Jce.UTF_8.load(deserializer, this) } internal fun BytePacketBuilder.writeJceStruct( serializer: SerializationStrategy, - struct: T, - charset: JceCharset = JceCharset.UTF8 + struct: T ) { - Jce.byCharSet(charset).dumpTo(serializer, struct, this) + Jce.UTF_8.dumpTo(serializer, struct, this) } internal fun ByteReadPacket.readJceStruct( serializer: DeserializationStrategy, - charset: JceCharset = JceCharset.UTF8, length: Int = this.remaining.toInt() ): T { - @OptIn(MiraiInternalAPI::class) - return Jce.byCharSet(charset).load(serializer, this.readPacketExact(length)) + return Jce.UTF_8.load(serializer, this.readPacketExact(length)) } /** @@ -103,9 +98,8 @@ private fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String } internal fun T.toByteArray( - serializer: SerializationStrategy, - c: JceCharset = JceCharset.UTF8 -): ByteArray = Jce.byCharSet(c).dump(serializer, this) + serializer: SerializationStrategy +): ByteArray = Jce.UTF_8.dump(serializer, this) internal fun BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy, v: T) { this.writeFully(v.toByteArray(serializer)) @@ -140,19 +134,12 @@ internal fun jceRequestSBuffer( name: String, serializer: SerializationStrategy, jceStruct: T -): ByteArray = jceRequestSBuffer(name, serializer, jceStruct, JceCharset.UTF8) - -internal fun jceRequestSBuffer( - name: String, - serializer: SerializationStrategy, - jceStruct: T, - charset: JceCharset ): ByteArray { return RequestDataVersion3( mapOf( name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0 ) - ).toByteArray(RequestDataVersion3.serializer(), charset) + ).toByteArray(RequestDataVersion3.serializer()) } private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A) 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 deleted file mode 100644 index 78671f853..000000000 --- a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/io/serialization/JceInputTest.kt +++ /dev/null @@ -1,651 +0,0 @@ -@file:Suppress("unused", "DEPRECATION_ERROR") - -package net.mamoe.mirai.qqandroid.io.serialization - -import kotlinx.io.core.buildPacket -import kotlinx.io.core.toByteArray -import kotlinx.io.core.writeFully -import kotlinx.serialization.MissingFieldException -import kotlinx.serialization.Serializable -import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin -import net.mamoe.mirai.qqandroid.utils.autoHexToBytes -import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.* -import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs -import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - - -internal const val BYTE: Byte = 0 -internal const val DOUBLE: Byte = 5 -internal const val FLOAT: Byte = 4 -internal const val INT: Byte = 2 -internal const val JCE_MAX_STRING_LENGTH = 104857600 -internal const val LIST: Byte = 9 -internal const val LONG: Byte = 3 -internal const val MAP: Byte = 8 -internal const val SHORT: Byte = 1 -internal const val SIMPLE_LIST: Byte = 13 -internal const val STRING1: Byte = 6 -internal const val STRING4: Byte = 7 -internal const val STRUCT_BEGIN: Byte = 10 -internal const val STRUCT_END: Byte = 11 -internal const val ZERO_TYPE: Byte = 12 - - -@Suppress("INVISIBLE_MEMBER") // bug -internal class JceInputTest { - init { - JceDecoder.debuggingMode = true - } - - - @Test - fun testConfigPush() { - val data = """ - 9A - 09 00 0B - 0A - 00 0F - 19 00 01 - 0A - 12 71 19 A3 B4 - 20 50 - 0B - 29 0C - 0B - 0A - 00 04 - 19 00 01 - 0A - 12 0B 27 59 65 - 20 50 - 0B - 29 0C - 0B - 0A - 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B - AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - - """.trimIndent().autoHexToBytes() - /* - 9A - 09 00 0B - 0A - 00 0F - 19 00 01 - 0A - 12 71 19 A3 B4 - 20 50 - 0B - 29 0C - 0B - 0A - 00 04 - 19 00 01 - 0A - 12 0B 27 59 65 - 20 50 - 0B - 29 0C - 0B - 0A - 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B - AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - - */ - /* - - 39 00 06 - 0A - 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 31 - 20 50 - 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 35 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 33 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 2D 00 00 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 32 76 E4 B8 DD 40 01 5D 00 01 02 54 8A 50 D0 04 0A 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 12 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 1A 40 08 01 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 40 08 05 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 78 08 0A 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 70 02 78 02 80 01 FA 01 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0E 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 20 50 0B 0A 16 0D 36 31 2E 31 35 31 2E 31 38 33 2E 32 31 20 50 0B 8A 06 0F 31 37 31 2E 31 31 32 2E 32 32 36 2E 32 33 37 10 03 0B 9A 09 00 0B 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - - */ - - data.loadAs(FileStoragePushFSSvcListFuckKotlin.serializer()) - } - - @Test - fun testIntToStructMap() { - @kotlinx.serialization.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 - data class TestSerializableClassC( - @JceId(5) val value3: Int = 123123 - ) - - @Serializable - data class TestSerializableClassB( - @JceId(0) val value: Int, - @JceId(123) val nested2: TestSerializableClassC, - @JceId(5) val value5: Int - ) - - @Serializable - data class TestSerializableClassA( - //@JceId(0) val map: Map - @JceId(1) val optional: String = "" - ) - - - val input = buildPacket { - writeJceHead(MAP, 0) // TestSerializableClassB - writeJceHead(BYTE, 0) - writeByte(1); - - { - writeJceHead(STRUCT_BEGIN, 0); - { - writeJceHead(INT, 0) - writeInt(123) - - writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 5) - writeInt(9) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(STRUCT_BEGIN, 1); - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - }() - - writeJceHead(STRING1, 1) - writeByte(1) - writeStringUtf8("1") - } - - assertEquals( - TestSerializableClassA( - /*mapOf( - TestSerializableClassB(123, TestSerializableClassC(123123), 9) - to TestSerializableClassC(123123) - ),*/ - - "1" - ), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testFuckingComprehensiveStruct() { - @Serializable - data class TestSerializableClassC( - @JceId(5) val value3: Int = 123123 - ) - - @Serializable - data class TestSerializableClassB( - @JceId(0) val value: Int, - @JceId(123) val nested2: TestSerializableClassC, - @JceId(5) val value5: Int - ) - - @Serializable - data class TestSerializableClassA( - @JceId(0) val map: Map - ) - - - val input = buildPacket { - writeJceHead(MAP, 0) // TestSerializableClassB - writeJceHead(BYTE, 0) - writeByte(1) - - writeJceHead(STRUCT_BEGIN, 0); - { - writeJceHead(INT, 0) - writeInt(123) - - writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 5) - writeInt(9) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(STRUCT_BEGIN, 1); - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - } - - assertEquals( - TestSerializableClassA( - mapOf( - TestSerializableClassB(123, TestSerializableClassC(123123), 9) - to TestSerializableClassC(123123) - ) - ), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testNestedJceStruct() { - @Serializable - data class TestSerializableClassC( - @JceId(5) val value3: Int - ) - - @Serializable - data class TestSerializableClassB( - @JceId(0) val value: Int, - @JceId(123) val nested2: TestSerializableClassC, - @JceId(5) val value5: Int - ) - - @Serializable - data class TestSerializableClassA( - @JceId(0) val value1: Int, - @JceId(4) val notOptional: Int, - @JceId(1) val nestedStruct: TestSerializableClassB, - @JceId(2) val optional: Int = 3 - ) - - val input = buildPacket { - writeJceHead(INT, 0) - writeInt(444) - - writeJceHead(STRUCT_BEGIN, 1); // TestSerializableClassB - { - writeJceHead(INT, 0) - writeInt(123) - - writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 5) - writeInt(9) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 4) - writeInt(5) - } - - assertEquals( - TestSerializableClassA( - 444, - 5, - TestSerializableClassB(123, TestSerializableClassC(123123), 9) - ), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testNestedList() { - @Serializable - data class TestSerializableClassA( - // @JceId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3), - @JceId(3) val byteArray2: List> = listOf(listOf(1, 2, 3, 4), listOf(1, 2, 3, 4)) - ) - - val input = buildPacket { - //writeJceHead(SIMPLE_LIST, 0) - //writeJceHead(BYTE, 0) - - //writeJceHead(BYTE, 0) - //byteArrayOf(1, 2, 3).let { - // writeByte(it.size.toByte()) - // writeFully(it) - //} - - writeJceHead(LIST, 3) - - writeJceHead(BYTE, 0) - writeByte(2) - listOf(listOf(1, 2, 3, 4), listOf(1, 2, 3, 4)).forEach { child -> - writeJceHead(LIST, 0) - - writeJceHead(BYTE, 0) - writeByte(child.size.toByte()) - - child.forEach { - writeJceHead(INT, 0) - writeInt(it) - } - } - } - - assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) - } - - @Test - fun testMap() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byteArray: Map - ) - - val input = buildPacket { - writeJceHead(MAP, 0) - - mapOf(1 to 2, 33 to 44).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - - it.forEach { (key, value) -> - writeJceHead(INT, 0) - writeInt(key) - - writeJceHead(INT, 1) - writeInt(value) - } - } - - writeJceHead(SIMPLE_LIST, 3) - writeJceHead(BYTE, 0) - - byteArrayOf(1, 2, 3, 4).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - writeFully(it) - } - } - - assertEquals( - TestSerializableClassA(mapOf(1 to 2, 33 to 44)), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testMapStringInt() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byteArray: Map - ) - - val input = buildPacket { - writeJceHead(MAP, 0) - - mapOf("str1" to 2, "str2" to 44).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - - it.forEach { (key, value) -> - writeJceHead(STRING1, 0) - writeByte(key.length.toByte()) - writeStringUtf8(key) - - writeJceHead(INT, 1) - writeInt(value) - } - } - } - - assertEquals( - TestSerializableClassA(mapOf("str1" to 2, "str2" to 44)), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testMapStringByteArray() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val map: Map - ) { - override fun toString(): String { - @Suppress("EXPERIMENTAL_API_USAGE") - return map.entries.joinToString { "${it.key}=${it.value.contentToString()}" } - } - } - - val input = buildPacket { - writeJceHead(MAP, 0) - - mapOf("str1" to byteArrayOf(2, 3, 4), "str2" to byteArrayOf(2, 3, 4)).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - - it.forEach { (key, value) -> - writeJceHead(STRING1, 0) - writeByte(key.length.toByte()) - writeFully(key.toByteArray()) - - writeJceHead(SIMPLE_LIST, 1) - writeJceHead(BYTE, 0) - writeJceHead(INT, 0) - writeInt(value.size) - writeFully(value) - } - } - } - - assertEquals( - TestSerializableClassA(mapOf("str1" to byteArrayOf(2, 3, 4), "str2" to byteArrayOf(2, 3, 4))).toString(), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input).toString() - ) - } - - @Test - fun testSimpleByteArray() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3), - @JceId(3) val byteArray2: List = listOf(1, 2, 3, 4) - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as TestSerializableClassA - - if (!byteArray.contentEquals(other.byteArray)) return false - if (byteArray2 != other.byteArray2) return false - - return true - } - - override fun hashCode(): Int { - var result = byteArray.contentHashCode() - result = 31 * result + byteArray2.hashCode() - return result - } - } - - val input = buildPacket { - writeJceHead(SIMPLE_LIST, 0) - writeJceHead(BYTE, 0) - - byteArrayOf(1, 2, 3).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - writeFully(it) - } - - writeJceHead(SIMPLE_LIST, 3) - writeJceHead(BYTE, 0) - - byteArrayOf(1, 2, 3, 4).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - writeFully(it) - } - } - - assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) - } - - - @Test - fun testSerializableClassA() { - @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, - @JceId(111112) val nullable2: Int? = null, - @JceId(111113) val optional: Int = 123 - ) - - 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(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) - } - - @Test - fun testNoSuchField() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byte: Byte = 66, - @JceId(1) val short: Short = 123, - @JceId(3) val int: Int - ) - - val input = buildPacket { - writeJceHead(BYTE, 0) - writeByte(66) - writeJceHead(SHORT, 1) - writeShort(123) - } - - assertFailsWith { Jce.UTF_8.load(TestSerializableClassA.serializer(), input) } - } - - @Test - fun testHeadSkip() { - val input = JceInput(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 - }, JceCharset.UTF8) - - assertEquals(123456, input.skipToHeadAndUseIfPossibleOrFail(3) { input.readJceIntValue(it) }) - - assertEquals(true, input.skipToHeadAndUseIfPossibleOrFail(17) { input.readJceBooleanValue(it) }) - - assertFailsWith { - input.skipToHeadAndUseIfPossibleOrFail(18) { - error("test failed") - } - } - } - - @Test - fun testReadPrimitive() { - val input = JceInput(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 - }, JceCharset.UTF8) - assertEquals(66, input.useHead { input.readJceByteValue(it) }) - assertEquals(123, input.useHead { input.readJceShortValue(it) }) - assertEquals(123456, input.useHead { input.readJceIntValue(it) }) - assertEquals(123f, input.useHead { input.readJceFloatValue(it) }) - assertEquals(123456789123456789, input.useHead { input.readJceLongValue(it) }) - assertEquals(123456.0, input.useHead { input.readJceDoubleValue(it) }) - assertEquals(true, input.useHead { input.readJceBooleanValue(it) }) - } -} \ No newline at end of file