Prechádzať zdrojové kódy

Review: misc improvements

Him188 6 rokov pred
rodič
commit
932a3ef1f2
36 zmenil súbory, kde vykonal 665 pridanie a 1021 odobranie
  1. 1 1
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt
  2. 9 6
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
  3. 10 8
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
  4. 2 5
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
  5. 6 11
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt
  6. 55 0
      mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt
  7. 0 28
      mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt
  8. 0 27
      mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt
  9. 0 66
      mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/utils.kt
  10. 1 1
      mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt
  11. 102 0
      mirai-core-qqandroid/src/jvmTest/kotlin/test/protoBuf.kt
  12. 28 18
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt
  13. 7 1
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt
  14. 0 46
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt
  15. 0 2
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt
  16. 1 0
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt
  17. 8 10
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
  18. 1 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt
  19. 0 6
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt
  20. 5 6
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
  21. 4 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt
  22. 167 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt
  23. 57 94
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt
  24. 0 292
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/protoBuf.kt
  25. 8 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt
  26. 0 6
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt
  27. 0 150
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/Varint.kt
  28. 142 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt
  29. 0 102
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/debugging.kt
  30. 14 32
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt
  31. 4 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt
  32. 25 40
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt
  33. 1 0
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt
  34. 0 54
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt
  35. 0 2
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt
  36. 7 3
      mirai-core/src/jvmTest/kotlin/mirai/test/testCaptchaPacket/TestCaptchaPacket.kt

+ 1 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt

@@ -10,6 +10,7 @@
 package net.mamoe.mirai.qqandroid.message
 
 import kotlinx.io.core.buildPacket
+import kotlinx.io.core.discardExact
 import kotlinx.io.core.readBytes
 import kotlinx.io.core.readUInt
 import net.mamoe.mirai.contact.Member
@@ -19,7 +20,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.utils.ExternalImage
 import net.mamoe.mirai.utils.MiraiDebugAPI
 import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.io.discardExact
 import net.mamoe.mirai.utils.io.hexToBytes
 import net.mamoe.mirai.utils.io.read
 import net.mamoe.mirai.utils.io.toByteArray

+ 9 - 6
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt

@@ -7,12 +7,13 @@
  * https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
+@file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE")
+
 package net.mamoe.mirai.qqandroid.network
 
 import kotlinx.atomicfu.AtomicInt
 import kotlinx.atomicfu.atomic
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.toByteArray
+import kotlinx.io.core.*
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.RawAccountIdUse
 import net.mamoe.mirai.data.OnlineStatus
@@ -20,12 +21,10 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
 import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
 import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
-import net.mamoe.mirai.utils.DeviceInfo
 import net.mamoe.mirai.qqandroid.utils.NetworkType
-import net.mamoe.mirai.utils.SystemDeviceInfo
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.cryptor.ECDH
-import net.mamoe.mirai.utils.cryptor.decryptBy
+import net.mamoe.mirai.utils.cryptor.TEA
 import net.mamoe.mirai.utils.io.*
 
 /*
@@ -72,7 +71,7 @@ internal open class QQAndroidClient(
     internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? {
         keys.forEach { (key, value) ->
             kotlin.runCatching {
-                return mapper(data.decryptBy(value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
+                return mapper(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
             }
         }
         return null
@@ -314,6 +313,10 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
 internal typealias PSKeyMap = MutableMap<String, PSKey>
 internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
 
+internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray())
+
+internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
+
 internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) =
     data.read {
         repeat(readShort().toInt()) {

+ 10 - 8
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt

@@ -25,9 +25,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
+import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray
 import net.mamoe.mirai.utils.*
+import net.mamoe.mirai.utils.cryptor.TEA
 import net.mamoe.mirai.utils.cryptor.adjustToPublicKey
-import net.mamoe.mirai.utils.cryptor.decryptBy
 import net.mamoe.mirai.utils.io.*
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
@@ -194,8 +195,8 @@ internal object KnownPacketFactories {
 
             kotlin.runCatching {
                 when (flag2) {
-                    2 -> data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } }
-                    1 -> data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } }
+                    2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } }
+                    1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } }
                     0 -> data
                     else -> error("")
                 }
@@ -335,6 +336,7 @@ internal object KnownPacketFactories {
         return IncomingPacket(packetFactory, ssoSequenceId, packet, commandName)
     }
 
+    @UseExperimental(MiraiInternalAPI::class)
     private suspend fun <T : Packet?> ByteReadPacket.parseOicqResponse(
         bot: QQAndroidBot,
         packetFactory: OutgoingPacketFactory<T>,
@@ -352,10 +354,10 @@ internal object KnownPacketFactories {
         this.discardExact(1) // const = 0
         val packet = when (encryptionMethod) {
             4 -> {
-                var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())
+                var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())
 
                 val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey())
-                data = data.decryptBy(peerShareKey)
+                data = TEA.decrypt(data, peerShareKey)
 
                 packetFactory.decode(bot, data)
             }
@@ -366,13 +368,13 @@ internal object KnownPacketFactories {
                         this.readFully(byteArrayBuffer, 0, size)
 
                         runCatching {
-                            byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size)
+                            TEA.decrypt(byteArrayBuffer, bot.client.ecdh.keyPair.initialShareKey, size)
                         }.getOrElse {
-                            byteArrayBuffer.decryptBy(bot.client.randomKey, size)
+                            TEA.decrypt(byteArrayBuffer, bot.client.randomKey, size)
                         }.toReadPacket()
                     }
                 } else {
-                    this.decryptBy(bot.client.randomKey, 0, (this.remaining - 1).toInt())
+                    TEA.decrypt(this, bot.client.randomKey, 0, (this.remaining - 1).toInt())
                 }
 
                 packetFactory.decode(bot, data)

+ 2 - 5
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt

@@ -11,10 +11,7 @@
 
 package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
 
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
-import kotlinx.io.core.readUByte
-import kotlinx.io.core.readUInt
+import kotlinx.io.core.*
 import net.mamoe.mirai.contact.MemberPermission
 import net.mamoe.mirai.data.MultiPacket
 import net.mamoe.mirai.data.NoPacket
@@ -38,7 +35,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
 import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.debug
-import net.mamoe.mirai.utils.io.discardExact
+
 import net.mamoe.mirai.utils.io.read
 import net.mamoe.mirai.utils.io.readString
 import net.mamoe.mirai.utils.io.toUHexString

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 6 - 11
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt


+ 55 - 0
mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt

@@ -0,0 +1,55 @@
+/*
+ * 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
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package test
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.Input
+import kotlinx.io.core.readAvailable
+import kotlinx.io.core.use
+import kotlinx.io.pool.useInstance
+import net.mamoe.mirai.utils.DefaultLogger
+import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
+import net.mamoe.mirai.utils.io.ByteArrayPool
+import net.mamoe.mirai.utils.io.toReadPacket
+import net.mamoe.mirai.utils.io.toUHexString
+import net.mamoe.mirai.utils.withSwitch
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+
+val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true)
+
+inline fun ByteArray.debugPrintThis(name: String): ByteArray {
+    DebugLogger.debug(name + "=" + this.toUHexString())
+    return this
+}
+
+@UseExperimental(ExperimentalContracts::class, MiraiInternalAPI::class)
+inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R {
+
+    contract {
+        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
+        callsInPlace(onFail, InvocationKind.UNKNOWN)
+    }
+    ByteArrayPool.useInstance {
+        val count = this.readAvailable(it)
+        try {
+            return it.toReadPacket(0, count).use(block)
+        } catch (e: Throwable) {
+            onFail(it.take(count).toByteArray()).readAvailable(it)
+            DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
+            throw e
+        }
+    }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 28
mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 27
mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt


+ 0 - 66
mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/utils.kt

@@ -1,66 +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
- */
-
-@file:Suppress("DEPRECATION")
-
-package androidPacketTests
-
-import net.mamoe.mirai.utils.cryptor.decryptBy
-import org.bouncycastle.jce.provider.JCEECPrivateKey
-import org.bouncycastle.jce.spec.ECParameterSpec
-import org.bouncycastle.jce.spec.ECPrivateKeySpec
-import org.bouncycastle.math.ec.ECConstants
-import org.bouncycastle.math.ec.ECCurve
-import org.bouncycastle.util.encoders.Hex
-import java.math.BigInteger
-import java.security.interfaces.ECPrivateKey
-
-fun ByteArray.decryptBy16Zero() = this.decryptBy(ByteArray(16))
-
-fun ByteArray.dropTCPHead(): ByteArray = this.drop(16 * 3 + 6).toByteArray()
-
-
-@Suppress("LocalVariableName")
-fun loadPrivateKey(s: String): ECPrivateKey {
-    fun fromHex(
-        hex: String
-    ): BigInteger {
-        return BigInteger(1, Hex.decode(hex))
-    }
-
-    // p = 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1
-    // p = 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1
-    val p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37")
-    val a = ECConstants.ZERO
-    val b = BigInteger.valueOf(3)
-    val n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D")
-    val h = BigInteger.valueOf(1)
-
-    val curve: ECCurve = ECCurve.Fp(p, a, b)
-    //ECPoint G = curve.decodePoint(Hex.decode("03"
-    //+ "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D"));
-    //ECPoint G = curve.decodePoint(Hex.decode("03"
-//+ "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D"));
-    val G = curve.decodePoint(
-        Hex.decode(
-            "04"
-                    + "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D"
-                    + "9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D"
-        )
-    )
-
-    return JCEECPrivateKey(
-        "EC",
-        ECPrivateKeySpec(
-            fromHex(s),
-            ECParameterSpec(curve, G, n, h)
-        )
-    )
-    //  return KeyFactory.getInstance("ECDH").generatePrivate(PKCS8EncodedKeySpec(s))
-}

