mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-15 04:50:11 +08:00
JceStruct serialization
This commit is contained in:
parent
4686430248
commit
2e80d33048
@ -0,0 +1,292 @@
|
|||||||
|
package net.mamoe.mirai.qqandroid.io
|
||||||
|
|
||||||
|
import kotlinx.io.charsets.Charset
|
||||||
|
import kotlinx.io.core.*
|
||||||
|
import kotlin.experimental.or
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal val CharsetGBK = Charset.forName("GBK")
|
||||||
|
@PublishedApi
|
||||||
|
internal val CharsetUTF8 = Charset.forName("UTF8")
|
||||||
|
|
||||||
|
inline fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
|
||||||
|
return JceOutput(stringCharset).apply(block).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun BytePacketBuilder.writeJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit) {
|
||||||
|
return this.writePacket(buildJcePacket(stringCharset, block))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jceStruct(tag: Int, struct: JceStruct): ByteArray{
|
||||||
|
return buildJcePacket {
|
||||||
|
writeJceStruct(struct, tag)
|
||||||
|
}.readBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> jceMap(tag: Int, vararg entries: Pair<K, V>): ByteArray {
|
||||||
|
return buildJcePacket {
|
||||||
|
writeMap(mapOf(*entries), tag)
|
||||||
|
}.readBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* From: com.qq.taf.jce.JceOutputStream
|
||||||
|
*/
|
||||||
|
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||||
|
@UseExperimental(ExperimentalIoApi::class)
|
||||||
|
class JceOutput(
|
||||||
|
private val stringCharset: Charset = CharsetGBK
|
||||||
|
) {
|
||||||
|
private val output: BytePacketBuilder = BytePacketBuilder()
|
||||||
|
|
||||||
|
fun build(): ByteReadPacket = output.build()
|
||||||
|
|
||||||
|
fun close() = output.close()
|
||||||
|
fun flush() = output.flush()
|
||||||
|
|
||||||
|
fun writeByte(v: Byte, tag: Int) {
|
||||||
|
if (v.toInt() == 0) {
|
||||||
|
writeHead(ZERO_TYPE, tag)
|
||||||
|
} else {
|
||||||
|
writeHead(BYTE, tag)
|
||||||
|
output.writeByte(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeDouble(v: Double, tag: Int) {
|
||||||
|
writeHead(DOUBLE, tag)
|
||||||
|
output.writeDouble(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFloat(v: Float, tag: Int) {
|
||||||
|
writeHead(FLOAT, tag)
|
||||||
|
output.writeFloat(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: ByteArray, tag: Int) {
|
||||||
|
writeHead(SIMPLE_LIST, tag)
|
||||||
|
writeHead(BYTE, 0)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
output.writeFully(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: DoubleArray, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeDouble(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: FloatArray, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeFloat(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: IntArray, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeInt(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: LongArray, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeLong(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: ShortArray, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeShort(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFully(src: BooleanArray, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeBoolean(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> writeFully(src: Array<T>, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
writeInt(src.size, 0)
|
||||||
|
src.forEach {
|
||||||
|
writeObject(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeInt(v: Int, tag: Int) {
|
||||||
|
if (v in Short.MIN_VALUE..Short.MAX_VALUE) {
|
||||||
|
writeShort(v.toShort(), tag)
|
||||||
|
} else {
|
||||||
|
writeHead(INT, tag)
|
||||||
|
output.writeInt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeLong(v: Long, tag: Int) {
|
||||||
|
if (v in Int.MIN_VALUE..Int.MAX_VALUE) {
|
||||||
|
writeInt(v.toInt(), tag)
|
||||||
|
} else {
|
||||||
|
writeHead(LONG, tag)
|
||||||
|
output.writeLong(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeShort(v: Short, tag: Int) {
|
||||||
|
if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
|
||||||
|
writeByte(v.toByte(), tag)
|
||||||
|
} else {
|
||||||
|
writeHead(SHORT, tag)
|
||||||
|
output.writeShort(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeBoolean(v: Boolean, tag: Int) {
|
||||||
|
this.writeByte(if (v) 1 else 0, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeString(v: String, tag: Int) {
|
||||||
|
val array = v.toByteArray(stringCharset)
|
||||||
|
if (array.size > 255) {
|
||||||
|
writeHead(STRING4, tag)
|
||||||
|
output.writeInt(array.size)
|
||||||
|
output.writeFully(array)
|
||||||
|
} else {
|
||||||
|
writeHead(STRING1, tag)
|
||||||
|
output.writeByte(array.size.toByte())
|
||||||
|
output.writeFully(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> writeMap(map: Map<K, V>, tag: Int) {
|
||||||
|
writeHead(MAP, tag)
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
writeInt(0, 0)
|
||||||
|
} else {
|
||||||
|
writeInt(map.size, 0)
|
||||||
|
map.forEach { (key, value) ->
|
||||||
|
writeObject(key, 0)
|
||||||
|
writeObject(value, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeCollection(collection: Collection<*>?, tag: Int) {
|
||||||
|
writeHead(LIST, tag)
|
||||||
|
if (collection == null || collection.isEmpty()) {
|
||||||
|
writeInt(0, 0)
|
||||||
|
} else {
|
||||||
|
writeInt(collection.size, 0)
|
||||||
|
collection.forEach {
|
||||||
|
writeObject(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeJceStruct(v: JceStruct, tag: Int) {
|
||||||
|
writeHead(STRUCT_BEGIN, tag)
|
||||||
|
v.writeTo(this)
|
||||||
|
writeHead(STRUCT_END, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> writeObject(v: T, tag: Int) {
|
||||||
|
when (v) {
|
||||||
|
is Byte -> writeByte(v, tag)
|
||||||
|
is Short -> writeShort(v, tag)
|
||||||
|
is Int -> writeInt(v, tag)
|
||||||
|
is Long -> writeLong(v, tag)
|
||||||
|
is Float -> writeFloat(v, tag)
|
||||||
|
is Double -> writeDouble(v, tag)
|
||||||
|
is JceStruct -> writeJceStruct(v, tag)
|
||||||
|
is ByteArray -> writeFully(v, tag)
|
||||||
|
is Collection<*> -> writeCollection(v, tag)
|
||||||
|
is Boolean -> writeBoolean(v, tag)
|
||||||
|
is Map<*, *> -> writeMap(v, tag)
|
||||||
|
is IntArray -> writeFully(v, tag)
|
||||||
|
is ShortArray -> writeFully(v, tag)
|
||||||
|
is BooleanArray -> writeFully(v, tag)
|
||||||
|
is LongArray -> writeFully(v, tag)
|
||||||
|
is FloatArray -> writeFully(v, tag)
|
||||||
|
is DoubleArray -> writeFully(v, tag)
|
||||||
|
is Array<*> -> writeFully(v, tag)
|
||||||
|
is String -> writeString(v, tag)
|
||||||
|
|
||||||
|
//
|
||||||
|
// is ByteReadPacket -> ByteArrayPool.useInstance {
|
||||||
|
// v.readAvailable(it)
|
||||||
|
// writeFully(it, tag)
|
||||||
|
// }
|
||||||
|
else -> error("unsupported type: ${v.getClassName()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(v: Int, tag: Int) = writeInt(v, tag)
|
||||||
|
fun write(v: Byte, tag: Int) = writeByte(v, tag)
|
||||||
|
fun write(v: Short, tag: Int) = writeShort(v, tag)
|
||||||
|
fun write(v: Long, tag: Int) = writeLong(v, tag)
|
||||||
|
fun write(v: Float, tag: Int) = writeFloat(v, tag)
|
||||||
|
fun write(v: Double, tag: Int) = writeDouble(v, tag)
|
||||||
|
fun write(v: String, tag: Int) = writeString(v, tag)
|
||||||
|
fun write(v: Boolean, tag: Int) = writeBoolean(v, tag)
|
||||||
|
fun write(v: Collection<*>, tag: Int) = writeCollection(v, tag)
|
||||||
|
fun write(v: Map<*, *>, tag: Int) = writeMap(v, tag)
|
||||||
|
fun write(v: ByteArray, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: IntArray, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: BooleanArray, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: LongArray, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: ShortArray, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: Array<*>, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: FloatArray, tag: Int) = writeFully(v, tag)
|
||||||
|
fun write(v: DoubleArray, tag: Int) = writeFully(v, tag)
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal companion object {
|
||||||
|
const val BYTE: Int = 0
|
||||||
|
const val DOUBLE: Int = 5
|
||||||
|
const val FLOAT: Int = 4
|
||||||
|
const val INT: Int = 2
|
||||||
|
const val JCE_MAX_STRING_LENGTH = 104857600
|
||||||
|
const val LIST: Int = 9
|
||||||
|
const val LONG: Int = 3
|
||||||
|
const val MAP: Int = 8
|
||||||
|
const val SHORT: Int = 1
|
||||||
|
const val SIMPLE_LIST: Int = 13
|
||||||
|
const val STRING1: Int = 6
|
||||||
|
const val STRING4: Int = 7
|
||||||
|
const val STRUCT_BEGIN: Int = 10
|
||||||
|
const val STRUCT_END: Int = 11
|
||||||
|
const val ZERO_TYPE: Int = 12
|
||||||
|
|
||||||
|
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal fun writeHead(type: Int, tag: Int) {
|
||||||
|
if (tag < 15) {
|
||||||
|
this.output.writeByte(((tag shl 4) or type).toByte())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tag < 256) {
|
||||||
|
this.output.writeByte((type.toByte() or 0xF0.toByte()))
|
||||||
|
this.output.writeByte(tag.toByte())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw JceEncodeException("tag is too large: $tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JceEncodeException(message: String) : RuntimeException(message)
|
@ -1,3 +1,5 @@
|
|||||||
package net.mamoe.mirai.qqandroid.io
|
package net.mamoe.mirai.qqandroid.io
|
||||||
|
|
||||||
interface JceStruct
|
interface JceStruct {
|
||||||
|
fun writeTo(output: JceOutput) = Unit
|
||||||
|
}
|
@ -13,7 +13,6 @@ import net.mamoe.mirai.qqandroid.io.JceStruct
|
|||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
|
||||||
import net.mamoe.mirai.utils.io.readString
|
import net.mamoe.mirai.utils.io.readString
|
||||||
import net.mamoe.mirai.utils.io.toIoBuffer
|
import net.mamoe.mirai.utils.io.toIoBuffer
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal val CharsetGBK = Charset.forName("GBK")
|
internal val CharsetGBK = Charset.forName("GBK")
|
||||||
@ -24,7 +23,7 @@ fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset
|
|||||||
return Jce.byCharSet(c).load(deserializer, this)
|
return Jce.byCharSet(c).load(deserializer, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.UTF8): ByteArray = Jce.byCharSet(c).dump(serializer, this)
|
fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this)
|
||||||
|
|
||||||
enum class JceCharset(val kotlinCharset: Charset) {
|
enum class JceCharset(val kotlinCharset: Charset) {
|
||||||
GBK(Charset.forName("GBK")),
|
GBK(Charset.forName("GBK")),
|
||||||
@ -104,7 +103,6 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
|||||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||||
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
|
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
|
||||||
is MapLikeDescriptor -> {
|
is MapLikeDescriptor -> {
|
||||||
println("hello")
|
|
||||||
val entries = (value as Map<*, *>).entries
|
val entries = (value as Map<*, *>).entries
|
||||||
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
|
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
|
||||||
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
|
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
|
||||||
@ -115,24 +113,26 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
|||||||
}
|
}
|
||||||
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
|
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
|
||||||
is PrimitiveArrayDescriptor -> {
|
is PrimitiveArrayDescriptor -> {
|
||||||
if (value is ByteArray) {
|
// if (value is ByteArray) {
|
||||||
this.encodeTaggedByteArray(popTag(), value)
|
// this.encodeTaggedByteArray(popTag(), value)
|
||||||
} else {
|
// } else {
|
||||||
serializer.serialize(
|
serializer.serialize(
|
||||||
ListWriter(
|
ListWriter(
|
||||||
when (value) {
|
when (value) {
|
||||||
is ShortArray -> value.size
|
is ShortArray -> value.size
|
||||||
is IntArray -> value.size
|
is IntArray -> value.size
|
||||||
is LongArray -> value.size
|
is LongArray -> value.size
|
||||||
is FloatArray -> value.size
|
is FloatArray -> value.size
|
||||||
is DoubleArray -> value.size
|
is DoubleArray -> value.size
|
||||||
is CharArray -> value.size
|
is CharArray -> value.size
|
||||||
else -> error("unknown array type: ${value.getClassName()}")
|
is ByteArray -> value.size
|
||||||
}, popTag(), this
|
is BooleanArray -> value.size
|
||||||
),
|
else -> error("unknown array type: ${value.getClassName()}")
|
||||||
value
|
}, popTag(), this
|
||||||
)
|
),
|
||||||
}
|
value
|
||||||
|
)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
is ArrayClassDesc -> {
|
is ArrayClassDesc -> {
|
||||||
serializer.serialize(
|
serializer.serialize(
|
||||||
@ -260,13 +260,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal fun writeHead(type: Int, tag: Int) {
|
internal fun writeHead(type: Byte, tag: Int) {
|
||||||
if (tag < 15) {
|
if (tag < 15) {
|
||||||
this.output.write((tag shl 4) or type)
|
this.output.write((tag shl 4) or type.toInt())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (tag < 256) {
|
if (tag < 256) {
|
||||||
this.output.write(type or 0xF0)
|
this.output.write(type.toInt() or 0xF0)
|
||||||
this.output.write(tag)
|
this.output.write(tag)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -274,6 +274,45 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private open inner class JceMapReader(
|
||||||
|
val size: Int,
|
||||||
|
input: JceInput
|
||||||
|
) : JceDecoder(input) {
|
||||||
|
override fun decodeCollectionSize(desc: 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(desc: 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(desc: SerialDescriptor) {
|
||||||
|
input.readHead() // STRUCT_END
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private open inner class NullReader(
|
||||||
|
input: JceInput
|
||||||
|
) : JceDecoder(input)
|
||||||
|
|
||||||
private open inner class JceDecoder(
|
private open inner class JceDecoder(
|
||||||
internal val input: JceInput
|
internal val input: JceInput
|
||||||
) : TaggedDecoder<Int>() {
|
) : TaggedDecoder<Int>() {
|
||||||
@ -291,69 +330,112 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
|||||||
override fun decodeTaggedString(tag: Int): String = input.readString(tag)
|
override fun decodeTaggedString(tag: Int): String = input.readString(tag)
|
||||||
override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag)
|
override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 [KSerializer.serialize] 前
|
||||||
|
*/
|
||||||
|
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
|
||||||
|
println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
|
||||||
|
when (desc) {
|
||||||
|
// 由于 Byte 的数组有两种方式写入, 需特定读取器
|
||||||
|
ByteArraySerializer.descriptor -> {
|
||||||
|
// ByteArray, 交给 decodeSerializableValue 进行处理
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
is ListLikeDescriptor -> {
|
||||||
|
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 && desc.isNullable) {
|
||||||
|
return NullReader(this.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MapLikeDescriptor -> {
|
||||||
|
val tag = currentTagOrNull
|
||||||
|
|
||||||
|
if (tag != null && input.skipToTagOrNull(tag) { popTag() } == null && desc.isNullable) {
|
||||||
|
return NullReader(this.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return JceMapReader(input.readInt(0), this.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc.kind == StructureKind.CLASS || desc.kind == UnionKind.OBJECT) {
|
||||||
|
val tag = currentTagOrNull
|
||||||
|
if (tag != null) {
|
||||||
|
@Suppress("SENSELESS_COMPARISON") // 推断 bug
|
||||||
|
if (input.skipToTagOrNull(tag) {
|
||||||
|
popTag()
|
||||||
|
return JceStructReader(input)
|
||||||
|
} == null && desc.isNullable) {
|
||||||
|
return NullReader(this.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this // top-level
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input.input.endOfInput) {
|
||||||
|
val tag = currentTagOrNull
|
||||||
|
if (tag != null && input.peakHead().tag > tag) {
|
||||||
|
return NullReader(this.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.beginStructure(desc, *typeParams)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when (deserializer.descriptor) {
|
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
|
||||||
is MapLikeDescriptor -> {
|
if (deserializer is NullReader) {
|
||||||
deserializer as MapLikeSerializer<Any?, Any?, T, *>
|
return null
|
||||||
|
}
|
||||||
val tag = popTag()
|
when (deserializer.descriptor) {
|
||||||
this.input.skipToTagOrNull(tag) {
|
ByteArraySerializer.descriptor -> {
|
||||||
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
|
return input.readByteArray(popTag()) as T
|
||||||
val size = this.input.readInt(0)
|
}
|
||||||
val map = HashMap<Any?, Any?>(size)
|
is ListLikeDescriptor -> {
|
||||||
repeat(size) {
|
if (deserializer is ReferenceArraySerializer<*, *>
|
||||||
pushTag(0)
|
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty()
|
||||||
val key = deserializer.keySerializer.deserialize(this)
|
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer
|
||||||
pushTag(1)
|
) {
|
||||||
val value = deserializer.valueSerializer.deserialize(this)
|
return input.readByteArray(popTag()).toTypedArray() as T
|
||||||
map[key] = value
|
} else if (deserializer is ArrayListSerializer<*>
|
||||||
|
&& (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty()
|
||||||
|
&& (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer
|
||||||
|
) {
|
||||||
|
return input.readByteArray(popTag()).toMutableList() as T
|
||||||
}
|
}
|
||||||
return map as T
|
}
|
||||||
} ?: error("cannot find tag $tag")
|
is MapLikeDescriptor -> {
|
||||||
|
// 将 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 = HashSetSerializer(mapEntrySerial).deserialize(this)
|
||||||
|
return setOfEntries.associateBy({ it.key }, { it.value }) as T
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ByteArraySerializer.descriptor -> input.readByteArray(popTag()) as T
|
return super.decodeSerializableValue(deserializer)
|
||||||
ShortArraySerializer.descriptor -> input.readShortArray(popTag()) as T
|
}
|
||||||
IntArraySerializer.descriptor -> input.readIntArray(popTag()) as T
|
|
||||||
LongArraySerializer.descriptor -> input.readLongArray(popTag()) as T
|
|
||||||
FloatArraySerializer.descriptor -> input.readFloatArray(popTag()) as T
|
|
||||||
DoubleArraySerializer.descriptor -> input.readDoubleArray(popTag()) as T
|
|
||||||
CharArraySerializer.descriptor -> input.readByteArray(popTag()).map { it.toChar() }.toCharArray() as T
|
|
||||||
BooleanArraySerializer.descriptor -> input.readBooleanArray(popTag()) as T
|
|
||||||
|
|
||||||
is ArrayClassDesc -> {
|
@Suppress("UNCHECKED_CAST")
|
||||||
deserializer as ArrayListSerializer<Any?>
|
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
|
||||||
|
return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) as? T ?: error("value is not optional but cannot find")
|
||||||
val tag = popTag()
|
|
||||||
input.skipToTagOrNull(tag) { head ->
|
|
||||||
return Array(input.readInt(0)) {
|
|
||||||
input.readHead()
|
|
||||||
deserializer.deserialize(this)
|
|
||||||
} as T
|
|
||||||
} ?: error("cannot find tag $tag")
|
|
||||||
}
|
|
||||||
is ListLikeDescriptor -> {
|
|
||||||
deserializer as ListLikeSerializer<Any?, T, *>
|
|
||||||
|
|
||||||
val tag = currentTag
|
|
||||||
input.skipToTagOrNull(tag) { head ->
|
|
||||||
val size = input.readInt(0)
|
|
||||||
val list = ArrayList<Any?>(size)
|
|
||||||
|
|
||||||
repeat(size) {
|
|
||||||
//input.readHead()
|
|
||||||
this.pushTag( 0)
|
|
||||||
list.add(deserializer.typeParams[0].also { println(it.getClassName()) }.deserialize(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
return list as T
|
|
||||||
} ?: error("cannot find tag $tag")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
if (input.peakHead().type.toInt() == STRUCT_BEGIN) {
|
|
||||||
input.readHead()
|
|
||||||
deserializer.deserialize(this).also { input.readHead() }
|
|
||||||
} else deserializer.deserialize(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int =
|
override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int =
|
||||||
@ -606,23 +688,23 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val BYTE: Int = 0
|
internal const val BYTE: Byte = 0
|
||||||
internal const val DOUBLE: Int = 5
|
internal const val DOUBLE: Byte = 5
|
||||||
internal const val FLOAT: Int = 4
|
internal const val FLOAT: Byte = 4
|
||||||
internal const val INT: Int = 2
|
internal const val INT: Byte = 2
|
||||||
internal const val JCE_MAX_STRING_LENGTH = 104857600
|
internal const val JCE_MAX_STRING_LENGTH = 104857600
|
||||||
internal const val LIST: Int = 9
|
internal const val LIST: Byte = 9
|
||||||
internal const val LONG: Int = 3
|
internal const val LONG: Byte = 3
|
||||||
internal const val MAP: Int = 8
|
internal const val MAP: Byte = 8
|
||||||
internal const val SHORT: Int = 1
|
internal const val SHORT: Byte = 1
|
||||||
internal const val SIMPLE_LIST: Int = 13
|
internal const val SIMPLE_LIST: Byte = 13
|
||||||
internal const val STRING1: Int = 6
|
internal const val STRING1: Byte = 6
|
||||||
internal const val STRING4: Int = 7
|
internal const val STRING4: Byte = 7
|
||||||
internal const val STRUCT_BEGIN: Int = 10
|
internal const val STRUCT_BEGIN: Byte = 10
|
||||||
internal const val STRUCT_END: Int = 11
|
internal const val STRUCT_END: Byte = 11
|
||||||
internal const val ZERO_TYPE: Int = 12
|
internal const val ZERO_TYPE: Byte = 12
|
||||||
|
|
||||||
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
|
private fun Any?.getClassName(): String = (if (this == null) Unit::class else this::class).simpleName ?: "<unnamed class>"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
|
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
package net.mamoe.mirai.qqandroid.io.serialization
|
package net.mamoe.mirai.qqandroid.io.serialization
|
||||||
|
|
||||||
|
import kotlinx.io.core.readBytes
|
||||||
import kotlinx.serialization.SerialId
|
import kotlinx.serialization.SerialId
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.mamoe.mirai.qqandroid.io.JceOutput
|
||||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||||
|
import net.mamoe.mirai.qqandroid.io.buildJcePacket
|
||||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||||
|
import net.mamoe.mirai.utils.io.toUHexString
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
JceDecoderTest().testSimpleMap()
|
||||||
|
}
|
||||||
|
|
||||||
class JceDecoderTest {
|
class JceDecoderTest {
|
||||||
|
|
||||||
@ -18,23 +26,113 @@ class JceDecoderTest {
|
|||||||
@SerialId(4) val long: Long = 123,
|
@SerialId(4) val long: Long = 123,
|
||||||
@SerialId(5) val float: Float = 123f,
|
@SerialId(5) val float: Float = 123f,
|
||||||
@SerialId(6) val double: Double = 123.0
|
@SerialId(6) val double: Double = 123.0
|
||||||
) : JceStruct
|
) : JceStruct {
|
||||||
|
override fun writeTo(output: JceOutput) = output.run {
|
||||||
@Test
|
writeString(string, 0)
|
||||||
fun testEncoder() {
|
writeByte(byte, 1)
|
||||||
println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexJceStruct.serializer()).contentToString())
|
writeShort(short, 2)
|
||||||
|
writeInt(int, 3)
|
||||||
|
writeLong(long, 4)
|
||||||
|
writeFloat(float, 5)
|
||||||
|
writeDouble(double, 6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testEncoder2() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class TestComplexJceStruct(
|
class TestComplexJceStruct(
|
||||||
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
|
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
|
||||||
@SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3),
|
@SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3), // error here
|
||||||
@SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"),
|
@SerialId(9) val map: Map<String, Map<String, ByteArray>> = mapOf("哈哈" to mapOf("哈哈" to byteArrayOf(1, 2, 3))),
|
||||||
@SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct()
|
// @SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct(),
|
||||||
|
@SerialId(11) val byteList2: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(1, 2, 3))
|
||||||
) : JceStruct
|
) : JceStruct
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class TestComplexNullableJceStruct(
|
||||||
|
@SerialId(7) val byteArray: ByteArray? = byteArrayOf(1, 2, 3),
|
||||||
|
@SerialId(8) val byteList: List<Byte>? = listOf(1, 2, 3), // error here
|
||||||
|
@SerialId(9) val map: Map<String, Map<String, ByteArray>>? = mapOf("哈哈" to mapOf("哈哈" to byteArrayOf(1, 2, 3))),
|
||||||
|
@SerialId(10) val nestedJceStruct: TestSimpleJceStruct? = TestSimpleJceStruct(),
|
||||||
|
@SerialId(11) val byteList2: List<List<Int>>? = listOf(listOf(1, 2, 3), listOf(1, 2, 3))
|
||||||
|
) : JceStruct
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncoder() {
|
||||||
|
println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexNullableJceStruct.serializer()).contentToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncoder2() {
|
||||||
|
assertEquals(
|
||||||
|
buildJcePacket {
|
||||||
|
writeFully(byteArrayOf(1, 2, 3), 7)
|
||||||
|
writeCollection(listOf(1, 2, 3), 8)
|
||||||
|
writeMap(mapOf("哈哈" to mapOf("哈哈" to byteArrayOf(1, 2, 3))), 9)
|
||||||
|
writeJceStruct(TestSimpleJceStruct(), 10)
|
||||||
|
writeCollection(listOf(listOf(1, 2, 3), listOf(1, 2, 3)), 11)
|
||||||
|
}.readBytes().toUHexString(),
|
||||||
|
TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).toUHexString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNestedList() {
|
||||||
|
@Serializable
|
||||||
|
class TestNestedList(
|
||||||
|
@SerialId(7) val array: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3))
|
||||||
|
)
|
||||||
|
|
||||||
|
println(buildJcePacket {
|
||||||
|
writeCollection(listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3)), 7)
|
||||||
|
}.readBytes().loadAs(TestNestedList.serializer()).contentToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNestedArray() {
|
||||||
|
@Serializable
|
||||||
|
class TestNestedArray(
|
||||||
|
@SerialId(7) val array: Array<Array<Int>> = arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3))
|
||||||
|
)
|
||||||
|
|
||||||
|
println(buildJcePacket {
|
||||||
|
writeFully(arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)), 7)
|
||||||
|
}.readBytes().loadAs(TestNestedArray.serializer()).contentToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSimpleMap() {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class TestSimpleMap(
|
||||||
|
@SerialId(7) val map: Map<String, Long> = mapOf("byteArrayOf(1)" to 2222L)
|
||||||
|
)
|
||||||
|
println(buildJcePacket {
|
||||||
|
writeMap(mapOf("byteArrayOf(1)" to 2222), 7)
|
||||||
|
}.readBytes().loadAs(TestSimpleMap.serializer()).contentToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSimpleList() {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class TestSimpleList(
|
||||||
|
@SerialId(7) val list: List<String> = listOf("asd", "asdasdasd")
|
||||||
|
)
|
||||||
|
println(buildJcePacket {
|
||||||
|
writeCollection(listOf("asd", "asdasdasd"), 7)
|
||||||
|
}.readBytes().loadAs(TestSimpleList.serializer()).contentToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNestedMap() {
|
||||||
|
@Serializable
|
||||||
|
class TestNestedMap(
|
||||||
|
@SerialId(7) val map: Map<ByteArray, Map<ByteArray, ShortArray>> = mapOf(byteArrayOf(1) to mapOf(byteArrayOf(1) to shortArrayOf(2)))
|
||||||
|
)
|
||||||
|
println(buildJcePacket {
|
||||||
|
writeMap(mapOf(byteArrayOf(1) to mapOf(byteArrayOf(1) to shortArrayOf(2))), 7)
|
||||||
|
}.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value!!.contentToString())
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user