mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-17 10:04:44 +08:00
Extract JCE serialization to him188/jcekt. close #300
This commit is contained in:
parent
53ba8aba00
commit
1305709d3e
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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<String, String>()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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 <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)
|
||||
|
||||
fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T
|
||||
}
|
@ -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<JceId>(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<Int>() {
|
||||
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 <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
|
||||
serializer.descriptor.kind == StructureKind.MAP -> {
|
||||
try {
|
||||
val entries = (value as Map<*, *>).entries
|
||||
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
|
||||
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<Byte>).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<Int>() {
|
||||
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<Byte>
|
||||
// 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 <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): 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<Any?, T, Any?>).typeParams.isNotEmpty()
|
||||
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).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<Any?, Any?, T, *>)
|
||||
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 <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
|
||||
return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) 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(".")
|
||||
?: "<unnamed class>"
|
||||
}
|
||||
|
||||
fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket {
|
||||
val encoder = BytePacketBuilder()
|
||||
val dumper = JceEncoder(encoder)
|
||||
dumper.encode(serializer, obj)
|
||||
return encoder.build()
|
||||
}
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
|
||||
return dumpAsPacket(serializer, value).readBytes()
|
||||
}
|
||||
|
||||
/**
|
||||
* 注意 close [packet]!!
|
||||
*/
|
||||
fun <T> load(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
packet: ByteReadPacket,
|
||||
length: Int = packet.remaining.toInt()
|
||||
): T {
|
||||
return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer)
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
|
||||
return bytes.toReadPacket().use {
|
||||
val decoder = JceDecoder(JceInput(it))
|
||||
decoder.decode(deserializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <R> 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()
|
||||
}
|
||||
}
|
@ -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<Int, ProtoNumberType>
|
||||
|
||||
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<JceId>(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<ProtoType>(index)?.type
|
||||
|
@ -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`
|
@ -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<JceTag>() {
|
||||
override val updateMode: UpdateMode
|
||||
get() = UpdateMode.BANNED
|
||||
|
||||
override fun SerialDescriptor.getTag(index: Int): JceTag {
|
||||
val annotations = this.getElementAnnotations(index)
|
||||
|
||||
val id = annotations.filterIsInstance<JceId>().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
|
||||
}
|
||||
}
|
@ -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 <R> useHead(crossinline block: (JceHead) -> R): R {
|
||||
return currentHead.let(block).also { prepareNextHead() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null`
|
||||
*/
|
||||
inline fun <R> skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? {
|
||||
return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常
|
||||
*/
|
||||
inline fun <R : Any> skipToHeadAndUseIfPossibleOrFail(
|
||||
tag: Int,
|
||||
crossinline message: () -> String = { "tag not found: $tag" },
|
||||
crossinline block: (JceHead) -> R
|
||||
): R {
|
||||
return checkNotNull<R>(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()
|
||||
}
|
||||
}
|
@ -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 <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output) {
|
||||
output.writePacket(JceOld.byCharSet(this.charset).dumpAsPacket(serializer, ojb))
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T {
|
||||
return JceDecoder(
|
||||
JceInput(
|
||||
input,
|
||||
charset
|
||||
), context
|
||||
).decodeSerializableValue(deserializer)
|
||||
}
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
|
||||
return buildPacket { dumpTo(serializer, value, this) }.readBytes()
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, 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
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <T : JceStruct> ByteArray.loadWithUniPacket(
|
||||
): T = this.read { readUniPacket(deserializer, name) }
|
||||
|
||||
internal fun <T : JceStruct> ByteArray.loadAs(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
c: JceCharset = JceCharset.UTF8
|
||||
): T = this.read { Jce.byCharSet(c).load(deserializer, this) }
|
||||
deserializer: DeserializationStrategy<T>
|
||||
): T = this.read { Jce.UTF_8.load(deserializer, this) }
|
||||
|
||||
internal fun <T : JceStruct> BytePacketBuilder.writeJceStruct(
|
||||
serializer: SerializationStrategy<T>,
|
||||
struct: T,
|
||||
charset: JceCharset = JceCharset.UTF8
|
||||
struct: T
|
||||
) {
|
||||
Jce.byCharSet(charset).dumpTo(serializer, struct, this)
|
||||
Jce.UTF_8.dumpTo(serializer, struct, this)
|
||||
}
|
||||
|
||||
internal fun <T : JceStruct> ByteReadPacket.readJceStruct(
|
||||
serializer: DeserializationStrategy<T>,
|
||||
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 <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String
|
||||
}
|
||||
|
||||
internal fun <T : JceStruct> T.toByteArray(
|
||||
serializer: SerializationStrategy<T>,
|
||||
c: JceCharset = JceCharset.UTF8
|
||||
): ByteArray = Jce.byCharSet(c).dump(serializer, this)
|
||||
serializer: SerializationStrategy<T>
|
||||
): ByteArray = Jce.UTF_8.dump(serializer, this)
|
||||
|
||||
internal fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
|
||||
this.writeFully(v.toByteArray(serializer))
|
||||
@ -140,19 +134,12 @@ internal fun <T : JceStruct> jceRequestSBuffer(
|
||||
name: String,
|
||||
serializer: SerializationStrategy<T>,
|
||||
jceStruct: T
|
||||
): ByteArray = jceRequestSBuffer(name, serializer, jceStruct, JceCharset.UTF8)
|
||||
|
||||
internal fun <T : JceStruct> jceRequestSBuffer(
|
||||
name: String,
|
||||
serializer: SerializationStrategy<T>,
|
||||
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)
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user