+ 1 - 1
mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt

@@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable
 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.contentToString
 import net.mamoe.mirai.utils.io.toUHexString
 import kotlin.test.Test
 import kotlin.test.assertEquals

+ 102 - 0
mirai-core-qqandroid/src/jvmTest/kotlin/test/protoBuf.kt

@@ -0,0 +1,102 @@
+/*
+ * 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
+ */
+
+@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH")
+
+package net.mamoe.mirai.utils.cryptor
+
+import net.mamoe.mirai.utils.MiraiDebugAPI
+
+// ProtoBuf utilities
+
+
+@Suppress("FunctionName", "SpellCheckingInspection")
+/*
+ * Type	Meaning	Used For
+ * 0	Varint	int32, int64, uint32, uint64, sint32, sint64, bool, enum
+ * 1	64-bit	fixed64, sfixed64, double
+ * 2	Length-delimi	string, bytes, embedded messages, packed repeated fields
+ * 3	Start group	Groups (deprecated)
+ * 4	End group	Groups (deprecated)
+ * 5	32-bit	fixed32, sfixed32, float
+ *
+ * https://www.jianshu.com/p/f888907adaeb
+ */
+@MiraiDebugAPI
+fun ProtoFieldId(serializedId: UInt): ProtoFieldId =
+    ProtoFieldId(
+        protoFieldNumber(serializedId),
+        protoType(serializedId)
+    )
+
+@MiraiDebugAPI
+data class ProtoFieldId(
+    val fieldNumber: Int,
+    val type: ProtoType
+) {
+    override fun toString(): String = "$type $fieldNumber"
+}
+
+@Suppress("SpellCheckingInspection")
+@MiraiDebugAPI
+enum class ProtoType(val value: Byte, private val typeName: String) {
+    /**
+     * int32, int64, uint32, uint64, sint32, sint64, bool, enum
+     */
+    VAR_INT(0x00, "varint"),
+
+    /**
+     * fixed64, sfixed64, double
+     */
+    BIT_64(0x01, " 64bit"),
+
+    /**
+     * string, bytes, embedded messages, packed repeated fields
+     */
+    LENGTH_DELIMI(0x02, "delimi"),
+
+    /**
+     * Groups (deprecated)
+     */
+    START_GROUP(0x03, "startg"),
+
+    /**
+     * Groups (deprecated)
+     */
+    END_GROUP(0x04, "  endg"),
+
+    /**
+     * fixed32, sfixed32, float
+     */
+    BIT_32(0x05, " 32bit"),
+    ;
+
+    override fun toString(): String = this.typeName
+
+    companion object {
+        fun valueOf(value: Byte): ProtoType = values().firstOrNull { it.value == value } ?: error("Unknown ProtoType $value")
+    }
+}
+
+/**
+ * 由 ProtoBuf 序列化后的 id 得到类型
+ *
+ * serializedId = (fieldNumber << 3) | wireType
+ */
+@MiraiDebugAPI
+fun protoType(number: UInt): ProtoType =
+    ProtoType.valueOf(number.toInt().shl(29).ushr(29).toByte())
+
+/**
+ * ProtoBuf 序列化后的 id 转为序列前标记的 id
+ *
+ * serializedId = (fieldNumber << 3) | wireType
+ */
+@MiraiDebugAPI
+fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3)

+ 28 - 18
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt

@@ -15,23 +15,6 @@ import net.mamoe.mirai.network.BotNetworkHandler
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 
-/**
- * 在各平台实现的默认的验证码处理器.
- */
-actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
-    override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
-        error("should be implemented manually by you")
-    }
-
-    override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
-        error("should be implemented manually by you")
-    }
-
-    override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
-        error("should be implemented manually by you")
-    }
-}
-
 @Suppress("ClassName", "PropertyName")
 actual open class BotConfiguration actual constructor() {
     /**
@@ -76,7 +59,7 @@ actual open class BotConfiguration actual constructor() {
     /**
      * 验证码处理器
      */
-    actual var loginSolver: LoginSolver = defaultLoginSolver
+    actual var loginSolver: LoginSolver = LoginSolver.Default
 
     actual companion object {
         /**
@@ -115,4 +98,31 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath:
      */
     @BotConfigurationDsl
     companion object ByDeviceDotJson
+}
+
+/**
+ * 验证码, 设备锁解决器
+ */
+actual abstract class LoginSolver {
+    actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String?
+    actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
+    actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
+
+    actual companion object {
+        actual val Default: LoginSolver
+            get() = object : LoginSolver() {
+                override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
+                    error("should be implemented manually by you")
+                }
+
+                override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
+                    error("should be implemented manually by you")
+                }
+
+                override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
+                    error("should be implemented manually by you")
+                }
+            }
+    }
+
 }

+ 7 - 1
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt

@@ -1,5 +1,7 @@
 package net.mamoe.mirai.utils
 
+import android.os.Build
+
 private var isAddSuppressedSupported: Boolean = true
 
 @MiraiInternalAPI
@@ -9,7 +11,11 @@ actual fun Throwable.addSuppressed(e: Throwable) {
         return
     }
     try {
-        this.addSuppressed(e)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            this.addSuppressed(e)
+        } else {
+            isAddSuppressedSupported = false
+        }
     } catch (e: Exception) {
         isAddSuppressedSupported = false
     }

+ 0 - 46
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt

@@ -1,46 +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.utils.cryptor
-
-import net.mamoe.mirai.utils.MiraiDebugAPI
-import java.lang.reflect.Field
-import kotlin.reflect.full.allSuperclasses
-
-
-@MiraiDebugAPI
-actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String {
-    return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
-            this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
-                .distinctBy { it.name }
-                .filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" }
-                .joinToStringPrefixed(
-                    prefix = prefix
-                ) {
-                    it.isAccessible = true
-                    if (filter != null) {
-                        kotlin.runCatching {
-                            if (!filter(it.name, it.get(this))) return@joinToStringPrefixed ""
-                        }
-                    }
-                    it.name + "=" + kotlin.runCatching {
-                        val value = it.get(this)
-                        if (value == this) "<this>"
-                        else value.contentToString(prefix)
-                    }.getOrElse { "<!>" }
-                } + "\n$prefix}"
-}
-
-internal fun Any.allFieldsFromSuperClassesMatching(classFilter: (Class<out Any>) -> Boolean): Sequence<Field> {
-    return (this::class.java.takeIf(classFilter)?.declaredFields?.asSequence() ?: sequenceOf<Field>()) + this::class.allSuperclasses
-        .asSequence()
-        .map { it.java }
-        .filter(classFilter)
-        .flatMap { it.declaredFields.asSequence() }
-}

+ 0 - 2
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt

@@ -20,8 +20,6 @@ import java.nio.channels.DatagramChannel
 import java.nio.channels.ReadableByteChannel
 import java.nio.channels.WritableByteChannel
 
-actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
-
 /**
  * 多平台适配的 DatagramChannel.
  */

+ 1 - 0
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt

@@ -75,6 +75,7 @@ private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
     }
 }
 
+@UseExperimental(MiraiInternalAPI::class)
 actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
     this.checkOffsetAndLength(offset, length)
     if (length == 0) return ByteArray(0)

+ 8 - 10
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt

@@ -22,7 +22,6 @@ import net.mamoe.mirai.network.ForceOfflineException
 import net.mamoe.mirai.network.LoginFailedException
 import net.mamoe.mirai.network.closeAndJoin
 import net.mamoe.mirai.utils.*
