JceStruct serialization

This commit is contained in:
Him188 2020-01-28 17:11:01 +08:00
parent 4686430248
commit 2e80d33048
4 changed files with 585 additions and 111 deletions

View File

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

View File

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

View File

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

View File

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