From 2f9d104f88314724bbf3b1ce8dac88e8ea0ee40c Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Tue, 28 Jan 2020 20:37:02 +0800 Subject: [PATCH] Jce Serialization optional elements support --- mirai-api-http/build.gradle.kts | 2 +- mirai-core-qqandroid/build.gradle.kts | 2 +- .../mirai/qqandroid/io/serialization/Jce.kt | 167 +++++++++++------- .../network/protocol/jce/RequestPacket.kt | 1 - .../network/protocol/jce/SvcReqRegister.kt | 4 +- .../network/protocol/packet/PacketFactory.kt | 2 +- .../packet/chat/data/PushNotifyPack.kt | 16 +- .../chat/receive/MessageSvc.PushNotify.kt | 42 +++++ .../packet/chat/receive/MessageSvc.kt | 34 ---- .../network/protocol/packet/login/Register.kt | 7 +- .../packet/oidb/oidb0x769/Oidb0x769.kt | 21 +-- .../JceDecoderTest.kt | 26 +-- .../TestRequesetPacket.kt | 17 ++ mirai-core-timpc/build.gradle.kts | 2 +- mirai-core/build.gradle.kts | 2 +- .../kotlin/net.mamoe.mirai/utils/map.kt | 9 + mirai-debug/build.gradle.kts | 2 +- mirai-japt/build.gradle.kts | 2 +- mirai-plugins/image-sender/build.gradle.kts | 2 +- 19 files changed, 213 insertions(+), 147 deletions(-) create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt create mode 100644 mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt diff --git a/mirai-api-http/build.gradle.kts b/mirai-api-http/build.gradle.kts index 5c5ad76ae..4f036e1d1 100644 --- a/mirai-api-http/build.gradle.kts +++ b/mirai-api-http/build.gradle.kts @@ -55,7 +55,7 @@ kotlin { sourceSets.all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") dependencies { diff --git a/mirai-core-qqandroid/build.gradle.kts b/mirai-core-qqandroid/build.gradle.kts index e4d8b96a3..7afca1a66 100644 --- a/mirai-core-qqandroid/build.gradle.kts +++ b/mirai-core-qqandroid/build.gradle.kts @@ -58,7 +58,7 @@ kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") dependencies { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt index b644180d5..c4fa4f6fd 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt @@ -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 { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt index ed46f5cdd..562c2b7c4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt @@ -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( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt index 8b37ed0e4..013f7e0b3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt @@ -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, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index c33272337..019ad2ee6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -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) } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt index 88c6a5d03..234623085 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt @@ -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 \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt new file mode 100644 index 000000000..42c2687b5 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt @@ -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() + } + } +} + diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt deleted file mode 100644 index 2ddd56973..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ /dev/null @@ -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() - } - } -} - diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt index 0930e2928..7ac9839b5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt @@ -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()) + ) ) } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt index da7aed64b..5f82bd34d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt @@ -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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt index 875453673..1d6f56942 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt @@ -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()) } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt new file mode 100644 index 000000000..29ec6edb3 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt @@ -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)) + } + } +} \ No newline at end of file diff --git a/mirai-core-timpc/build.gradle.kts b/mirai-core-timpc/build.gradle.kts index e93a2041c..820adf222 100644 --- a/mirai-core-timpc/build.gradle.kts +++ b/mirai-core-timpc/build.gradle.kts @@ -58,7 +58,7 @@ kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") dependencies { diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts index aba19e519..22b20102d 100644 --- a/mirai-core/build.gradle.kts +++ b/mirai-core/build.gradle.kts @@ -57,7 +57,7 @@ kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") dependencies { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt new file mode 100644 index 000000000..0e9710358 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt @@ -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 \ No newline at end of file diff --git a/mirai-debug/build.gradle.kts b/mirai-debug/build.gradle.kts index 322ebd5a0..9c686358c 100644 --- a/mirai-debug/build.gradle.kts +++ b/mirai-debug/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") } } diff --git a/mirai-japt/build.gradle.kts b/mirai-japt/build.gradle.kts index 6a0bb70af..eee895d1d 100644 --- a/mirai-japt/build.gradle.kts +++ b/mirai-japt/build.gradle.kts @@ -17,7 +17,7 @@ kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") } } diff --git a/mirai-plugins/image-sender/build.gradle.kts b/mirai-plugins/image-sender/build.gradle.kts index 53f40635c..e519033a2 100644 --- a/mirai-plugins/image-sender/build.gradle.kts +++ b/mirai-plugins/image-sender/build.gradle.kts @@ -20,7 +20,7 @@ kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.enableLanguageFeature("NewInference") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") } }