-import net.mamoe.mirai.utils.io.logStacktrace
 import kotlin.coroutines.CoroutineContext
 
 /*
@@ -144,17 +143,16 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
         }
 
         suspend fun doInit() {
-            repeat(2) {
-                try {
-                    _network.init()
-                    return
-                } catch (e: Exception) {
-                    e.logStacktrace()
+            tryNTimesOrException(2) {
+                if (it != 0) {
+                    delay(3000)
+                    logger.warning("Init failed. Retrying in 3s...")
                 }
-                logger.warning("Init failed. Retrying in 3s...")
-                delay(3000)
+                _network.init()
+            }?.let {
+                network.logger.error(it)
+                logger.error("cannot init. some features may be affected")
             }
-            logger.error("cannot init. some features may be affected")
         }
 
         logger.info("Initializing BotNetworkHandler")

+ 1 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt

@@ -17,7 +17,6 @@ import net.mamoe.mirai.event.EventDisabled
 import net.mamoe.mirai.event.Listener
 import net.mamoe.mirai.event.ListeningStatus
 import net.mamoe.mirai.utils.*
-import net.mamoe.mirai.utils.io.logStacktrace
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.coroutineContext
 import kotlin.jvm.JvmField
@@ -65,8 +64,8 @@ internal class Handler<in E : Event>
                     MiraiLogger.warning(
                         """Event processing: An exception occurred but no CoroutineExceptionHandler found, 
                         either in coroutineContext from Handler job, or in subscriberContext""".trimIndent()
+                        , e
                     )
-                    e.logStacktrace("Event processing(No CoroutineExceptionHandler found)")
                 }
             // this.complete() // do not `completeExceptionally`, otherwise parentJob will fai`l.
             // ListeningStatus.STOPPED

+ 0 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt

@@ -34,7 +34,6 @@ import kotlin.coroutines.EmptyCoroutineContext
  * @see CoroutineScope.incoming
  */
 @UseExperimental(ExperimentalContracts::class)
