Rearrange classes

This commit is contained in:
Him188 2020-03-06 14:41:15 +08:00
parent d8c64286e2
commit 115ccec9ed
8 changed files with 523 additions and 377 deletions

View File

@ -0,0 +1,14 @@
package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.Output
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialFormat
import kotlinx.serialization.SerializationStrategy
interface IOFormat : SerialFormat {
fun <T> dump(serializer: SerializationStrategy<T>, input: Input): ByteArray
fun <T> load(deserializer: DeserializationStrategy<T>, output: Output): T
}

View File

@ -21,6 +21,7 @@ import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceHead
import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toReadPacket
@ -588,7 +589,10 @@ class Jce private constructor(private val charset: JceCharset, override val cont
}
tag = readUByte().toUInt()
}
currentJceHead = JceHead(tag = tag.toInt(), type = type.toByte())
currentJceHead = JceHead(
tag = tag.toInt(),
type = type.toByte()
)
// println("doReadHead: $currentJceHead")
return currentJceHead
}

View File

@ -1,376 +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.io.serialization
import io.ktor.utils.io.core.*
import kotlinx.serialization.*
import kotlinx.serialization.internal.TaggedDecoder
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.BYTE
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.DOUBLE
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.FLOAT
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.INT
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LIST
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LONG
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.MAP
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SHORT
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SIMPLE_LIST
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING1
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING4
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_BEGIN
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_END
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.ZERO_TYPE
import net.mamoe.mirai.utils.io.readString
interface IOFormat : SerialFormat {
fun <T> dump(serializer: SerializationStrategy<T>, input: Input): ByteArray
fun <T> load(deserializer: DeserializationStrategy<T>, output: Output): T
}
/**
* Jce 数据结构序列化和反序列化器.
*
* @author Him188
*/
class JceNew(
override val context: SerialModule
) : SerialFormat, IOFormat {
companion object Default : IOFormat by JceNew(EmptyModule)
override fun <T> dump(serializer: SerializationStrategy<T>, input: Input): ByteArray {
TODO("Not yet implemented")
}
override fun <T> load(deserializer: DeserializationStrategy<T>, output: Output): T {
TODO("Not yet implemented")
}
}
/**
* 标注 JCE 序列化时使用的 ID
*/
@SerialInfo
annotation class JceId(val id: Int)
/**
* 类中元素的 tag
*
* 保留这个结构, 为将来增加功能的兼容性.
*/
internal data class JceTag(
val id: Int,
val isNullable: Boolean
)
@OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai.
private 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.asSequence().filterIsInstance<JceId>().firstOrNull()?.id
?: annotations.asSequence().filterIsInstance<ProtoId>().firstOrNull()?.id) // 旧版本兼容
?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}")
return JceTag(id, this.getElementDescriptor(index).isNullable)
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
TODO("Not yet implemented")
}
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
TODO("Not yet implemented")
}
override fun decodeTaggedInt(tag: JceTag): Int =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceIntValue(it) }
override fun decodeTaggedByte(tag: JceTag): Byte =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceByteValue(it) }
override fun decodeTaggedBoolean(tag: JceTag): Boolean =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceBooleanValue(it) }
override fun decodeTaggedFloat(tag: JceTag): Float =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceFloatValue(it) }
override fun decodeTaggedDouble(tag: JceTag): Double =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceDoubleValue(it) }
override fun decodeTaggedShort(tag: JceTag): Short =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceShortValue(it) }
override fun decodeTaggedLong(tag: JceTag): Long =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceLongValue(it) }
override fun decodeTaggedString(tag: JceTag): String =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceStringValue(it) }
override fun decodeTaggedEnum(tag: JceTag, enumDescription: SerialDescriptor): Int {
return super.decodeTaggedEnum(tag, enumDescription)
}
override fun decodeTaggedChar(tag: JceTag): Char {
return super.decodeTaggedChar(tag)
}
override fun decodeTaggedNotNullMark(tag: JceTag): Boolean {
println("!! decodeTaggedNotNullMark: $tag")
return super.decodeTaggedNotNullMark(tag)
}
}
private class JceInput(
val input: Input, val charset: JceCharset
) {
private var _head: JceHead? = null
val currentHead: JceHead get() = _head ?: error("No current JceHead available")
val currentHeadOrNull: JceHead? get() = _head
/**
* 读取下一个 [JceHead] 并保存. 可通过 [currentHead] 获取这个 [JceHead].
*
* @return 是否成功读取. 返回 `false` 时代表 [Input.endOfInput]
*/
fun prepareNextHead(): Boolean {
return readNextHeadButDoNotAssignTo_Head().also { _head = it; } != null
}
fun nextHead(): JceHead {
check(prepareNextHead()) { "No more JceHead available" }
return currentHead
}
/**
* 直接读取下一个 [JceHead] 并返回.
* 返回 `null` 则代表 [Input.endOfInput]
*/
@Suppress("FunctionName")
@OptIn(ExperimentalUnsignedTypes::class)
private fun readNextHeadButDoNotAssignTo_Head(): JceHead? {
val var2 = input.readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u) {
if (input.endOfInput) {
return null
}
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> skipToTagAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? {
return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() }
}
/**
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常
*/
inline fun <R : Any> skipToTagAndUseIfPossibleOrFail(
tag: Int,
crossinline message: () -> String = { "tag not found: $tag" },
crossinline block: (JceHead) -> R
): R {
return checkNotNull<R>(skipToTagAndUseIfPossibleOrNull(tag, block), message)
}
tailrec fun skipToHeadOrNull(tag: Int): JceHead? {
val current: JceHead = currentHead // 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 = when (type) {
BYTE -> this.input.discardExact(1)
SHORT -> this.input.discardExact(2)
INT -> this.input.discardExact(4)
LONG -> this.input.discardExact(8)
FLOAT -> this.input.discardExact(4)
DOUBLE -> this.input.discardExact(8)
STRING1 -> this.input.discardExact(this.input.readUByte().toInt())
STRING4 -> this.input.discardExact(this.input.readInt())
MAP -> { // map
repeat(skipToTagAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
} * 2) {
useHead { skipField(it.type) }
}
}
LIST -> { // list
repeat(skipToTagAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
}) {
useHead { skipField(it.type) }
}
}
STRUCT_BEGIN -> {
fun skipToStructEnd() {
var head: JceHead
do {
head = nextHead()
skipField(head.type)
} while (head.type.toInt() != 11)
}
skipToStructEnd()
}
STRUCT_END, ZERO_TYPE -> {
}
SIMPLE_LIST -> {
val head = nextHead()
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
this.input.discardExact(
skipToTagAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
}
)
}
else -> error("invalid type: $type")
}
// region readers
fun readJceIntValue(head: JceHead): Int {
return when (head.type) {
ZERO_TYPE -> 0
BYTE -> input.readByte().toInt()
SHORT -> input.readShort().toInt()
INT -> input.readInt()
else -> error("type mismatch: ${head.type}")
}
}
fun readJceShortValue(head: JceHead): Short {
return when (head.type) {
ZERO_TYPE -> 0
BYTE -> input.readByte().toShort()
SHORT -> input.readShort()
else -> error("type mismatch: ${head.type}")
}
}
fun readJceLongValue(head: JceHead): Long {
return when (head.type) {
ZERO_TYPE -> 0
BYTE -> input.readByte().toLong()
SHORT -> input.readShort().toLong()
INT -> input.readInt().toLong()
LONG -> input.readLong()
else -> error("type mismatch ${head.type}")
}
}
fun readJceByteValue(head: JceHead): Byte {
return when (head.type) {
ZERO_TYPE -> 0
BYTE -> input.readByte()
else -> error("type mismatch: ${head.type}")
}
}
fun readJceFloatValue(head: JceHead): Float {
return when (head.type) {
ZERO_TYPE -> 0f
FLOAT -> input.readFloat()
else -> error("type mismatch: ${head.type}")
}
}
@OptIn(ExperimentalUnsignedTypes::class)
fun readJceStringValue(head: JceHead): String {
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)")
}
}
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.type}")
}
}
fun readJceBooleanValue(head: JceHead): Boolean {
return readJceByteValue(head) == 0.toByte()
}
}
@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 {
val typeString = 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")
}
return "JceHead(tag=$tag, type=$type($typeString))"
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.io.serialization.jce
import kotlinx.serialization.*
import kotlinx.serialization.internal.TaggedDecoder
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoId
@OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai.
private 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.asSequence().filterIsInstance<JceId>().firstOrNull()?.id
?: annotations.asSequence().filterIsInstance<ProtoId>().firstOrNull()?.id) // 旧版本兼容
?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}")
return JceTag(
id,
this.getElementDescriptor(index).isNullable
)
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
TODO("Not yet implemented")
}
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
TODO("Not yet implemented")
}
override fun decodeTaggedInt(tag: JceTag): Int =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceIntValue(it) }
override fun decodeTaggedByte(tag: JceTag): Byte =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceByteValue(it) }
override fun decodeTaggedBoolean(tag: JceTag): Boolean =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceBooleanValue(it) }
override fun decodeTaggedFloat(tag: JceTag): Float =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceFloatValue(it) }
override fun decodeTaggedDouble(tag: JceTag): Double =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceDoubleValue(it) }
override fun decodeTaggedShort(tag: JceTag): Short =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceShortValue(it) }
override fun decodeTaggedLong(tag: JceTag): Long =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceLongValue(it) }
override fun decodeTaggedString(tag: JceTag): String =
jce.skipToTagAndUseIfPossibleOrFail(tag.id) { jce.readJceStringValue(it) }
override fun decodeTaggedEnum(tag: JceTag, enumDescription: SerialDescriptor): Int {
return super.decodeTaggedEnum(tag, enumDescription)
}
override fun decodeTaggedChar(tag: JceTag): Char {
return super.decodeTaggedChar(tag)
}
override fun decodeTaggedNotNullMark(tag: JceTag): Boolean {
println("!! decodeTaggedNotNullMark: $tag")
return super.decodeTaggedNotNullMark(tag)
}
}

