mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-24 14:30:09 +08:00
Jce Serialization optional elements support
This commit is contained in:
parent
d47bd11df5
commit
2f9d104f88
@ -55,7 +55,7 @@ kotlin {
|
||||
|
||||
sourceSets.all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
|
||||
dependencies {
|
||||
|
@ -58,7 +58,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
|
||||
dependencies {
|
||||
|
@ -1,8 +1,5 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.io.ByteArrayOutputStream
|
||||
import kotlinx.io.ByteBuffer
|
||||
import kotlinx.io.ByteOrder
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.serialization.*
|
||||
@ -11,6 +8,8 @@ import kotlinx.serialization.modules.EmptyModule
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.utils.io.readIoBuffer
|
||||
import net.mamoe.mirai.utils.io.readString
|
||||
import net.mamoe.mirai.utils.io.toIoBuffer
|
||||
|
||||
@ -23,6 +22,14 @@ fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset
|
||||
return Jce.byCharSet(c).load(deserializer, this)
|
||||
}
|
||||
|
||||
fun <T> BytePacketBuilder.writeJceStruct(serializer: SerializationStrategy<T>, struct: T, charset: JceCharset = JceCharset.GBK) {
|
||||
this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct))
|
||||
}
|
||||
|
||||
fun <T> ByteReadPacket.readRemainingAsJceStruct(serializer: DeserializationStrategy<T>, charset: JceCharset = JceCharset.UTF8): T {
|
||||
return Jce.byCharSet(charset).load(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) {
|
||||
@ -39,7 +46,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
private val count: Int,
|
||||
private val tag: Int,
|
||||
private val parentEncoder: JceEncoder
|
||||
) : JceEncoder(ByteArrayOutputStream()) {
|
||||
) : JceEncoder(BytePacketBuilder()) {
|
||||
override fun SerialDescriptor.getTag(index: Int): Int {
|
||||
return 0
|
||||
}
|
||||
@ -47,12 +54,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
parentEncoder.writeHead(LIST, this.tag)
|
||||
parentEncoder.encodeTaggedInt(0, count)
|
||||
parentEncoder.output.write(this.output.toByteArray())
|
||||
parentEncoder.output.writePacket(this.output.build())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class JceMapWriter(
|
||||
output: ByteArrayOutputStream
|
||||
output: BytePacketBuilder
|
||||
) : JceEncoder(output) {
|
||||
override fun SerialDescriptor.getTag(index: Int): Int {
|
||||
return if (index % 2 == 0) 0 else 1
|
||||
@ -81,7 +88,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
@UseExperimental(ExperimentalIoApi::class)
|
||||
private open inner class JceEncoder(
|
||||
internal val output: ByteArrayOutputStream
|
||||
internal val output: BytePacketBuilder
|
||||
) : TaggedEncoder<Int>() {
|
||||
override val context get() = this@Jce.context
|
||||
|
||||
@ -100,6 +107,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
else -> throw SerializationException("Primitives are not supported at top-level")
|
||||
}
|
||||
|
||||
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
|
||||
is MapLikeDescriptor -> {
|
||||
@ -113,9 +121,6 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
}
|
||||
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
|
||||
is PrimitiveArrayDescriptor -> {
|
||||
// if (value is ByteArray) {
|
||||
// this.encodeTaggedByteArray(popTag(), value)
|
||||
// } else {
|
||||
serializer.serialize(
|
||||
ListWriter(
|
||||
when (value) {
|
||||
@ -132,13 +137,16 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
),
|
||||
value
|
||||
)
|
||||
// }
|
||||
}
|
||||
is ArrayClassDesc -> {
|
||||
serializer.serialize(
|
||||
ListWriter((value as Array<*>).size, popTag(), this),
|
||||
value
|
||||
)
|
||||
val descriptor = serializer.descriptor as ReferenceArraySerializer<Any, Any?>
|
||||
if (descriptor.typeParams.isNotEmpty() && descriptor.typeParams[0] is ByteSerializer) {
|
||||
encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray())
|
||||
} else
|
||||
serializer.serialize(
|
||||
ListWriter((value as Array<*>).size, popTag(), this),
|
||||
value
|
||||
)
|
||||
}
|
||||
is ListLikeDescriptor -> {
|
||||
serializer.serialize(
|
||||
@ -151,11 +159,15 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
if (currentTagOrNull == null) {
|
||||
serializer.serialize(this, value)
|
||||
} else {
|
||||
this.writeHead(STRUCT_BEGIN, currentTag)
|
||||
this.writeHead(STRUCT_BEGIN, popTag())
|
||||
serializer.serialize(this, value)
|
||||
this.writeHead(STRUCT_END, 0)
|
||||
}
|
||||
} else serializer.serialize(this, value)
|
||||
} else if (value is ProtoBuf) {
|
||||
this.encodeTaggedByteArray(popTag(), kotlinx.serialization.protobuf.ProtoBuf.dump(value))
|
||||
} else {
|
||||
serializer.serialize(this, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,7 +176,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
writeHead(ZERO_TYPE, tag)
|
||||
} else {
|
||||
writeHead(BYTE, tag)
|
||||
output.write(value.toInt())
|
||||
output.writeByte(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,7 +185,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
encodeTaggedByte(tag, value.toByte())
|
||||
} else {
|
||||
writeHead(SHORT, tag)
|
||||
output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
|
||||
output.writeShort(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,18 +194,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
encodeTaggedShort(tag, value.toShort())
|
||||
} else {
|
||||
writeHead(INT, tag)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
|
||||
output.writeInt(value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedFloat(tag: Int, value: Float) {
|
||||
writeHead(FLOAT, tag)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
|
||||
output.writeFloat(value)
|
||||
}
|
||||
|
||||
override fun encodeTaggedDouble(tag: Int, value: Double) {
|
||||
writeHead(DOUBLE, tag)
|
||||
output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
|
||||
output.writeDouble(value)
|
||||
}
|
||||
|
||||
override fun encodeTaggedLong(tag: Int, value: Long) {
|
||||
@ -201,7 +213,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
encodeTaggedInt(tag, value.toInt())
|
||||
} else {
|
||||
writeHead(LONG, tag)
|
||||
output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
|
||||
output.writeLong(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,19 +240,20 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
writeHead(SIMPLE_LIST, tag)
|
||||
writeHead(BYTE, 0)
|
||||
encodeTaggedInt(0, bytes.size)
|
||||
output.write(bytes)
|
||||
output.writeFully(bytes)
|
||||
}
|
||||
|
||||
override fun encodeTaggedString(tag: Int, value: String) {
|
||||
require(value.length <= JCE_MAX_STRING_LENGTH) { "string is too long for tag $tag" }
|
||||
val array = value.toByteArray(charset.kotlinCharset)
|
||||
if (array.size > 255) {
|
||||
writeHead(STRING4, tag)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
|
||||
output.write(array)
|
||||
output.writeInt(array.size)
|
||||
output.writeFully(array)
|
||||
} else {
|
||||
writeHead(STRING1, tag)
|
||||
output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
|
||||
output.write(array)
|
||||
output.writeByte(array.size.toByte()) // one byte
|
||||
output.writeFully(array)
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,12 +275,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
@PublishedApi
|
||||
internal fun writeHead(type: Byte, tag: Int) {
|
||||
if (tag < 15) {
|
||||
this.output.write((tag shl 4) or type.toInt())
|
||||
this.output.writeByte(((tag shl 4) or type.toInt()).toByte())
|
||||
return
|
||||
}
|
||||
if (tag < 256) {
|
||||
this.output.write(type.toInt() or 0xF0)
|
||||
this.output.write(tag)
|
||||
this.output.writeByte((type.toInt() or 0xF0).toByte())
|
||||
this.output.writeByte(tag.toByte())
|
||||
return
|
||||
}
|
||||
error("tag is too large: $tag")
|
||||
@ -305,7 +318,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
input: JceInput
|
||||
) : JceDecoder(input) {
|
||||
override fun endStructure(desc: SerialDescriptor) {
|
||||
input.readHead() // STRUCT_END
|
||||
input.readHeadOrNull() // STRUCT_END
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,11 +346,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
|
||||
override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int =
|
||||
TODO()
|
||||
|
||||
/**
|
||||
* 在 [KSerializer.serialize] 前
|
||||
*/
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
|
||||
println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
|
||||
//println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
|
||||
when (desc) {
|
||||
// 由于 Byte 的数组有两种方式写入, 需特定读取器
|
||||
ByteArraySerializer.descriptor -> {
|
||||
@ -374,7 +388,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
return NullReader(this.input)
|
||||
}
|
||||
|
||||
if (tag!=null) {
|
||||
if (tag != null) {
|
||||
popTag()
|
||||
}
|
||||
return JceMapReader(input.readInt(0), this.input)
|
||||
@ -411,30 +425,40 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
}
|
||||
|
||||
override fun decodeTaggedNotNullMark(tag: Int): Boolean {
|
||||
return !input.input.endOfInput && input.peakHead().tag <= tag
|
||||
return !isTagOptional(tag)
|
||||
}
|
||||
|
||||
fun isTagOptional(tag: Int): Boolean {
|
||||
return input.input.endOfInput || input.peakHead().tag > tag
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
|
||||
println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
|
||||
//println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
|
||||
if (deserializer is NullReader) {
|
||||
return null
|
||||
}
|
||||
when (deserializer.descriptor) {
|
||||
ByteArraySerializer.descriptor -> {
|
||||
return input.readByteArray(popTag()) as T
|
||||
val tag = popTag()
|
||||
return if (isTagOptional(tag)) input.readByteArrayOrNull(tag) as? T
|
||||
else input.readByteArray(tag) 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
|
||||
val tag = popTag()
|
||||
return if (isTagOptional(tag)) input.readByteArrayOrNull(tag)?.toTypedArray() as? T
|
||||
else input.readByteArray(tag).toTypedArray() as T
|
||||
} else if (deserializer is ArrayListSerializer<*>
|
||||
&& (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty()
|
||||
&& (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer
|
||||
) {
|
||||
return input.readByteArray(popTag()).toMutableList() as T
|
||||
val tag = popTag()
|
||||
return if (isTagOptional(tag)) input.readByteArrayOrNull(tag)?.toMutableList() as? T
|
||||
else input.readByteArray(tag).toMutableList() as T
|
||||
}
|
||||
return super.decodeSerializableValue(deserializer)
|
||||
}
|
||||
@ -447,7 +471,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
}
|
||||
}
|
||||
val tag = currentTagOrNull ?: return deserializer.deserialize(this)
|
||||
return if (this.decodeTaggedNotNullMark(tag)){
|
||||
return if (this.decodeTaggedNotNullMark(tag)) {
|
||||
deserializer.deserialize(this)
|
||||
} else {
|
||||
null
|
||||
@ -456,7 +480,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
|
||||
@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")
|
||||
return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) as? T ?: error("value with tag $currentTagOrNull is not optional but cannot find")
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,17 +495,24 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun readHead(): JceHead = input.readHead()
|
||||
internal fun readHead(): JceHead = input.readHead() ?: error("no enough data to read head")
|
||||
|
||||
@PublishedApi
|
||||
internal fun peakHead(): JceHead = input.makeView().readHead()
|
||||
internal fun readHeadOrNull(): JceHead? = input.readHead()
|
||||
|
||||
private fun IoBuffer.readHead(): JceHead {
|
||||
@PublishedApi
|
||||
internal fun peakHead(): JceHead = input.makeView().readHead() ?: error("no enough data to read head")
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE") // 避免 stacktrace 出现两个 readHead
|
||||
private inline fun IoBuffer.readHead(): JceHead? {
|
||||
if (endOfInput) return null
|
||||
val var2 = readUByte()
|
||||
val type = var2 and 15u
|
||||
var tag = var2.toUInt() shr 4
|
||||
if (tag == 15u)
|
||||
if (tag == 15u) {
|
||||
if (endOfInput) return null
|
||||
tag = readUByte().toUInt()
|
||||
}
|
||||
return JceHead(tag = tag.toInt(), type = type.toByte())
|
||||
}
|
||||
|
||||
@ -535,9 +566,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
}
|
||||
|
||||
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
|
||||
when (it.type.toInt()) {
|
||||
9 -> ByteArray(readInt(0)) { readByte(0) }
|
||||
13 -> {
|
||||
when (it.type) {
|
||||
LIST -> ByteArray(readInt(0)) { readByte(0) }
|
||||
SIMPLE_LIST -> {
|
||||
val head = readHead()
|
||||
check(head.type.toInt() == 0) { "type mismatch" }
|
||||
input.readBytes(readInt(0))
|
||||
@ -560,9 +591,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
} as T
|
||||
|
||||
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
|
||||
return when (head.type.toInt()) {
|
||||
6 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
|
||||
7 -> input.readString(
|
||||
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
|
||||
)
|
||||
@ -571,13 +602,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
}
|
||||
|
||||
fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toLong()
|
||||
1 -> input.readShort().toLong()
|
||||
2 -> input.readInt().toLong()
|
||||
3 -> input.readLong()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
return when (it.type) {
|
||||
ZERO_TYPE -> 0
|
||||
BYTE -> input.readByte().toLong()
|
||||
SHORT -> input.readShort().toLong()
|
||||
INT -> input.readInt().toLong()
|
||||
LONG -> input.readLong()
|
||||
else -> error("type mismatch ${it.type} when reading tag $tag")
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,7 +707,6 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
internal inline fun <R> skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
|
||||
while (true) {
|
||||
if (this.input.endOfInput) {
|
||||
println("endOfInput")
|
||||
return null
|
||||
}
|
||||
|
||||
@ -699,7 +729,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
val GBK = Jce(JceCharset.GBK)
|
||||
|
||||
fun byCharSet(c: JceCharset): Jce {
|
||||
return if (c === JceCharset.UTF8) {
|
||||
return if (c == JceCharset.UTF8) {
|
||||
UTF8
|
||||
} else {
|
||||
GBK
|
||||
@ -725,11 +755,22 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
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 {
|
||||
val encoder = ByteArrayOutputStream()
|
||||
fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket {
|
||||
val encoder = BytePacketBuilder()
|
||||
val dumper = JceEncoder(encoder)
|
||||
dumper.encode(serializer, obj)
|
||||
return encoder.toByteArray()
|
||||
return encoder.build()
|
||||
}
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
|
||||
return dumpAsPacket(serializer, obj).readBytes()
|
||||
}
|
||||
|
||||
fun <T> load(deserializer: DeserializationStrategy<T>, packet: ByteReadPacket): T {
|
||||
packet.readIoBuffer().withUse {
|
||||
val decoder = JceDecoder(JceInput(this))
|
||||
return decoder.decode(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
|
||||
|
@ -8,7 +8,6 @@ import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
|
||||
private val EMPTY_MAP = mapOf<String, String>()
|
||||
private val EMPTY_SBUFFER_MAP = mapOf<Int, ByteArray>()
|
||||
|
||||
@Serializable
|
||||
class RequestPacket(
|
||||
|
@ -34,11 +34,11 @@ class SvcReqRegister(
|
||||
@SerialId(24) val iLastWatchStartTime: Long = 0L,
|
||||
@SerialId(26) val uOldSSOIp: Long = 0L,
|
||||
@SerialId(27) val uNewSSOIp: Long = 0L,
|
||||
@SerialId(28) val sChannelNo: String? = "",
|
||||
@SerialId(28) val sChannelNo: String? = null,
|
||||
@SerialId(29) val lCpId: Long = 0L,
|
||||
@SerialId(30) val strVendorName: String? = null,
|
||||
@SerialId(31) val strVendorOSName: String? = null,
|
||||
@SerialId(32) val strIOSIdfa: String? = "",
|
||||
@SerialId(32) val strIOSIdfa: String? = null,
|
||||
@SerialId(33) val bytes_0x769_reqbody: ByteArray? = null,
|
||||
@SerialId(34) val bIsSetStatus: Byte = 0,
|
||||
@SerialId(35) val vecServerBuf: ByteArray? = null,
|
||||
|
@ -250,7 +250,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) {
|
||||
val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer())
|
||||
PacketLogger.verbose(uni.toString())
|
||||
consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
|
||||
/// consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ internal class RequestPushNotify(
|
||||
@SerialId(1) val ctype: Byte = 0,
|
||||
@SerialId(2) val strService: String?,
|
||||
@SerialId(3) val strCmd: String?,
|
||||
@SerialId(4) val vNotifyCookie: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(4) val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(5) val usMsgType: Int?,
|
||||
@SerialId(6) val wUserActive: Int?,
|
||||
@SerialId(7) val wGeneralFlag: Int?,
|
||||
@ -28,14 +28,14 @@ internal class RequestPushNotify(
|
||||
internal class MsgInfo(
|
||||
@SerialId(0) val lFromUin: Long = 0L,
|
||||
@SerialId(1) val uMsgTime: Long = 0L,
|
||||
@SerialId(2) val shMsgType: Short?,
|
||||
@SerialId(3) val shMsgSeq: Short?,
|
||||
@SerialId(2) val shMsgType: Short,
|
||||
@SerialId(3) val shMsgSeq: Short,
|
||||
@SerialId(4) val strMsg: String?,
|
||||
@SerialId(5) val uRealMsgTime: Int?,
|
||||
@SerialId(6) val vMsg: ByteArray?,
|
||||
@SerialId(7) val uAppShareID: Long?,
|
||||
@SerialId(8) val vMsgCookies: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(9) val vAppShareCookie: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(8) val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(9) val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(10) val lMsgUid: Long?,
|
||||
@SerialId(11) val lLastChangeTime: Long?,
|
||||
@SerialId(12) val vCPicInfo: List<CPicInfo>?,
|
||||
@ -59,12 +59,12 @@ class ShareData(
|
||||
|
||||
@Serializable
|
||||
class TempMsgHead(
|
||||
@SerialId(0) val c2c_type: Int = 0,
|
||||
@SerialId(1) val serviceType: Int = 0
|
||||
@SerialId(0) val c2c_type: Int? = 0,
|
||||
@SerialId(1) val serviceType: Int? = 0
|
||||
) : JceStruct
|
||||
|
||||
@Serializable
|
||||
class CPicInfo(
|
||||
@SerialId(0) val vPath: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(1) val vHost: ByteArray = EMPTY_BYTE_ARRAY
|
||||
@SerialId(1) val vHost: ByteArray? = EMPTY_BYTE_ARRAY
|
||||
) : JceStruct
|
@ -0,0 +1,42 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
import net.mamoe.mirai.utils.firstValue
|
||||
import net.mamoe.mirai.utils.io.debugPrint
|
||||
import net.mamoe.mirai.utils.io.toReadPacket
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
|
||||
class MessageSvc {
|
||||
internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
|
||||
discardExact(8)
|
||||
|
||||
@Serializable
|
||||
class ResponseDataRequestPushNotify(
|
||||
@SerialId(0) val notify: RequestPushNotify
|
||||
) : JceStruct
|
||||
|
||||
val requestPushNotify = readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
|
||||
.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
|
||||
.toReadPacket().apply { discardExact(1) }
|
||||
.debugPrint()
|
||||
.readRemainingAsJceStruct(RequestPushNotify.serializer())
|
||||
|
||||
println(requestPushNotify.contentToString())
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
import net.mamoe.mirai.utils.io.readRemainingBytes
|
||||
|
||||
class MessageSvc {
|
||||
internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
|
||||
val req = Jce.UTF8.load(
|
||||
RequestPacket.serializer(),
|
||||
this.apply { discardExact(8) }.readRemainingBytes()
|
||||
)
|
||||
val messageNotification = Jce.UTF8.load(
|
||||
RequestPushNotify.serializer(),
|
||||
req.sBuffer.loadAs(RequestDataVersion2.serializer()).map.entries.first().value.entries.first().value
|
||||
)
|
||||
println(messageNotification.contentToString())
|
||||
ProtoBuf
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.writeFully
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
|
||||
@ -56,7 +56,8 @@ class StatSvc {
|
||||
client, subAppId = subAppId, commandName = commandName,
|
||||
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
|
||||
) {
|
||||
writeFully(
|
||||
writeJceStruct(
|
||||
RequestPacket.serializer(),
|
||||
RequestPacket(
|
||||
sServantName = "PushService",
|
||||
sFuncName = "SvcReqRegister",
|
||||
@ -121,7 +122,7 @@ class StatSvc {
|
||||
).toByteArray(RequestDataStructSvcReqRegister.serializer())
|
||||
)
|
||||
).toByteArray(RequestDataVersion3.serializer())
|
||||
).toByteArray(RequestPacket.serializer())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
|
||||
|
||||
class Oidb0x769 {
|
||||
@Serializable
|
||||
@ -13,19 +14,19 @@ class Oidb0x769 {
|
||||
// @SerialId(5) val city: String,
|
||||
// @SerialId(6) val req_debug_msg: Int = 0,
|
||||
// @SerialId(101) val query_uin_package_usage_req: QueryUinPackageUsageReq
|
||||
)
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class QueryUinPackageUsageReq(
|
||||
@SerialId(1) val type: Int,
|
||||
@SerialId(2) val uinFileSize: Long = 0
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class ConfigSeq(
|
||||
@SerialId(1) val type: Int, // uint
|
||||
@SerialId(2) val version: Int // uint
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class DeviceInfo(
|
||||
@ -37,7 +38,7 @@ class Oidb0x769 {
|
||||
//@SerialId(6) val storage: Storage,
|
||||
//@SerialId(7) val screen: Screen,
|
||||
//@SerialId(8) val camera: Camera
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class OS(
|
||||
@ -46,27 +47,27 @@ class Oidb0x769 {
|
||||
@SerialId(3) val sdk: String,
|
||||
@SerialId(4) val kernel: String,
|
||||
@SerialId(5) val rom: String
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class Camera(
|
||||
@SerialId(1) val primary: Long,
|
||||
@SerialId(2) val secondary: Long,
|
||||
@SerialId(3) val flag: Boolean
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class CPU(
|
||||
@SerialId(1) val model: String,
|
||||
@SerialId(2) val frequency: Int,
|
||||
@SerialId(3) val cores: Int
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class Memory(
|
||||
@SerialId(1) val total: Int,
|
||||
@SerialId(2) val process: Int
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class Screen(
|
||||
@ -75,11 +76,11 @@ class Oidb0x769 {
|
||||
@SerialId(3) val height: Int,
|
||||
@SerialId(4) val dpi: Int,
|
||||
@SerialId(5) val multiTouch: Boolean
|
||||
)
|
||||
): ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class Storage(
|
||||
@SerialId(1) val builtin: Int,
|
||||
@SerialId(2) val external: Int
|
||||
)
|
||||
): ProtoBuf
|
||||
}
|
@ -7,9 +7,7 @@ import net.mamoe.mirai.qqandroid.io.JceOutput
|
||||
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.io.toUHexString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
fun main() {
|
||||
JceDecoderTest().testSimpleMap()
|
||||
@ -41,7 +39,8 @@ class JceDecoderTest {
|
||||
|
||||
@Serializable
|
||||
class TestComplexJceStruct(
|
||||
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
|
||||
@SerialId(6) val string: String = "haha",
|
||||
@SerialId(7) val byteArray: ByteArray = ByteArray(2000),
|
||||
@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(),
|
||||
@ -50,10 +49,11 @@ class JceDecoderTest {
|
||||
|
||||
@Serializable
|
||||
class TestComplexNullableJceStruct(
|
||||
@SerialId(7) val byteArray: ByteArray? = byteArrayOf(1, 2, 3),
|
||||
@SerialId(6) val string: String = "haha",
|
||||
@SerialId(7) val byteArray: ByteArray = ByteArray(2000),
|
||||
@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(10) val nestedJceStruct: TestComplexJceStruct? = TestComplexJceStruct(),
|
||||
@SerialId(11) val byteList2: List<List<Int>>? = listOf(listOf(1, 2, 3), listOf(1, 2, 3))
|
||||
) : JceStruct
|
||||
|
||||
@ -63,20 +63,10 @@ class JceDecoderTest {
|
||||
}
|
||||
|
||||
@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(),
|
||||
TestComplexNullableJceStruct().toByteArray(TestComplexNullableJceStruct.serializer()).toUHexString()
|
||||
)
|
||||
fun testEncoder3() {
|
||||
println(TestComplexNullableJceStruct().toByteArray(TestComplexNullableJceStruct.serializer()).loadAs(TestComplexNullableJceStruct.serializer()).contentToString())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testNestedList() {
|
||||
@Serializable
|
||||
@ -133,6 +123,6 @@ class JceDecoderTest {
|
||||
)
|
||||
println(buildJcePacket {
|
||||
writeMap(mapOf(byteArrayOf(1) to mapOf(byteArrayOf(1) to shortArrayOf(2))), 7)
|
||||
}.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value!!.contentToString())
|
||||
}.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString())
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
|
||||
class TestRequesetPacket {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
val data =
|
||||
"10 03 2C 3C 4C 56 0B 50 75 73 68 53 65 72 76 69 63 65 66 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 7D 00 01 D6 00 08 00 01 06 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 1D 00 01 BE 00 0A 02 DD B8 E4 76 10 07 2C 36 00 40 0B 5C 6C 7C 8C 9C AC B0 1D C0 01 D6 00 EC FD 10 00 00 10 07 EA 76 78 FD EB 06 C3 33 B2 18 80 32 15 09 BA F1 11 04 08 FC 12 F6 13 19 41 6E 64 72 6F 69 64 20 53 44 4B 20 62 75 69 6C 74 20 66 6F 72 20 78 38 36 F6 14 19 41 6E 64 72 6F 69 64 20 53 44 4B 20 62 75 69 6C 74 20 66 6F 72 20 78 38 36 F6 15 02 31 30 F0 16 01 F1 17 0F 06 FC 18 FC 1A F3 1B A9 00 FE 00 66 00 82 00 FC 1D F6 1E 04 4D 49 55 49 F6 1F 14 3F 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 5F 32 33 5F 31 37 FD 21 00 00 0D 0A 04 08 2E 10 00 0A 05 08 9B 02 10 00 FC 22 FC 24 0B 8C 98 0C A8 0C".hexToBytes()
|
||||
|
||||
println(data.loadAs(RequestPacket.serializer(), JceCharset.UTF8))
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
|
||||
dependencies {
|
||||
|
@ -57,7 +57,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
|
||||
dependencies {
|
||||
|
@ -0,0 +1,9 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
|
||||
|
||||
fun <K, V> Map<K, V>.firstValueOrNull(): V? = this.entries.firstOrNull()?.value
|
||||
|
||||
fun <K, V> Map<K, V>.firstKey(): K = this.entries.first().key
|
||||
|
||||
fun <K, V> Map<K, V>.firstKeyOrNull(): K? = this.entries.firstOrNull()?.key
|
@ -30,7 +30,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.enableLanguageFeature("InlineClasses")
|
||||
languageSettings.enableLanguageFeature("NewInference")
|
||||
|
||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user