-@MessageDsl
 inline fun <R> CoroutineScope.subscribeMessages(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R
@@ -60,7 +59,6 @@ inline fun <R> CoroutineScope.subscribeMessages(
  * @see CoroutineScope.incoming
  */
 @UseExperimental(ExperimentalContracts::class)
-@MessageDsl
 inline fun <R> CoroutineScope.subscribeGroupMessages(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R
@@ -81,7 +79,6 @@ inline fun <R> CoroutineScope.subscribeGroupMessages(
  * @see CoroutineScope.incoming
  */
 @UseExperimental(ExperimentalContracts::class)
-@MessageDsl
 inline fun <R> CoroutineScope.subscribeFriendMessages(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R
@@ -102,7 +99,6 @@ inline fun <R> CoroutineScope.subscribeFriendMessages(
  * @see CoroutineScope.incoming
  */
 @UseExperimental(ExperimentalContracts::class)
-@MessageDsl
 inline fun <R> Bot.subscribeMessages(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R
@@ -125,7 +121,6 @@ inline fun <R> Bot.subscribeMessages(
  * @see CoroutineScope.incoming
  */
 @UseExperimental(ExperimentalContracts::class)
-@MessageDsl
 inline fun <R> Bot.subscribeGroupMessages(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R
@@ -146,7 +141,6 @@ inline fun <R> Bot.subscribeGroupMessages(
  * @see CoroutineScope.incoming
  */
 @UseExperimental(ExperimentalContracts::class)
-@MessageDsl
 inline fun <R> Bot.subscribeFriendMessages(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R

+ 5 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt

@@ -18,18 +18,17 @@ import kotlin.jvm.JvmStatic
 /**
  * 验证码, 设备锁解决器
  */
-abstract class LoginSolver {
+expect abstract class LoginSolver {
     abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String?
 
     abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
 
     abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
-}
 
-/**
- * 在各平台实现的默认的验证码处理器.
- */
-expect var defaultLoginSolver: LoginSolver
+    companion object {
+        val Default: LoginSolver
+    }
+}
 
 /**
  * [Bot] 配置

+ 4 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt

@@ -29,6 +29,7 @@ import kotlin.jvm.JvmName
  * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
  */
 suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
+    @UseExperimental(MiraiInternalAPI::class)
     ByteArrayPool.useInstance {
         do {
             val size = this.readAvailable(it)
@@ -41,6 +42,7 @@ suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
  * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
  */
 suspend fun ByteReadChannel.copyTo(dst: Output) {
+    @UseExperimental(MiraiInternalAPI::class)
     ByteArrayPool.useInstance {
         do {
             val size = this.readAvailable(it)
@@ -72,6 +74,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel)
  */
 suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) {
     try {
+        @UseExperimental(MiraiInternalAPI::class)
         ByteArrayPool.useInstance {
             do {
                 val size = this.readAvailable(it)
@@ -88,6 +91,7 @@ suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) {
  */
 suspend fun ByteReadChannel.copyAndClose(dst: Output) {
     try {
+        @UseExperimental(MiraiInternalAPI::class)
         ByteArrayPool.useInstance {
             do {
                 val size = this.readAvailable(it)

+ 167 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt

@@ -0,0 +1,167 @@
+/*
+ * 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
+ */
+
+@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH")
+
+package net.mamoe.mirai.utils
+
+import net.mamoe.mirai.utils.io.toUHexString
+import kotlin.reflect.KClass
+import kotlin.reflect.KProperty0
+
+private val indent: String = " ".repeat(4)
+
+/**
+ * 将所有元素加入转换为多行的字符串表示.
+ */
+@MiraiDebugAPI
+internal fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
+    return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform)
+}
+
+/**
+ * 将内容格式化为较可读的字符串输出.
+ *
+ * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)`
+ * [ByteArray] 和 [UByteArray]: 十六进制表示, 通过 [ByteArray.toUHexString]
+ * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString.
+ * [Map]: 多行输出. 每行显示一个值. 递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
+ * `data class`: 调用其 [toString]
+ * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
+ */
+@Suppress("FunctionName") // 这样就不容易被 IDE 提示
+@MiraiDebugAPI("Extremely slow")
+//@Suppress("Unsupported") // false positive
+fun Any?._miraiContentToString(prefix: String = ""): String = when (this) {
+    is Unit -> "Unit"
+    is UInt -> "0x" + this.toUHexString("") + "($this)"
+    is UByte -> "0x" + this.toUHexString() + "($this)"
+    is UShort -> "0x" + this.toUHexString("") + "($this)"
+    is ULong -> "0x" + this.toUHexString("") + "($this)"
+    is Int -> "0x" + this.toUHexString("") + "($this)"
+    is Byte -> "0x" + this.toUHexString() + "($this)"
+    is Short -> "0x" + this.toUHexString("") + "($this)"
+    is Long -> "0x" + this.toUHexString("") + "($this)"
+
+    is Boolean -> if (this) "true" else "false"
+
+    is ByteArray -> {
+        if (this.size == 0) "<Empty ByteArray>"
+        else this.toUHexString()
+    }
+    is UByteArray -> {
+        if (this.size == 0) "<Empty UByteArray>"
+        else this.toUHexString()
+    }
+    is ShortArray -> {
+        if (this.size == 0) "<Empty ShortArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is IntArray -> {
+        if (this.size == 0) "<Empty IntArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is LongArray -> {
+        if (this.size == 0) "<Empty LongArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is FloatArray -> {
+        if (this.size == 0) "<Empty FloatArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is DoubleArray -> {
+        if (this.size == 0) "<Empty DoubleArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is UShortArray -> {
+        if (this.size == 0) "<Empty ShortArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is UIntArray -> {
+        if (this.size == 0) "<Empty IntArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is ULongArray -> {
+        if (this.size == 0) "<Empty LongArray>"
+        else this.iterator()._miraiContentToString()
+    }
+    is Array<*> -> {
+        if (this.size == 0) "<Empty Array>"
+        else this.iterator()._miraiContentToString()
+    }
+    is BooleanArray -> {
+        if (this.size == 0) "<Empty BooleanArray>"
+        else this.iterator()._miraiContentToString()
+    }
+
+    is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) }
+    is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) }
+    is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) }
+    is Map<*, *> -> this.entries.joinToString(
+        prefix = "{",
+        postfix = "}"
+    ) { it.key._miraiContentToString(prefix) + "=" + it.value._miraiContentToString(prefix) }
+    else -> {
+        if (this == null) "null"
+        else if (this::class.isData) this.toString()
+        else {
+            if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) {
+                this.contentToStringReflectively(prefix + indent)
+            } else this.toString()
+            /*
+            (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" +
+                    this::class.members.asSequence().filterIsInstance<KProperty<*>>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC }
+                        .joinToStringPrefixed(
+                            prefix = indent
+                        ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(indent) }.getOrElse { "<!>" } }
+             */
+        }
+    }
+}
+
+@MiraiDebugAPI
+private fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)? = null): String {
+    val newPrefix = "$prefix    "
+    return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
+            this.allMembersFromSuperClassesMatching { it.simpleName?.startsWith("net.mamoe.mirai") == true }
+                .distinctBy { it.name }
+                .filterNot { it.name.contains("$") || it.name == "Companion" || it.isConst || it.name == "serialVersionUID" }
+                .mapNotNull {
+                    val value = it.get()
+                    if (filter != null) {
+                        kotlin.runCatching {
+                            if (!filter(it.name, value))
+                                return@mapNotNull it.name to value
+                        }
+                    }
+                    null
+                }
+                .joinToStringPrefixed(
+                    prefix = newPrefix
+                ) { (name: String, value: Any?) ->
+                    "$name=" + kotlin.runCatching {
+                        if (value == this) "<this>"
+                        else value._miraiContentToString(newPrefix)
+                    }.getOrElse { "<!>" }
+                } + "\n$prefix}"
+}
+
+private fun Any.thisClassAndSuperclassSequence(): Sequence<KClass<out Any>> {
+    return sequenceOf(this::class) +
+            this::class.supertypes.asSequence()
+                .mapNotNull { type -> type.classifier?.takeIf { it is KClass<*> } as? KClass<out Any> }
+}
+
+private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>) -> Boolean): Sequence<KProperty0<*>> {
+    return this.thisClassAndSuperclassSequence()
+        .filter { classFilter(it) }
+        .map { it.members }
+        .flatMap { it.asSequence() }
+        .mapNotNull { it as? KProperty0<*> }
+}

+ 57 - 94
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt

@@ -10,8 +10,8 @@
 package net.mamoe.mirai.utils.cryptor
 
 import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.IoBuffer
 import kotlinx.io.pool.useInstance
+import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.io.ByteArrayPool
 import net.mamoe.mirai.utils.io.toByteArray
 import net.mamoe.mirai.utils.io.toUHexString
@@ -20,7 +20,6 @@ import kotlin.experimental.xor
 import kotlin.jvm.JvmStatic
 import kotlin.random.Random
 
-
 /**
  * 解密错误
  */
@@ -29,98 +28,52 @@ class DecryptionFailedException : Exception {
     constructor(message: String?) : super(message)
 }
 
-
-// region encrypt
-
-/**
- * 使用 [key] 解密 [this]
- *
- * @param key 长度至少为 16
- * @throws DecryptionFailedException 解密错误时
- */
-fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray =
-    TEA.encrypt(this, key, sourceLength = length)
-
-/**
- * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密.
- *
- * @param key 长度至少为 16
- * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程
- * @throws DecryptionFailedException 解密错误时
- */
-inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
-    ByteArrayPool.useInstance {
-        this.readFully(it, offset, length)
-        consumer(it.encryptBy(key, length = length))
-    }
-}
-
-// endregion
-
-
-// region decrypt
-
-/**
- * 使用 [key] 解密 [this].
- *
- * @param key 固定长度 16
- * @throws DecryptionFailedException 解密错误时
- */
-fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray =
-    TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
-
 /**
- * 使用 [key] 解密 [this].
- * [key] 将会被读取掉前 16 个字节
- * 将会使用 [ByteArrayPool] 来缓存 [key].
+ * TEA 算法加密解密工具类.
  *
- * @param key 长度至少为 16
- * @throws DecryptionFailedException 解密错误时
+ * **注意**: 此为 Mirai 内部 API. 它可能会在任何时刻被改变.
  */
-fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
-    checkDataLengthAndReturnSelf(length)
-    return ByteArrayPool.useInstance { keyBuffer ->
-        key.readFully(keyBuffer, 0, key.readRemaining)
-        TEA.decrypt(this, keyBuffer, sourceLength = length)
+@MiraiInternalAPI
+object TEA {
+    // TODO: 2020/2/28 使用 stream 式输入以避免缓存
+
+    /**
+     * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密.
+     *
+     * @param key 长度至少为 16
+     * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程
+     * @throws DecryptionFailedException 解密错误时
+     */
+    inline fun encrypt(
+        receiver: ByteReadPacket,
+        key: ByteArray,
+        offset: Int = 0,
+        length: Int = receiver.remaining.toInt() - offset,
+        consumer: (ByteArray) -> Unit
+    ) {
+        ByteArrayPool.useInstance {
+            receiver.readFully(it, offset, length)
+            consumer(encrypt(it, key, length = length))
+        }
     }
-}
 
-/**
- * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 解密.
- *
- * @param key 长度至少为 16
- * @throws DecryptionFailedException 解密错误时
- */
-fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
-    return ByteArrayPool.useInstance {
-        this.readFully(it, offset, length)
-        it.checkDataLengthAndReturnSelf(length)
-        TEA.decrypt(it, key, length)
+    @JvmStatic
+    fun decrypt(receiver: ByteReadPacket, key: ByteArray, offset: Int = 0, length: Int = (receiver.remaining - offset).toInt()): ByteReadPacket =
+        decryptAsByteArray(receiver, key, offset, length) { data -> ByteReadPacket(data) }
+
+    inline fun <R> decryptAsByteArray(
+        receiver: ByteReadPacket,
+        key: ByteArray,
+        offset: Int = 0,
+        length: Int = (receiver.remaining - offset).toInt(),
+        consumer: (ByteArray) -> R
+    ): R {
+        return ByteArrayPool.useInstance {
+            receiver.readFully(it, offset, length)
+            consumer(decrypt(it, key, length))
+        }.also { receiver.close() }
     }
-}
-
-// endregion
-
-// region ByteReadPacket extension
-
-fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
 
-fun ByteReadPacket.decryptBy(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
-
-inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R =
-    ByteArrayPool.useInstance {
-        readFully(it, offset, length)
-        consumer(it.decryptBy(key, length))
-    }.also { close() }
-
-inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R =
-    ByteArrayPool.useInstance {
-        readFully(it, offset, length)
-        consumer(it.decryptBy(key, length))
-    }.also { close() }
-
-// endregion
-private object TEA {
     private const val UINT32_MASK = 0xffffffffL
 
     private fun doOption(data: ByteArray, key: ByteArray, length: Int, encrypt: Boolean): ByteArray {
@@ -345,15 +298,25 @@ private object TEA {
 
     private fun fail(): Nothing = throw DecryptionFailedException()
 
-    @PublishedApi
+    /**
+     * 使用 [key] 加密 [source]
+     *
+     * @param key 长度至少为 16
+     * @throws DecryptionFailedException 解密错误时
+     */
     @JvmStatic
-    internal fun encrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray =
-        doOption(source, key, sourceLength, true)
-
-    @PublishedApi
+    fun encrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray =
+        doOption(source, key, length, true)
+
+    /**
+     * 使用 [key] 解密 [source]
+     *
+     * @param key 长度至少为 16
+     * @throws DecryptionFailedException 解密错误时
+     */
     @JvmStatic
-    internal fun decrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray =
-        doOption(source, key, sourceLength, false)
+    fun decrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray =
+        doOption(source, key, length, false)
 
     private fun ByteArray.pack(offset: Int, len: Int): Long {
         var result: Long = 0

+ 0 - 292
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/protoBuf.kt

@@ -1,292 +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
- */
-
-@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH")
-
-package net.mamoe.mirai.utils.cryptor
-
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
-import kotlinx.io.core.readUInt
-import kotlinx.io.core.readULong
-import net.mamoe.mirai.utils.MiraiDebugAPI
-import net.mamoe.mirai.utils.MiraiExperimentalAPI
-import net.mamoe.mirai.utils.io.*
-import kotlin.jvm.JvmStatic
-
-// ProtoBuf utilities
-
-
-@Suppress("FunctionName", "SpellCheckingInspection")
-/*
- * Type	Meaning	Used For
- * 0	Varint	int32, int64, uint32, uint64, sint32, sint64, bool, enum
- * 1	64-bit	fixed64, sfixed64, double
- * 2	Length-delimi	string, bytes, embedded messages, packed repeated fields
- * 3	Start group	Groups (deprecated)
- * 4	End group	Groups (deprecated)
- * 5	32-bit	fixed32, sfixed32, float
- *
- * https://www.jianshu.com/p/f888907adaeb
- */
-@MiraiDebugAPI
-fun ProtoFieldId(serializedId: UInt): ProtoFieldId =
-    ProtoFieldId(
-        protoFieldNumber(serializedId),
-        protoType(serializedId)
-    )
-
-@MiraiDebugAPI
-data class ProtoFieldId(
-    val fieldNumber: Int,
-    val type: ProtoType
-) {
-    override fun toString(): String = "$type $fieldNumber"
-}
-
-@Suppress("SpellCheckingInspection")
-@MiraiDebugAPI
-enum class ProtoType(val value: Byte, private val typeName: String) {
-    /**
-     * int32, int64, uint32, uint64, sint32, sint64, bool, enum
-     */
-    VAR_INT(0x00, "varint"),
-
-    /**
-     * fixed64, sfixed64, double
-     */
-    BIT_64(0x01, " 64bit"),
-
-    /**
-     * string, bytes, embedded messages, packed repeated fields
-     */
-    LENGTH_DELIMI(0x02, "delimi"),
-
-    /**
-     * Groups (deprecated)
-     */
-    START_GROUP(0x03, "startg"),
-
-    /**
-     * Groups (deprecated)
-     */
-    END_GROUP(0x04, "  endg"),
-
-    /**
-     * fixed32, sfixed32, float
-     */
-    BIT_32(0x05, " 32bit"),
-    ;
-
-    override fun toString(): String = this.typeName
-
-    companion object {
-        fun valueOf(value: Byte): ProtoType = values().firstOrNull { it.value == value } ?: error("Unknown ProtoType $value")
-    }
-}
-
-/**
- * 由 ProtoBuf 序列化后的 id 得到类型
- *
- * serializedId = (fieldNumber << 3) | wireType
- */
-@MiraiDebugAPI
-fun protoType(number: UInt): ProtoType =
-    ProtoType.valueOf(number.toInt().shl(29).ushr(29).toByte())
-
-/**
- * ProtoBuf 序列化后的 id 转为序列前标记的 id
- *
- * serializedId = (fieldNumber << 3) | wireType
- */
-@MiraiDebugAPI
-fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3)
-
-@MiraiDebugAPI
-class ProtoMap(map: MutableMap<ProtoFieldId, Any>) : MutableMap<ProtoFieldId, Any> by map {
-    companion object {
-        @JvmStatic
-        internal val indent: String = "    "
-    }
-
-    override fun toString(): String {
-        return this.entries.joinToString(prefix = "ProtoMap(size=$size){\n$indent", postfix = "\n}", separator = "\n$indent") {
-            "${it.key}=" + it.value.contentToString()
-        }
-    }
-
-    fun toStringPrefixed(prefix: String): String {
-        return this.entries.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent") {
-            "${it.key}=" + it.value.contentToString(prefix)
-        }
-    }
-    /*
-    override fun put(key: ProtoFieldId, value: Any): Any? {
-        println("${key}=" + value.contentToString())
-        return null
-    }*/
-}
-
-/**
- * 将所有元素加入转换为多行的字符串表示.
- */
-@MiraiDebugAPI
-fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
-    return this.joinToString(prefix = "$prefix${ProtoMap.indent}", separator = "\n$prefix${ProtoMap.indent}", transform = transform)
-}
-
-/**
- * 将内容格式化为较可读的字符串输出.
- *
- * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)`
- * [ByteArray] 和 [UByteaArray]: 十六进制表示, 通过 [ByteArray.toUHexString]
- * [ProtoMap]: 调用 [ProtoMap.toStringPrefixed]
- * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString.
- * [Map]: 多行输出. 每行显示一个值. 递归调用 [contentToString]. 嵌套结构将会以缩进表示
- * `data class`: 调用其 [toString]
- * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [contentToString]. 嵌套结构将会以缩进表示
- */
-@MiraiDebugAPI("Extremely slow")
-//@Suppress("Unsupported") // false positive
-fun Any?.contentToString(prefix: String = ""): String = when (this) {
-    is Unit -> "Unit"
-    is UInt -> "0x" + this.toUHexString("") + "($this)"
-    is UByte -> "0x" + this.toUHexString() + "($this)"
-    is UShort -> "0x" + this.toUHexString("") + "($this)"
-    is ULong -> "0x" + this.toUHexString("") + "($this)"
-    is Int -> "0x" + this.toUHexString("") + "($this)"
-    is Byte -> "0x" + this.toUHexString() + "($this)"
-    is Short -> "0x" + this.toUHexString("") + "($this)"
-    is Long -> "0x" + this.toUHexString("") + "($this)"
-
-    is UVarInt -> "0x" + this.toUHexString("") + "($this)"
-
-    is Boolean -> if (this) "true" else "false"
-
-    is ByteArray -> {
-        if (this.size == 0) "<Empty ByteArray>"
-        else this.toUHexString()
-    }
-    is UByteArray -> {
-        if (this.size == 0) "<Empty UByteArray>"
-        else this.toUHexString()
-    }
-    is ShortArray -> {
-        if (this.size == 0) "<Empty ShortArray>"
-        else this.iterator().contentToString()
-    }
-    is IntArray -> {
-        if (this.size == 0) "<Empty IntArray>"
-        else this.iterator().contentToString()
-    }
-    is LongArray -> {
-        if (this.size == 0) "<Empty LongArray>"
-        else this.iterator().contentToString()
-    }
-    is FloatArray -> {
-        if (this.size == 0) "<Empty FloatArray>"
-        else this.iterator().contentToString()
-    }
-    is DoubleArray -> {
-        if (this.size == 0) "<Empty DoubleArray>"
-        else this.iterator().contentToString()
-    }
-    is UShortArray -> {
-        if (this.size == 0) "<Empty ShortArray>"
-        else this.iterator().contentToString()
-    }
-    is UIntArray -> {
-        if (this.size == 0) "<Empty IntArray>"
-        else this.iterator().contentToString()
-    }
-    is ULongArray -> {
-        if (this.size == 0) "<Empty LongArray>"
-        else this.iterator().contentToString()
-    }
-    is Array<*> -> {
-        if (this.size == 0) "<Empty Array>"
-        else this.iterator().contentToString()
-    }
-    is BooleanArray -> {
-        if (this.size == 0) "<Empty BooleanArray>"
-        else this.iterator().contentToString()
-    }
-
-    is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}"
-    is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
-    is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
-    is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
-    is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) }
-    else -> {
-        if (this == null) "null"
-        else if (this::class.isData) this.toString()
-        else {
-            if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) {
-                this.contentToStringReflectively(prefix + ProtoMap.indent)
-            } else this.toString()
-            /*
-            (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" +
-                    this::class.members.asSequence().filterIsInstance<KProperty<*>>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC }
-                        .joinToStringPrefixed(
-                            prefix = ProtoMap.indent
-                        ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(ProtoMap.indent) }.getOrElse { "<!>" } }
-             */
-        }
-    }
-}
-
-@MiraiExperimentalAPI("Extremely slow")
-@MiraiDebugAPI("Extremely slow")
-expect fun Any.contentToStringReflectively(prefix: String = "", filter: ((String, Any?) -> Boolean)? = null): String
-
-@MiraiDebugAPI
-@Suppress("UNCHECKED_CAST")
-fun ByteReadPacket.readProtoMap(length: Long = this.remaining): ProtoMap {
-    val map = ProtoMap(mutableMapOf())
-
-
-    val expectingRemaining = this.remaining - length
-    while (this.remaining != expectingRemaining) {
-        require(this.remaining > expectingRemaining) { "Expecting to read $length bytes, but read ${expectingRemaining + length - this.remaining}" }
-
-        try {
-            val id = ProtoFieldId(readUVarInt())
-
-            fun readValue(): Any = when (id.type) {
-                ProtoType.VAR_INT -> UVarInt(readUVarInt())
-                ProtoType.BIT_32 -> readUInt()
-                ProtoType.BIT_64 -> readULong()
-                ProtoType.LENGTH_DELIMI -> tryReadProtoMapOrByteArray(readUVarInt().toInt())
-
-                ProtoType.START_GROUP -> Unit
-                ProtoType.END_GROUP -> Unit
-            }
-
-            if (map.containsKey(id)) {
-                if (map[id] !is MutableList<*>) map[id] = mutableListOf(map[id]!!)
-                (map[id] as MutableList<Any>) += readValue()
-            } else {
-                map[id] = readValue()
-            }
-        } catch (e: IllegalStateException) {
-            e.logStacktrace()
-            return map
-        }
-    }
-    return map
-}
-
-private fun ByteReadPacket.tryReadProtoMapOrByteArray(length: Int): Any {
-    val bytes = this.readBytes(length)
-    return try {
-        bytes.toReadPacket().readProtoMap().apply { require(none { it.key.type == ProtoType.START_GROUP || it.key.type == ProtoType.END_GROUP }) }
-    } catch (e: Exception) {
-        bytes
-    }
-}

+ 8 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt

@@ -30,5 +30,13 @@ object ByteArrayPool : DefaultPool<ByteArray>(256) {
     override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE)
 
     override fun clearInstance(instance: ByteArray): ByteArray = instance
+
+    fun checkBufferSize(size: Int) {
+        require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" }
+    }
+
+    fun checkBufferSize(size: Long) {
+        require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" }
+    }
 }
 

+ 0 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt

@@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.Closeable
-import kotlinx.io.errors.IOException
 import net.mamoe.mirai.utils.MiraiInternalAPI
 
 /**
@@ -32,11 +31,6 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl
     val isOpen: Boolean
 }
 
-/**
- * Channel 被关闭
- */
-expect class ClosedChannelException : IOException
-
 /**
  * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
  */

+ 0 - 150
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/Varint.kt

@@ -1,150 +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
- */
-
-@file:JvmName("Varint")
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
-package net.mamoe.mirai.utils.io
-
-import kotlinx.io.core.Input
-import kotlinx.io.core.Output
-import kotlin.experimental.or
-import kotlin.jvm.JvmName
-import kotlin.jvm.JvmSynthetic
-
-/**
- * Tool class for VarInt or VarLong operations.
- *
- * Some code from http://wiki.vg/Protocol.
- *
- * Source project: [Nukkit](http://github.com/nukkit/nukkit)
- *
- * @author MagicDroidX from Nukkit Project
- * @author lmlstarqaq from Nukkit Project
- */
-
-internal fun encodeZigZag32(signedInt: Int): Long {
-    return (signedInt shl 1 xor (signedInt shr 31)).toLong()
-}
-
-@JvmSynthetic
-internal fun decodeZigZag32(uint: UInt): Int {
-    return decodeZigZag32(uint.toLong())
-}
-
-internal fun decodeZigZag32(uint: Long): Int {
-    return (uint shr 1).toInt() xor -(uint and 1).toInt()
-}
-
-internal fun encodeZigZag64(signedLong: Long): Long {
-    return signedLong shl 1 xor (signedLong shr 63)
-}
-
-internal fun decodeZigZag64(signedLong: Long): Long {
-    return signedLong.ushr(1) xor -(signedLong and 1)
-}
-
-
-inline class UVarInt(
-    val data: UInt
-)
-
-@JvmSynthetic
-fun Input.readUVarInt(): UInt {
-    return read(this, 5).toUInt()
-}
-
-
-fun Input.readVarLong(): Long {
-    return decodeZigZag64(readUVarLong().toLong())
-}
-
-
-@JvmSynthetic
-fun Input.readUVarLong(): ULong {
-    return read(this, 10).toULong()
-}
-
-fun Output.writeVarInt(signedInt: Int) {
-    this.writeUVarInt(encodeZigZag32(signedInt))
-}
-
-@JvmSynthetic
-fun Output.writeUVarInt(uint: UInt) {
-    return writeUVarInt(uint.toLong())
-}
-
-fun Output.writeUVarInt(uint: Long) {
-    this.write0(uint)
-}
-
-fun Output.writeVarLong(signedLong: Long) {
-    this.writeUVarLong(encodeZigZag64(signedLong))
-}
-
-fun Output.writeUVarLong(ulong: Long) {
-    this.write0(ulong)
-}
-
-fun UVarInt.toByteArray(): ByteArray {
-    val list = mutableListOf<Byte>()
-    var value = this.data.toLong()
-    do {
-        var temp = (value and 127).toByte()
-        value = value ushr 7
-        if (value != 0L) {
-            temp = temp or 128.toByte()
-        }
-        list += temp
-    } while (value != 0L)
-    return list.toByteArray()
-}
-
-fun UVarInt.toUHexString(separator: String = " "): String = buildString {
-    var value = data.toLong()
-
-    var isFirst = true
-    do {
-        if (!isFirst) {
-            append(separator)
-        }
-        var temp = (value and 127).toByte()
-        value = value ushr 7
-        if (value != 0L) {
-            temp = temp or 128.toByte()
-        }
-        append(temp.toUByte().fixToUHex())
-        isFirst = false
-    } while (value != 0L)
-}
-
-private fun Output.write0(long: Long) {
-    var value = long
-    do {
-        var temp = (value and 127).toByte()
-        value = value ushr 7
-        if (value != 0L) {
-            temp = temp or 128.toByte()
-        }
-        this.writeByte(temp)
-    } while (value != 0L)
-}
-
-private fun read(stream: Input, maxSize: Int): Long {
-    var value: Long = 0
-    var size = 0
-    var b = stream.readByte().toInt()
-    while (b and 0x80 == 0x80) {
-        value = value or ((b and 0x7F).toLong() shl size++ * 7)
-        require(size < maxSize) { "VarLong too big(expecting maxSize=$maxSize)" }
-        b = stream.readByte().toInt()
-    }
-
-    return value or ((b and 0x7F).toLong() shl size * 7)
-}

+ 142 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt

@@ -0,0 +1,142 @@
+/*
+ * 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.utils.io
+
+import io.ktor.utils.io.ByteReadChannel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.io.InputStream
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.Input
+import kotlinx.io.core.readAvailable
+import kotlinx.io.pool.useInstance
+import net.mamoe.mirai.utils.MiraiInternalAPI
+
+
+/**
+ * 由 [chunkedFlow] 分割得到的区块
+ */
+class ChunkedInput(
+    /**
+     * 区块的数据.
+     * 由 [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问.
+     * 它的大小由 [ByteArrayPool.BUFFER_SIZE] 决定, 而有效(有数据)的大小由 [bufferSize] 决定.
+     *
+     * **注意**: 不要将他带出 [Flow.collect] 作用域, 否则将造成内存泄露
+     */
+    val buffer: ByteArray,
+    internal var size: Int
+) {
+    /**
+     * [buffer] 的有效大小
+     */
+    val bufferSize: Int get() = size
+}
+
+/**
+ * 创建将 [ByteReadPacket] 以固定大小分割的 [Sequence].
+ *
+ * 对于一个 1000 长度的 [ByteReadPacket] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence],
+ * 其长度分别为: 300, 300, 300, 100.
+ *
+ * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence]
+ */
+@UseExperimental(MiraiInternalAPI::class)
+fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
+    ByteArrayPool.checkBufferSize(sizePerPacket)
+    if (this.remaining <= sizePerPacket.toLong()) {
+        ByteArrayPool.useInstance { buffer ->
+            return flowOf(ChunkedInput(buffer, this.readAvailable(buffer)))
+        }
+    }
+    return flow {
+        ByteArrayPool.useInstance { buffer ->
+            val chunkedInput = ChunkedInput(buffer, 0)
+            do {
+                chunkedInput.size = [email protected](buffer)
+                emit(chunkedInput)
+            } while ([email protected])
+        }
+    }
+}
+
+/**
+ * 创建将 [ByteReadChannel] 以固定大小分割的 [Sequence].
+ *
+ * 对于一个 1000 长度的 [ByteReadChannel] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence],
+ * 其长度分别为: 300, 300, 300, 100.
+ */
+@UseExperimental(MiraiInternalAPI::class)
+fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
+    ByteArrayPool.checkBufferSize(sizePerPacket)
+    if (this.isClosedForRead) {
+        return flowOf()
+    }
+    return flow {
+        ByteArrayPool.useInstance { buffer ->
+            val chunkedInput = ChunkedInput(buffer, 0)
+            do {
+                chunkedInput.size = [email protected](buffer, 0, buffer.size)
+                emit(chunkedInput)
+            } while ([email protected])
+        }
+    }
+}
+
+
+/**
+ * 创建将 [Input] 以固定大小分割的 [Sequence].
+ *
+ * 对于一个 1000 长度的 [Input] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence],
+ * 其长度分别为: 300, 300, 300, 100.
+ */
+@UseExperimental(MiraiInternalAPI::class, ExperimentalCoroutinesApi::class)
+internal fun Input.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
+    ByteArrayPool.checkBufferSize(sizePerPacket)
+
+    if (this.endOfInput) {
+        return flowOf()
+    }
+
+    return flow {
+        ByteArrayPool.useInstance { buffer ->
+            val chunkedInput = ChunkedInput(buffer, 0)
+            while ([email protected]) {
+                chunkedInput.size = [email protected](buffer)
+                emit(chunkedInput)
+            }
+        }
+    }
+}
+
+/**
+ * 创建将 [ByteReadPacket] 以固定大小分割的 [Sequence].
+ *
+ * 对于一个 1000 长度的 [ByteReadPacket] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence],
+ * 其长度分别为: 300, 300, 300, 100.
+ *
+ * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence]
+ */
+@UseExperimental(MiraiInternalAPI::class, ExperimentalCoroutinesApi::class)
+internal fun InputStream.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
+    ByteArrayPool.checkBufferSize(sizePerPacket)
+
+    return flow {
+        ByteArrayPool.useInstance { buffer ->
+            val chunkedInput = ChunkedInput(buffer, 0)
+            while ([email protected]() != 0) {
+                chunkedInput.size = [email protected](buffer)
+                emit(chunkedInput)
+            }
+        }
+    }
+}

+ 0 - 102
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/debugging.kt

@@ -1,102 +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
- */
-
-@file:Suppress("NOTHING_TO_INLINE")
-@file:JvmMultifileClass
-@file:JvmName("Utils")
-
-package net.mamoe.mirai.utils.io
-
-import kotlinx.io.core.*
-import kotlinx.io.pool.useInstance
-import net.mamoe.mirai.utils.DefaultLogger
-import net.mamoe.mirai.utils.MiraiDebugAPI
-import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
-import net.mamoe.mirai.utils.withSwitch
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.InvocationKind
-import kotlin.contracts.contract
-import kotlin.jvm.JvmMultifileClass
-import kotlin.jvm.JvmName
-
-
-@MiraiDebugAPI("Unsatble")
-val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(false)
-
-@MiraiDebugAPI("Unstable")
-inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
-
-@MiraiDebugAPI("Low efficiency.")
-inline fun String.debugPrintThis(name: String): String {
-    DebugLogger.debug("$name=$this")
-    return this
-}
-
-@MiraiDebugAPI("Low efficiency.")
-inline fun ByteArray.debugPrintThis(name: String): ByteArray {
-    DebugLogger.debug(name + "=" + this.toUHexString())
-    return this
-}
-
-@MiraiDebugAPI("Low efficiency.")
-inline fun IoBuffer.debugPrintThis(name: String): IoBuffer {
-    ByteArrayPool.useInstance {
-        val count = this.readAvailable(it)
-        DebugLogger.debug(name + "=" + it.toUHexString(offset = 0, length = count))
-        return it.toIoBuffer(0, count)
-    }
-}
-
-@MiraiDebugAPI("Low efficiency.")
-inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
-    ByteArrayPool.useInstance {
-        val count = this.readAvailable(it)
-        block(it.toIoBuffer(0, count))
-        return it.toIoBuffer(0, count)
-    }
-}
-
-@MiraiDebugAPI("Low efficiency.")
-inline fun Input.debugDiscardExact(n: Number, name: String = "") {
-    DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
-}
-
-@MiraiDebugAPI("Low efficiency.")
-inline fun ByteReadPacket.debugPrintThis(name: String = ""): ByteReadPacket {
-    ByteArrayPool.useInstance {
-        val count = this.readAvailable(it)
-        DebugLogger.debug("ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
-        return it.toReadPacket(0, count)
-    }
-}
-
-/**
- * 备份数据, 并在 [block] 失败后执行 [onFail].
- *
- * 此方法非常低效. 请仅在测试环境使用.
- */
-@MiraiDebugAPI("Low efficiency")
-@UseExperimental(ExperimentalContracts::class)
-inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R {
-
-    contract {
-        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
-        callsInPlace(onFail, InvocationKind.UNKNOWN)
-    }
-    ByteArrayPool.useInstance {
-        val count = this.readAvailable(it)
-        try {
-            return it.toReadPacket(0, count).use(block)
-        } catch (e: Throwable) {
-            onFail(it.take(count).toByteArray()).readAvailable(it)
-            DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
-            throw e
-        }
-    }
-}

+ 14 - 32
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt

@@ -20,26 +20,12 @@ import kotlinx.io.core.*
 import kotlinx.io.pool.useInstance
 import net.mamoe.mirai.utils.MiraiDebugAPI
 import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.cryptor.contentToString
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 import kotlin.jvm.JvmSynthetic
 
-@Suppress("NOTHING_TO_INLINE")
-inline fun Input.discardExact(n: Short) = this.discardExact(n.toInt())
-
-@Suppress("NOTHING_TO_INLINE")
-@JvmSynthetic
-inline fun Input.discardExact(n: UShort) = this.discardExact(n.toInt())
-
-@Suppress("NOTHING_TO_INLINE")
-@JvmSynthetic
-inline fun Input.discardExact(n: UByte) = this.discardExact(n.toInt())
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun Input.discardExact(n: Byte) = this.discardExact(n.toInt())
-
-fun ByteReadPacket.transferTo(outputStream: OutputStream) {
+@UseExperimental(MiraiInternalAPI::class)
+fun ByteReadPacket.copyTo(outputStream: OutputStream) {
     ByteArrayPool.useInstance {
         while (this.isNotEmpty) {
             outputStream.write(it, 0, this.readAvailable(it))
@@ -56,21 +42,13 @@ inline fun <R> ByteReadPacket.useBytes(
     block(it, n)
 }
 
+@MiraiInternalAPI
 inline fun ByteReadPacket.readPacketExact(
     n: Int = remaining.toInt()//not that safe but adequate
 ): ByteReadPacket = this.readBytes(n).toReadPacket()
 
-inline fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray())
-
-inline fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray())
-
-inline fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().toInt())
-
-inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
-
 private inline fun <R> inline(block: () -> R): R = block()
 
-
 typealias TlvMap = MutableMap<Int, ByteArray>
 
 inline fun TlvMap.getOrFail(tag: Int): ByteArray {
@@ -81,12 +59,13 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr
     return this[tag] ?: error(lazyMessage(tag))
 }
 
+@Suppress("FunctionName")
 @MiraiInternalAPI
-inline fun Input.readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = readTLVMap(true, tagSize, suppressDuplication)
+inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = _readTLVMap(true, tagSize, suppressDuplication)
 
 @MiraiDebugAPI
-@Suppress("DuplicatedCode")
-fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap {
+@Suppress("DuplicatedCode", "FunctionName")
+fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap {
     val map = mutableMapOf<Int, ByteArray>()
     var key = 0
 
@@ -108,11 +87,14 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica
         }.toUByte() != UByte.MAX_VALUE) {
 
         if (map.containsKey(key)) {
+            @Suppress("ControlFlowWithEmptyBody")
             if (!suppressDuplication) {
-                DebugLogger.error(
+                /*
+                @Suppress("DEPRECATION")
+                MiraiLogger.error(
                     @Suppress("IMPLICIT_CAST_TO_ANY")
                     """
-                Error readTLVMap: 
+                Error readTLVMap:
                 duplicated key ${when (tagSize) {
                         1 -> key.toByte()
                         2 -> key.toShort()
@@ -122,13 +104,13 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica
                 map=${map.contentToString()}
                 duplicating value=${this.readUShortLVByteArray().toUHexString()}
                 """.trimIndent()
-                )
+                )*/
             } else {
                 this.discardExact(this.readShort().toInt() and 0xffff)
             }
         } else {
             try {
-                map[key] = this.readUShortLVByteArray()
+                map[key] = this.readBytes(readUShort().toInt())
             } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException
                 // if (expectingEOF) {
                 //     return map

+ 4 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt

@@ -14,8 +14,9 @@
 package net.mamoe.mirai.utils.io
 
 import kotlinx.io.core.*
+import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.coerceAtMostOrFail
-import net.mamoe.mirai.utils.cryptor.encryptBy
+import net.mamoe.mirai.utils.cryptor.TEA
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 
@@ -67,5 +68,6 @@ fun BytePacketBuilder.writeHex(uHex: String) {
 /**
  * 会使用 [ByteArrayPool] 缓存
  */
+@UseExperimental(MiraiInternalAPI::class)
 inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
-    BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) }
+    TEA.encrypt(BytePacketBuilder().apply(encoder).build(), key) { decrypted -> writeFully(decrypted) }

+ 25 - 40
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt

@@ -31,40 +31,12 @@ import javax.imageio.ImageIO
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 
-/**
- * 平台默认的验证码识别器.
- *
- * 可被修改, 除覆盖配置外全局生效.
- */
-actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
-
-
-interface LoginSolverInputReader{
-    suspend fun read(question:String):String?
-
-    suspend operator fun invoke(question: String):String?{
-        return read(question)
-    }
-}
-class DefaultLoginSolverInputReader: LoginSolverInputReader{
-    override suspend fun read(question: String): String? {
-        return readLine()
-    }
-}
-
 class DefaultLoginSolver(
-    val reader: LoginSolverInputReader = DefaultLoginSolverInputReader(),
-    val overrideLogger:MiraiLogger? = null
+    private val input: suspend () -> String,
+    private val overrideLogger: MiraiLogger? = null
 ) : LoginSolver() {
-    fun getLogger(bot: Bot):MiraiLogger{
-        if(overrideLogger!=null){
-            return overrideLogger
-        }
-        return bot.logger
-    }
-
     override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? = loginSolverLock.withLock {
-        val logger = getLogger(bot)
+        val logger = overrideLogger ?: bot.logger
         val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
         withContext(Dispatchers.IO) {
             tempFile.createNewFile()
@@ -86,39 +58,38 @@ class DefaultLoginSolver(
             }
         }
         logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
-                return reader("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")!!.takeUnless { it.isEmpty() || it.length != 4 }.also {
+        return input().takeUnless { it.isEmpty() || it.length != 4 }.also {
             logger.info("正在提交[$it]中...")
         }
     }
 
     override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? = loginSolverLock.withLock {
-        val logger = getLogger(bot)
+        val logger = overrideLogger ?: bot.logger
         logger.info("需要滑动验证码")
         logger.info("请在任意浏览器中打开以下链接并完成验证码. ")
         logger.info("完成后请输入任意字符 ")
         logger.info(url)
-        return reader("完成后请输入任意字符").also {
+        return input().also {
             logger.info("正在提交中...")
         }
     }
 
     override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock {
-        val logger = getLogger(bot)
+        val logger = overrideLogger ?: bot.logger
         logger.info("需要进行账户安全认证")
         logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
         logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
         logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
         logger.info("这步操作将在后续的版本中优化")
         logger.info(url)
-        return reader("完成后请输入任意字符").also {
+        return input().also {
             logger.info("正在提交中...")
         }
     }
-
 }
 
 // Copied from Ktor CIO
-public fun File.writeChannel(
+private fun File.writeChannel(
     coroutineContext: CoroutineContext = Dispatchers.IO
 ): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
     @Suppress("BlockingMethodInNonBlockingContext")
@@ -134,7 +105,7 @@ private val loginSolverLock = Mutex()
 /**
  * @author NaturalHG
  */
-public fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
+private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
     val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
     val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
     val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
@@ -229,7 +200,7 @@ actual open class BotConfiguration actual constructor() {
     /**
      * 验证码处理器
      */
-    actual var loginSolver: LoginSolver = defaultLoginSolver
+    actual var loginSolver: LoginSolver = LoginSolver.Default
 
     actual companion object {
         /**
@@ -279,4 +250,18 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath:
      */
     @BotConfigurationDsl
     companion object ByDeviceDotJson
+}
+
+/**
+ * 验证码, 设备锁解决器
+ */
+actual abstract class LoginSolver {
+    actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String?
+    actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
+    actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
+
+    actual companion object {
+        actual val Default: LoginSolver
+            get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") })
+    }
 }

+ 1 - 0
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt

@@ -56,6 +56,7 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
 
 actual val Http: HttpClient get() = HttpClient(CIO)
 
+@UseExperimental(MiraiInternalAPI::class)
 actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
     this.checkOffsetAndLength(offset, length)
     if (length == 0) return ByteArray(0)

+ 0 - 54
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt

@@ -1,54 +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.utils.cryptor
-
-import net.mamoe.mirai.utils.MiraiDebugAPI
-import java.lang.reflect.Field
-import kotlin.reflect.full.allSuperclasses
-
-
-val FIELD_TRY_SET_ACCESSIBLE = Field::class.java.declaredMethods.firstOrNull { it.name == "trySetAccessible" }
-
-@MiraiDebugAPI
-actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String {
-    val newPrefix = prefix + ProtoMap.indent
-    return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
-            this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
-                .distinctBy { it.name }
-                .filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" }
-                .filterNot { it.isEnumConstant }
-                .map {
-                    FIELD_TRY_SET_ACCESSIBLE?.invoke(it, true) ?: kotlin.run { it.isAccessible = true }
-                    val value = it.get(this)
-                    if (filter != null) {
-                        kotlin.runCatching {
-                            if (!filter(it.name, value)) return@map it.name to FIELD_TRY_SET_ACCESSIBLE
-                        }
-                    }
-                    it.name to value
-                }
-                .filterNot { it.second === FIELD_TRY_SET_ACCESSIBLE }
-                .joinToStringPrefixed(
-                    prefix = newPrefix
-                ) { (name, value) ->
-                    "$name=" + kotlin.runCatching {
-                        if (value == this) "<this>"
-                        else value.contentToString(newPrefix)
-                    }.getOrElse { "<!>" }
-                } + "\n$prefix}"
-}
-
-internal fun Any.allFieldsFromSuperClassesMatching(classFilter: (Class<out Any>) -> Boolean): Sequence<Field> {
-    return (this::class.java.takeIf(classFilter)?.declaredFields?.asSequence() ?: sequenceOf<Field>()) + this::class.allSuperclasses
-        .asSequence()
-        .map { it.java }
-        .filter(classFilter)
-        .flatMap { it.declaredFields.asSequence() }
-}

+ 0 - 2
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt

@@ -21,8 +21,6 @@ import java.nio.channels.ReadableByteChannel
 import java.nio.channels.WritableByteChannel
 
 
-actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
-
 /**
  * 多平台适配的 DatagramChannel.
  */

+ 7 - 3
mirai-core/src/jvmTest/kotlin/mirai/test/testCaptchaPacket/TestCaptchaPacket.kt

@@ -9,16 +9,20 @@
 
 package mirai.test.testCaptchaPacket
 
-import net.mamoe.mirai.utils.cryptor.decryptBy
+import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.utils.cryptor.TEA.decrypt
 import net.mamoe.mirai.utils.io.hexToBytes
 import net.mamoe.mirai.utils.io.toUHexString
 
 
+@MiraiInternalAPI
 fun main() {
     val key = "65 F7 F3 14 E3 94 10 1F DD 95 84 A3 F5 9F AD 94".hexToBytes()
     val data =
-        "8D 4F 6A 70 F8 4A DE 43 AF 75 D1 3F 3A 3F F2 E0 A8 16 1A 46 13 CD B0 51 45 00 29 52 57 75 6D 4A 4C D9 B7 98 8C B0 96 EC 57 4E 67 FB 8D C5 F1 BF 72 38 40 42 19 54 C2 28 F4 72 C8 AE 24 EB 66 B5 D0 45 0B 72 44 81 E2 F6 2B EE C3 85 93 BA CB B7 72 F4 1A 30 F9 5B 3D B0 79 3E F4 0B F2 1A A7 49 60 3B 37 02 60 0C 5D D5 76 76 47 4F B5 B3 F5 CA 58 6C FC D2 41 3E 24 D1 FB 0A 18 53 D8 E5 A5 85 A8 BC 51 54 3B 66 5B 21 C6 7B AF C9 62 F0 AA 9C CF 2E 84 0F CC 15 5B 35 93 49 5C E4 28 49 A7 8A D3 30 A9 6E 36 4E 7A 49 28 69 4D C3 25 39 6E 45 6E 40 F2 86 1E F4 4F 00 A6 9D E6 9B 84 19 69 C1 31 6A 17 BA F0 0D 8A 22 09 86 24 92 F7 22 C3 47 7F F2 BF 94 8A 8A B5 29".hexToBytes()
-            .decryptBy(key)
+        decrypt(
+            "8D 4F 6A 70 F8 4A DE 43 AF 75 D1 3F 3A 3F F2 E0 A8 16 1A 46 13 CD B0 51 45 00 29 52 57 75 6D 4A 4C D9 B7 98 8C B0 96 EC 57 4E 67 FB 8D C5 F1 BF 72 38 40 42 19 54 C2 28 F4 72 C8 AE 24 EB 66 B5 D0 45 0B 72 44 81 E2 F6 2B EE C3 85 93 BA CB B7 72 F4 1A 30 F9 5B 3D B0 79 3E F4 0B F2 1A A7 49 60 3B 37 02 60 0C 5D D5 76 76 47 4F B5 B3 F5 CA 58 6C FC D2 41 3E 24 D1 FB 0A 18 53 D8 E5 A5 85 A8 BC 51 54 3B 66 5B 21 C6 7B AF C9 62 F0 AA 9C CF 2E 84 0F CC 15 5B 35 93 49 5C E4 28 49 A7 8A D3 30 A9 6E 36 4E 7A 49 28 69 4D C3 25 39 6E 45 6E 40 F2 86 1E F4 4F 00 A6 9D E6 9B 84 19 69 C1 31 6A 17 BA F0 0D 8A 22 09 86 24 92 F7 22 C3 47 7F F2 BF 94 8A 8A B5 29".hexToBytes(),
+            key
+        )
     println(data.toUHexString())
 
     //00 02 00 00 08 04 01 E0 00 00 04 56 00 00 00 01 00 00 15 E3 01 00 38 58 CE A0 12 81 31 5C 5E 36 23 5B E4 0E 05 A6 47 BF 7C 1A 7A 35 37 59 90 17 50 66 0C 07 03 77 E4 48 DB 28 0A CF C3 A9 B7 C0 95 D3 9D 00 AA A5 EB FB D6 85 8D 10 61 5A D0 01 03 00 19 02 CA 53 7E F0 7B 32 82 EC 9F DE CF 51 8B A4 93 26 76 EC 42 1C 02 00 74 58 14 00 05 00 00 00 00 00 04 6C 73 64 61 00 40 CE 99 84 E8 F1 59 31 B0 3F 6C 4D 44 09 E4 82 77 96 67 03 A7 3A EA 8F 36 B9 20 79 7E C9 0F 75 3C 2A C3 E1 E5 C6 00 B3 5E 91 5B 47 63 EF AF 30 C0 48 2F 58 23 96 CF 65 2F 4C 75 95 A6 CA 5A 2C 5C 00 10 E1 50 C9 F4 F6 F4 2F D1 7F E9 8C AB B6 1C 38 7B

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov