Extract JCE serialization to him188/jcekt. close #300

This commit is contained in:
Him188 2020-05-04 14:48:01 +08:00
parent 53ba8aba00
commit 1305709d3e
25 changed files with 30 additions and 2354 deletions

View File

@ -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()
}

View File

@ -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"
}

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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")

View File

@ -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

View File

@ -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>()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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

View File

@ -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`

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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")
}
}
}
}

View File

@ -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)