View File

@ -0,0 +1,237 @@
/*
* 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.io.serialization.jce
import io.ktor.utils.io.core.*
import net.mamoe.mirai.qqandroid.io.serialization.Jce
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.utils.io.readString
/**
* Jce Input. 需要手动管理 head.
*/
class JceInput(
val input: Input, val charset: JceCharset
) {
private var _head: JceHead? = null
val currentHead: JceHead get() = _head ?: error("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 {
check(prepareNextHead()) { "No more JceHead available" }
return currentHead
}
/**
* 直接读取下一个 [JceHead] 并返回.
* 返回 `null` 则代表 [Input.endOfInput]
*/
@Suppress("FunctionName")
@OptIn(ExperimentalUnsignedTypes::class)
private fun readNextHeadButDoNotAssignTo_Head(): JceHead? {
val var2 = input.readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u) {
if (input.endOfInput) {
return null
}
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> skipToTagAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? {
return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() }
}
/**
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常
*/
inline fun <R : Any> skipToTagAndUseIfPossibleOrFail(
tag: Int,
crossinline message: () -> String = { "tag not found: $tag" },
crossinline block: (JceHead) -> R
): R {
return checkNotNull<R>(skipToTagAndUseIfPossibleOrNull(tag, block), message)
}
tailrec fun skipToHeadOrNull(tag: Int): JceHead? {
val current: JceHead = currentHead // 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 = 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
repeat(skipToTagAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
} * 2) {
useHead { skipField(it.type) }
}
}
Jce.LIST -> { // list
repeat(skipToTagAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
}) {
useHead { skipField(it.type) }
}
}
Jce.STRUCT_BEGIN -> {
fun skipToStructEnd() {
var head: JceHead
do {
head = nextHead()
skipField(head.type)
} while (head.type.toInt() != 11)
}
skipToStructEnd()
}
Jce.STRUCT_END, Jce.ZERO_TYPE -> {
}
Jce.SIMPLE_LIST -> {
val head = nextHead()
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
this.input.discardExact(
skipToTagAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
}
)
}
else -> error("invalid type: $type")
}
// region readers
fun readJceIntValue(head: JceHead): Int {
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.type}")
}
}
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.type}")
}
}
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 {
return when (head.type) {
Jce.ZERO_TYPE -> 0
Jce.BYTE -> input.readByte()
else -> error("type mismatch: ${head.type}")
}
}
fun readJceFloatValue(head: JceHead): Float {
return when (head.type) {
Jce.ZERO_TYPE -> 0f
Jce.FLOAT -> input.readFloat()
else -> error("type mismatch: ${head.type}")
}
}
@OptIn(ExperimentalUnsignedTypes::class)
fun readJceStringValue(head: JceHead): String {
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.type}, 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.type}")
}
}
fun readJceBooleanValue(head: JceHead): Boolean {
return readJceByteValue(head) == 0.toByte()
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.io.serialization.jce
import io.ktor.utils.io.core.*
import kotlinx.serialization.*
import kotlinx.serialization.internal.TaggedDecoder
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.serialization.IOFormat
import net.mamoe.mirai.qqandroid.io.serialization.Jce
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.BYTE
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.DOUBLE
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.FLOAT
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.INT
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LIST
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LONG
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.MAP
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SHORT
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SIMPLE_LIST
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING1
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING4
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_BEGIN
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_END
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.ZERO_TYPE
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.utils.io.readString
/**
* Jce 数据结构序列化和反序列化器.
*
* @author Him188
*/
class JceNew(
override val context: SerialModule
) : SerialFormat, IOFormat {
companion object Default : IOFormat by JceNew(
EmptyModule
)
override fun <T> dump(serializer: SerializationStrategy<T>, input: Input): ByteArray {
TODO("Not yet implemented")
}
override fun <T> load(deserializer: DeserializationStrategy<T>, output: Output): T {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.io.serialization.jce
import kotlinx.serialization.SerialInfo
import net.mamoe.mirai.qqandroid.io.serialization.Jce
/**
* 标注 JCE 序列化时使用的 ID
*/
@SerialInfo
annotation class JceId(val id: Int)
/**
* 类中元素的 tag
*
* 保留这个结构, 为将来增加功能的兼容性.
*/
internal data class JceTag(
val id: Int,
val isNullable: Boolean
)
@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 {
val typeString = 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")
}
return "JceHead(tag=$tag, type=$type($typeString))"
}
}

View File

@ -0,0 +1,67 @@
@file:Suppress("unused")
package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.core.Output
import io.ktor.utils.io.core.buildPacket
import io.ktor.utils.io.core.writeInt
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceInput
import kotlin.test.Test
import kotlin.test.assertEquals
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
/*
* 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
*/
@Suppress("INVISIBLE_MEMBER") // bug
internal class JceInputTest {
@Test
fun testHeadStack() {
}
@Test
fun testReadInt() {
val input = JceInput(buildPacket {
writeHead(INT, 0)
writeInt(123456)
}, JceCharset.UTF8)
assertEquals(123456, input.readJceIntValue(input.nextHead()))
}
@PublishedApi
internal fun Output.writeHead(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")
}
}