Commit 932a3ef1 authored by Him188's avatar Him188

Review: misc improvements

parent 1ff5df1d
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
...@@ -19,7 +20,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm ...@@ -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.ExternalImage
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI 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.hexToBytes
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toByteArray
......
...@@ -7,12 +7,13 @@ ...@@ -7,12 +7,13 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.*
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse import net.mamoe.mirai.RawAccountIdUse
import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.data.OnlineStatus
...@@ -20,12 +21,10 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot ...@@ -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.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv 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.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.ECDH 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.* import net.mamoe.mirai.utils.io.*
/* /*
...@@ -72,7 +71,7 @@ internal open class QQAndroidClient( ...@@ -72,7 +71,7 @@ internal open class QQAndroidClient(
internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? { internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? {
keys.forEach { (key, value) -> keys.forEach { (key, value) ->
kotlin.runCatching { 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 return null
...@@ -314,6 +313,10 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : ...@@ -314,6 +313,10 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
internal typealias PSKeyMap = MutableMap<String, PSKey> internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token> 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) = internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) =
data.read { data.read {
repeat(readShort().toInt()) { repeat(readShort().toInt()) {
......
...@@ -25,9 +25,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc ...@@ -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.Heartbeat
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc 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.protocol.packet.login.WtLogin
import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray
import net.mamoe.mirai.utils.* 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.adjustToPublicKey
import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
...@@ -194,8 +195,8 @@ internal object KnownPacketFactories { ...@@ -194,8 +195,8 @@ internal object KnownPacketFactories {
kotlin.runCatching { kotlin.runCatching {
when (flag2) { when (flag2) {
2 -> data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } } 2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } }
1 -> data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } } 1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } }
0 -> data 0 -> data
else -> error("") else -> error("")
} }
...@@ -335,6 +336,7 @@ internal object KnownPacketFactories { ...@@ -335,6 +336,7 @@ internal object KnownPacketFactories {
return IncomingPacket(packetFactory, ssoSequenceId, packet, commandName) return IncomingPacket(packetFactory, ssoSequenceId, packet, commandName)
} }
@UseExperimental(MiraiInternalAPI::class)
private suspend fun <T : Packet?> ByteReadPacket.parseOicqResponse( private suspend fun <T : Packet?> ByteReadPacket.parseOicqResponse(
bot: QQAndroidBot, bot: QQAndroidBot,
packetFactory: OutgoingPacketFactory<T>, packetFactory: OutgoingPacketFactory<T>,
...@@ -352,10 +354,10 @@ internal object KnownPacketFactories { ...@@ -352,10 +354,10 @@ internal object KnownPacketFactories {
this.discardExact(1) // const = 0 this.discardExact(1) // const = 0
val packet = when (encryptionMethod) { val packet = when (encryptionMethod) {
4 -> { 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()) val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey())
data = data.decryptBy(peerShareKey) data = TEA.decrypt(data, peerShareKey)
packetFactory.decode(bot, data) packetFactory.decode(bot, data)
} }
...@@ -366,13 +368,13 @@ internal object KnownPacketFactories { ...@@ -366,13 +368,13 @@ internal object KnownPacketFactories {
this.readFully(byteArrayBuffer, 0, size) this.readFully(byteArrayBuffer, 0, size)
runCatching { runCatching {
byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size) TEA.decrypt(byteArrayBuffer, bot.client.ecdh.keyPair.initialShareKey, size)
}.getOrElse { }.getOrElse {
byteArrayBuffer.decryptBy(bot.client.randomKey, size) TEA.decrypt(byteArrayBuffer, bot.client.randomKey, size)
}.toReadPacket() }.toReadPacket()
} }
} else { } 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) packetFactory.decode(bot, data)
......
...@@ -11,10 +11,7 @@ ...@@ -11,10 +11,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.*
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.NoPacket import net.mamoe.mirai.data.NoPacket
...@@ -38,7 +35,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket ...@@ -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.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.debug 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.read
import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
......
...@@ -8,81 +8,34 @@ ...@@ -8,81 +8,34 @@
*/ */
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils.io package test
import kotlinx.io.core.* 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 kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch 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 net.mamoe.mirai.utils.withSwitch
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@MiraiDebugAPI("Unsatble") val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true)
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 { inline fun ByteArray.debugPrintThis(name: String): ByteArray {
DebugLogger.debug(name + "=" + this.toUHexString()) DebugLogger.debug(name + "=" + this.toUHexString())
return this return this
} }
@MiraiDebugAPI("Low efficiency.") @UseExperimental(ExperimentalContracts::class, MiraiInternalAPI::class)
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 { inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R {
contract { contract {
......
/*
* 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))
}
...@@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable ...@@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceOutput import net.mamoe.mirai.qqandroid.io.JceOutput
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.buildJcePacket import net.mamoe.mirai.qqandroid.io.buildJcePacket
import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.contentToString
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
......
/*
* 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)
...@@ -15,23 +15,6 @@ import net.mamoe.mirai.network.BotNetworkHandler ...@@ -15,23 +15,6 @@ import net.mamoe.mirai.network.BotNetworkHandler
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext 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") @Suppress("ClassName", "PropertyName")
actual open class BotConfiguration actual constructor() { actual open class BotConfiguration actual constructor() {
/** /**
...@@ -76,7 +59,7 @@ 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 { actual companion object {
/** /**
...@@ -115,4 +98,31 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: ...@@ -115,4 +98,31 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath:
*/ */
@BotConfigurationDsl @BotConfigurationDsl
companion object ByDeviceDotJson 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")
}
}
}
} }
\ No newline at end of file
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import android.os.Build
private var isAddSuppressedSupported: Boolean = true private var isAddSuppressedSupported: Boolean = true
@MiraiInternalAPI @MiraiInternalAPI
...@@ -9,7 +11,11 @@ actual fun Throwable.addSuppressed(e: Throwable) { ...@@ -9,7 +11,11 @@ actual fun Throwable.addSuppressed(e: Throwable) {
return return
} }
try { try {
this.addSuppressed(e) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
this.addSuppressed(e)
} else {
isAddSuppressedSupported = false
}
} catch (e: Exception) { } catch (e: Exception) {
isAddSuppressedSupported = false isAddSuppressedSupported = false
} }
......
/*
* 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() }
}
\ No newline at end of file
...@@ -20,8 +20,6 @@ import java.nio.channels.DatagramChannel ...@@ -20,8 +20,6 @@ import java.nio.channels.DatagramChannel
import java.nio.channels.ReadableByteChannel import java.nio.channels.ReadableByteChannel
import java.nio.channels.WritableByteChannel import java.nio.channels.WritableByteChannel
actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
/** /**
* 多平台适配的 DatagramChannel. * 多平台适配的 DatagramChannel.
*/ */
......
...@@ -75,6 +75,7 @@ private inline fun InputStream.readInSequence(block: (Int) -> Unit) { ...@@ -75,6 +75,7 @@ private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
} }
} }
@UseExperimental(MiraiInternalAPI::class)
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length) this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0) if (length == 0) return ByteArray(0)
......
...@@ -22,7 +22,6 @@ import net.mamoe.mirai.network.ForceOfflineException ...@@ -22,7 +22,6 @@ import net.mamoe.mirai.network.ForceOfflineException
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.closeAndJoin import net.mamoe.mirai.network.closeAndJoin
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/* /*
...@@ -144,17 +143,16 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -144,17 +143,16 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
suspend fun doInit() { suspend fun doInit() {
repeat(2) { tryNTimesOrException(2) {
try { if (it != 0) {
_network.init() delay(3000)
return logger.warning("Init failed. Retrying in 3s...")
} catch (e: Exception) {
e.logStacktrace()
} }
logger.warning("Init failed. Retrying in 3s...") _network.init()
delay(3000) }?.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") logger.info("Initializing BotNetworkHandler")
......
...@@ -17,7 +17,6 @@ import net.mamoe.mirai.event.EventDisabled ...@@ -17,7 +17,6 @@ import net.mamoe.mirai.event.EventDisabled
import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
...@@ -65,8 +64,8 @@ internal class Handler<in E : Event> ...@@ -65,8 +64,8 @@ internal class Handler<in E : Event>
MiraiLogger.warning( MiraiLogger.warning(
"""Event processing: An exception occurred but no CoroutineExceptionHandler found, """Event processing: An exception occurred but no CoroutineExceptionHandler found,
either in coroutineContext from Handler job, or in subscriberContext""".trimIndent() 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. // this.complete() // do not `completeExceptionally`, otherwise parentJob will fai`l.
// ListeningStatus.STOPPED // ListeningStatus.STOPPED
......
...@@ -34,7 +34,6 @@ import kotlin.coroutines.EmptyCoroutineContext ...@@ -34,7 +34,6 @@ import kotlin.coroutines.EmptyCoroutineContext
* @see CoroutineScope.incoming * @see CoroutineScope.incoming
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl
inline fun <R> CoroutineScope.subscribeMessages( inline fun <R> CoroutineScope.subscribeMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R
...@@ -60,7 +59,6 @@ inline fun <R> CoroutineScope.subscribeMessages( ...@@ -60,7 +59,6 @@ inline fun <R> CoroutineScope.subscribeMessages(
* @see CoroutineScope.incoming * @see CoroutineScope.incoming
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl
inline fun <R> CoroutineScope.subscribeGroupMessages( inline fun <R> CoroutineScope.subscribeGroupMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R
...@@ -81,7 +79,6 @@ inline fun <R> CoroutineScope.subscribeGroupMessages( ...@@ -81,7 +79,6 @@ inline fun <R> CoroutineScope.subscribeGroupMessages(
* @see CoroutineScope.incoming * @see CoroutineScope.incoming
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl
inline fun <R> CoroutineScope.subscribeFriendMessages( inline fun <R> CoroutineScope.subscribeFriendMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R
...@@ -102,7 +99,6 @@ inline fun <R> CoroutineScope.subscribeFriendMessages( ...@@ -102,7 +99,6 @@ inline fun <R> CoroutineScope.subscribeFriendMessages(
* @see CoroutineScope.incoming * @see CoroutineScope.incoming
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl
inline fun <R> Bot.subscribeMessages( inline fun <R> Bot.subscribeMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R
...@@ -125,7 +121,6 @@ inline fun <R> Bot.subscribeMessages( ...@@ -125,7 +121,6 @@ inline fun <R> Bot.subscribeMessages(
* @see CoroutineScope.incoming * @see CoroutineScope.incoming
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl
inline fun <R> Bot.subscribeGroupMessages( inline fun <R> Bot.subscribeGroupMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R
...@@ -146,7 +141,6 @@ inline fun <R> Bot.subscribeGroupMessages( ...@@ -146,7 +141,6 @@ inline fun <R> Bot.subscribeGroupMessages(
* @see CoroutineScope.incoming * @see CoroutineScope.incoming
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl
inline fun <R> Bot.subscribeFriendMessages( inline fun <R> Bot.subscribeFriendMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R
......
...@@ -18,18 +18,17 @@ import kotlin.jvm.JvmStatic ...@@ -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 onSolvePicCaptcha(bot: Bot, data: IoBuffer): String?
abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
}
/** companion object {
* 在各平台实现的默认的验证码处理器. val Default: LoginSolver
*/ }
expect var defaultLoginSolver: LoginSolver }
/** /**
* [Bot] 配置 * [Bot] 配置
......
...@@ -29,6 +29,7 @@ import kotlin.jvm.JvmName ...@@ -29,6 +29,7 @@ import kotlin.jvm.JvmName
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/ */
suspend fun ByteReadChannel.copyTo(dst: OutputStream) { suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
do { do {
val size = this.readAvailable(it) val size = this.readAvailable(it)
...@@ -41,6 +42,7 @@ suspend fun ByteReadChannel.copyTo(dst: OutputStream) { ...@@ -41,6 +42,7 @@ suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/ */
suspend fun ByteReadChannel.copyTo(dst: Output) { suspend fun ByteReadChannel.copyTo(dst: Output) {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
do { do {
val size = this.readAvailable(it) val size = this.readAvailable(it)
...@@ -72,6 +74,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) ...@@ -72,6 +74,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel)
*/ */
suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) {
try { try {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
do { do {
val size = this.readAvailable(it) val size = this.readAvailable(it)
...@@ -88,6 +91,7 @@ suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { ...@@ -88,6 +91,7 @@ suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) {
*/ */
suspend fun ByteReadChannel.copyAndClose(dst: Output) { suspend fun ByteReadChannel.copyAndClose(dst: Output) {
try { try {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
do { do {
val size = this.readAvailable(it) val size = this.readAvailable(it)
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
package net.mamoe.mirai.utils.cryptor package net.mamoe.mirai.utils.cryptor
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
...@@ -20,7 +20,6 @@ import kotlin.experimental.xor ...@@ -20,7 +20,6 @@ import kotlin.experimental.xor
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
import kotlin.random.Random import kotlin.random.Random
/** /**
* 解密错误 * 解密错误
*/ */
...@@ -29,98 +28,52 @@ class DecryptionFailedException : Exception { ...@@ -29,98 +28,52 @@ class DecryptionFailedException : Exception {
constructor(message: String?) : super(message) 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]. * TEA 算法加密解密工具类.
* [key] 将会被读取掉前 16 个字节
* 将会使用 [ByteArrayPool] 来缓存 [key].
* *
* @param key 长度至少为 16 * **注意**: 此为 Mirai 内部 API. 它可能会在任何时刻被改变.
* @throws DecryptionFailedException 解密错误时
*/ */
fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray { @MiraiInternalAPI
checkDataLengthAndReturnSelf(length) object TEA {
return ByteArrayPool.useInstance { keyBuffer -> // TODO: 2020/2/28 使用 stream 式输入以避免缓存
key.readFully(keyBuffer, 0, key.readRemaining)
TEA.decrypt(this, keyBuffer, sourceLength = length) /**
* 在 [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))
}
} }
}
/** @JvmStatic
* 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 解密. fun decrypt(receiver: ByteReadPacket, key: ByteArray, offset: Int = 0, length: Int = (receiver.remaining - offset).toInt()): ByteReadPacket =
* decryptAsByteArray(receiver, key, offset, length) { data -> ByteReadPacket(data) }
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时 inline fun <R> decryptAsByteArray(
*/ receiver: ByteReadPacket,
fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray { key: ByteArray,
return ByteArrayPool.useInstance { offset: Int = 0,
this.readFully(it, offset, length) length: Int = (receiver.remaining - offset).toInt(),
it.checkDataLengthAndReturnSelf(length) consumer: (ByteArray) -> R
TEA.decrypt(it, key, length) ): 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 const val UINT32_MASK = 0xffffffffL
private fun doOption(data: ByteArray, key: ByteArray, length: Int, encrypt: Boolean): ByteArray { private fun doOption(data: ByteArray, key: ByteArray, length: Int, encrypt: Boolean): ByteArray {
...@@ -345,15 +298,25 @@ private object TEA { ...@@ -345,15 +298,25 @@ private object TEA {
private fun fail(): Nothing = throw DecryptionFailedException() private fun fail(): Nothing = throw DecryptionFailedException()
@PublishedApi /**
* 使用 [key] 加密 [source]
*
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
@JvmStatic @JvmStatic
internal fun encrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray = fun encrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray =
doOption(source, key, sourceLength, true) doOption(source, key, length, true)
@PublishedApi /**
* 使用 [key] 解密 [source]
*
* @param key 长度至少为 16
* @throws DecryptionFailedException 解密错误时
*/
@JvmStatic @JvmStatic
internal fun decrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray = fun decrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray =
doOption(source, key, sourceLength, false) doOption(source, key, length, false)
private fun ByteArray.pack(offset: Int, len: Int): Long { private fun ByteArray.pack(offset: Int, len: Int): Long {
var result: Long = 0 var result: Long = 0
......
...@@ -30,5 +30,13 @@ object ByteArrayPool : DefaultPool<ByteArray>(256) { ...@@ -30,5 +30,13 @@ object ByteArrayPool : DefaultPool<ByteArray>(256) {
override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE)
override fun clearInstance(instance: ByteArray): ByteArray = instance 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" }
}
} }
...@@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io ...@@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
import kotlinx.io.errors.IOException
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
...@@ -32,11 +31,6 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl ...@@ -32,11 +31,6 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl
val isOpen: Boolean val isOpen: Boolean
} }
/**
* Channel 被关闭
*/
expect class ClosedChannelException : IOException
/** /**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/ */
......
/*
* 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)
}
\ No newline at end of file
/*
* 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 = this@chunkedFlow.readAvailable(buffer)
emit(chunkedInput)
} while (this@chunkedFlow.isNotEmpty)
}
}
}
/**
* 创建将 [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 = this@chunkedFlow.readAvailable(buffer, 0, buffer.size)
emit(chunkedInput)
} while (!this@chunkedFlow.isClosedForRead)
}
}
}
/**
* 创建将 [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 (!this@chunkedFlow.endOfInput) {
chunkedInput.size = this@chunkedFlow.readAvailable(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 (this@chunkedFlow.available() != 0) {
chunkedInput.size = this@chunkedFlow.read(buffer)
emit(chunkedInput)
}
}
}
}
\ No newline at end of file
...@@ -20,26 +20,12 @@ import kotlinx.io.core.* ...@@ -20,26 +20,12 @@ import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.contentToString
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@Suppress("NOTHING_TO_INLINE") @UseExperimental(MiraiInternalAPI::class)
inline fun Input.discardExact(n: Short) = this.discardExact(n.toInt()) fun ByteReadPacket.copyTo(outputStream: OutputStream) {
@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) {
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
while (this.isNotEmpty) { while (this.isNotEmpty) {
outputStream.write(it, 0, this.readAvailable(it)) outputStream.write(it, 0, this.readAvailable(it))
...@@ -56,21 +42,13 @@ inline fun <R> ByteReadPacket.useBytes( ...@@ -56,21 +42,13 @@ inline fun <R> ByteReadPacket.useBytes(
block(it, n) block(it, n)
} }
@MiraiInternalAPI
inline fun ByteReadPacket.readPacketExact( inline fun ByteReadPacket.readPacketExact(
n: Int = remaining.toInt()//not that safe but adequate n: Int = remaining.toInt()//not that safe but adequate
): ByteReadPacket = this.readBytes(n).toReadPacket() ): 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() private inline fun <R> inline(block: () -> R): R = block()
typealias TlvMap = MutableMap<Int, ByteArray> typealias TlvMap = MutableMap<Int, ByteArray>
inline fun TlvMap.getOrFail(tag: Int): ByteArray { inline fun TlvMap.getOrFail(tag: Int): ByteArray {
...@@ -81,12 +59,13 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr ...@@ -81,12 +59,13 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr
return this[tag] ?: error(lazyMessage(tag)) return this[tag] ?: error(lazyMessage(tag))
} }
@Suppress("FunctionName")
@MiraiInternalAPI @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 @MiraiDebugAPI
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode", "FunctionName")
fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap { fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap {
val map = mutableMapOf<Int, ByteArray>() val map = mutableMapOf<Int, ByteArray>()
var key = 0 var key = 0
...@@ -108,11 +87,14 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica ...@@ -108,11 +87,14 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica
}.toUByte() != UByte.MAX_VALUE) { }.toUByte() != UByte.MAX_VALUE) {
if (map.containsKey(key)) { if (map.containsKey(key)) {
@Suppress("ControlFlowWithEmptyBody")
if (!suppressDuplication) { if (!suppressDuplication) {
DebugLogger.error( /*
@Suppress("DEPRECATION")
MiraiLogger.error(
@Suppress("IMPLICIT_CAST_TO_ANY") @Suppress("IMPLICIT_CAST_TO_ANY")
""" """
Error readTLVMap: Error readTLVMap:
duplicated key ${when (tagSize) { duplicated key ${when (tagSize) {
1 -> key.toByte() 1 -> key.toByte()
2 -> key.toShort() 2 -> key.toShort()
...@@ -122,13 +104,13 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica ...@@ -122,13 +104,13 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica
map=${map.contentToString()} map=${map.contentToString()}
duplicating value=${this.readUShortLVByteArray().toUHexString()} duplicating value=${this.readUShortLVByteArray().toUHexString()}
""".trimIndent() """.trimIndent()
) )*/
} else { } else {
this.discardExact(this.readShort().toInt() and 0xffff) this.discardExact(this.readShort().toInt() and 0xffff)
} }
} else { } else {
try { try {
map[key] = this.readUShortLVByteArray() map[key] = this.readBytes(readUShort().toInt())
} catch (e: Exception) { // BufferUnderflowException, java.io.EOFException } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException
// if (expectingEOF) { // if (expectingEOF) {
// return map // return map
......
...@@ -14,8 +14,9 @@ ...@@ -14,8 +14,9 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.coerceAtMostOrFail 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.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -67,5 +68,6 @@ fun BytePacketBuilder.writeHex(uHex: String) { ...@@ -67,5 +68,6 @@ fun BytePacketBuilder.writeHex(uHex: String) {
/** /**
* 会使用 [ByteArrayPool] 缓存 * 会使用 [ByteArrayPool] 缓存
*/ */
@UseExperimental(MiraiInternalAPI::class)
inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = 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) }
\ No newline at end of file \ No newline at end of file
...@@ -31,40 +31,12 @@ import javax.imageio.ImageIO ...@@ -31,40 +31,12 @@ import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext 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( class DefaultLoginSolver(
val reader: LoginSolverInputReader = DefaultLoginSolverInputReader(), private val input: suspend () -> String,
val overrideLogger:MiraiLogger? = null private val overrideLogger: MiraiLogger? = null
) : LoginSolver() { ) : 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 { 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() } val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
tempFile.createNewFile() tempFile.createNewFile()
...@@ -86,39 +58,38 @@ class DefaultLoginSolver( ...@@ -86,39 +58,38 @@ class DefaultLoginSolver(
} }
} }
logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") 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]中...") logger.info("正在提交[$it]中...")
} }
} }
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? = loginSolverLock.withLock { 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("请在任意浏览器中打开以下链接并完成验证码. ")
logger.info("完成后请输入任意字符 ") logger.info("完成后请输入任意字符 ")
logger.info(url) logger.info(url)
return reader("完成后请输入任意字符").also { return input().also {
logger.info("正在提交中...") logger.info("正在提交中...")
} }
} }
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock { 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("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题") logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次") logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符") logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
logger.info("这步操作将在后续的版本中优化") logger.info("这步操作将在后续的版本中优化")
logger.info(url) logger.info(url)
return reader("完成后请输入任意字符").also { return input().also {
logger.info("正在提交中...") logger.info("正在提交中...")
} }
} }
} }
// Copied from Ktor CIO // Copied from Ktor CIO
public fun File.writeChannel( private fun File.writeChannel(
coroutineContext: CoroutineContext = Dispatchers.IO coroutineContext: CoroutineContext = Dispatchers.IO
): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) { ): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
...@@ -134,7 +105,7 @@ private val loginSolverLock = Mutex() ...@@ -134,7 +105,7 @@ private val loginSolverLock = Mutex()
/** /**
* @author NaturalHG * @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 newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH) val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB) val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
...@@ -229,7 +200,7 @@ actual open class BotConfiguration actual constructor() { ...@@ -229,7 +200,7 @@ actual open class BotConfiguration actual constructor() {
/** /**
* 验证码处理器 * 验证码处理器
*/ */
actual var loginSolver: LoginSolver = defaultLoginSolver actual var loginSolver: LoginSolver = LoginSolver.Default
actual companion object { actual companion object {
/** /**
...@@ -279,4 +250,18 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: ...@@ -279,4 +250,18 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath:
*/ */
@BotConfigurationDsl @BotConfigurationDsl
companion object ByDeviceDotJson 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") })
}
} }
\ No newline at end of file
...@@ -56,6 +56,7 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress ...@@ -56,6 +56,7 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual val Http: HttpClient get() = HttpClient(CIO) actual val Http: HttpClient get() = HttpClient(CIO)
@UseExperimental(MiraiInternalAPI::class)
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length) this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0) if (length == 0) return ByteArray(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() }
}
\ No newline at end of file
...@@ -21,8 +21,6 @@ import java.nio.channels.ReadableByteChannel ...@@ -21,8 +21,6 @@ import java.nio.channels.ReadableByteChannel
import java.nio.channels.WritableByteChannel import java.nio.channels.WritableByteChannel
actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
/** /**
* 多平台适配的 DatagramChannel. * 多平台适配的 DatagramChannel.
*/ */
......
...@@ -9,16 +9,20 @@ ...@@ -9,16 +9,20 @@
package mirai.test.testCaptchaPacket 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.hexToBytes
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
@MiraiInternalAPI
fun main() { fun main() {
val key = "65 F7 F3 14 E3 94 10 1F DD 95 84 A3 F5 9F AD 94".hexToBytes() val key = "65 F7 F3 14 E3 94 10 1F DD 95 84 A3 F5 9F AD 94".hexToBytes()
val data = 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() decrypt(
.decryptBy(key) "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()) 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 //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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment