Commit 2e80d330 authored by Him188's avatar Him188

JceStruct serialization

parent 46864302
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)
\ No newline at end of file
package net.mamoe.mirai.qqandroid.io package net.mamoe.mirai.qqandroid.io
interface JceStruct interface JceStruct {
\ No newline at end of file fun writeTo(output: JceOutput) = Unit
}
\ No newline at end of file
...@@ -13,7 +13,6 @@ import net.mamoe.mirai.qqandroid.io.JceStruct ...@@ -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 ...@@ -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 ...@@ -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,9 +113,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -115,9 +113,9 @@ 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) {
...@@ -127,12 +125,14 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -127,12 +125,14 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
is FloatArray -> value.size is FloatArray -> value.size
is DoubleArray -> value.size is DoubleArray -> value.size
is CharArray -> value.size is CharArray -> value.size
is ByteArray -> value.size
is BooleanArray -> value.size
else -> error("unknown array type: ${value.getClassName()}") else -> error("unknown array type: ${value.getClassName()}")
}, popTag(), this }, popTag(), this
), ),
value value
) )
} // }
} }
is ArrayClassDesc -> { is ArrayClassDesc -> {
serializer.serialize( serializer.serialize(
...@@ -260,13 +260,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -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 ...@@ -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 ...@@ -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)
@Suppress("UNCHECKED_CAST") /**
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when (deserializer.descriptor) { * 在 [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 -> { is MapLikeDescriptor -> {
deserializer as MapLikeSerializer<Any?, Any?, T, *> val tag = currentTagOrNull
val tag = popTag()
this.input.skipToTagOrNull(tag) {
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
val size = this.input.readInt(0)
val map = HashMap<Any?, Any?>(size)
repeat(size) {
pushTag(0)
val key = deserializer.keySerializer.deserialize(this)
pushTag(1)
val value = deserializer.valueSerializer.deserialize(this)
map[key] = value
}
return map as T
} ?: error("cannot find tag $tag")
}
ByteArraySerializer.descriptor -> input.readByteArray(popTag()) as T
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 -> { if (tag != null && input.skipToTagOrNull(tag) { popTag() } == null && desc.isNullable) {
deserializer as ArrayListSerializer<Any?> return NullReader(this.input)
}
val tag = popTag() return JceMapReader(input.readInt(0), this.input)
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 if (desc.kind == StructureKind.CLASS || desc.kind == UnionKind.OBJECT) {
input.skipToTagOrNull(tag) { head -> val tag = currentTagOrNull
val size = input.readInt(0) if (tag != null) {
val list = ArrayList<Any?>(size) @Suppress("SENSELESS_COMPARISON") // 推断 bug
if (input.skipToTagOrNull(tag) {
popTag()
return JceStructReader(input)
} == null && desc.isNullable) {
return NullReader(this.input)
}
}
repeat(size) { return this // top-level
//input.readHead()
this.pushTag( 0)
list.add(deserializer.typeParams[0].also { println(it.getClassName()) }.deserialize(this))
} }
return list as T if (!input.input.endOfInput) {
} ?: error("cannot find tag $tag") val tag = currentTagOrNull
if (tag != null && input.peakHead().tag > tag) {
return NullReader(this.input)
} }
else -> {
if (input.peakHead().type.toInt() == STRUCT_BEGIN) {
input.readHead()
deserializer.deserialize(this).also { input.readHead() }
} else deserializer.deserialize(this)
} }
return super.beginStructure(desc, *typeParams)
}
@Suppress("UNCHECKED_CAST")
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
if (deserializer is NullReader) {
return null
}
when (deserializer.descriptor) {
ByteArraySerializer.descriptor -> {
return input.readByteArray(popTag()) as T
}
is ListLikeDescriptor -> {
if (deserializer is ReferenceArraySerializer<*, *>
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty()
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer
) {
return input.readByteArray(popTag()).toTypedArray() as T
} else if (deserializer is ArrayListSerializer<*>
&& (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty()
&& (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer
) {
return input.readByteArray(popTag()).toMutableList() as T
}
}
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
}
}
return super.decodeSerializableValue(deserializer)
}
@Suppress("UNCHECKED_CAST")
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) as? T ?: error("value is not optional but cannot find")
} }
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 ...@@ -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 {
......
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 { ...@@ -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 {
override fun writeTo(output: JceOutput) = output.run {
writeString(string, 0)
writeByte(byte, 1)
writeShort(short, 2)
writeInt(int, 3)
writeLong(long, 4)
writeFloat(float, 5)
writeDouble(double, 6)
}
}
@Serializable
class TestComplexJceStruct(
@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
@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 ) : JceStruct
@Test @Test
fun testEncoder() { fun testEncoder() {
println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexJceStruct.serializer()).contentToString()) println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexNullableJceStruct.serializer()).contentToString())
} }
@Test @Test
fun testEncoder2() { 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 @Serializable
class TestComplexJceStruct( class TestNestedArray(
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3), @SerialId(7) val array: Array<Array<Int>> = arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3))
@SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3), )
@SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"),
@SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct() println(buildJcePacket {
) : JceStruct 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())
}
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment