Commit 932a3ef1 authored by Him188's avatar Him188

Review: misc improvements

parent 1ff5df1d
......@@ -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
......
......@@ -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()) {
......
......@@ -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)
......
......@@ -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
......
......@@ -8,81 +8,34 @@
*/
@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 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.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
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@MiraiDebugAPI("Unsatble")
val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(false)
val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true)
@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)
@UseExperimental(ExperimentalContracts::class, MiraiInternalAPI::class)
inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R {
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
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
......
/*
* 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
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")
}
}
}
}
\ No newline at end of file
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
}
......
/*
* 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
import java.nio.channels.ReadableByteChannel
import java.nio.channels.WritableByteChannel
actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
/**
* 多平台适配的 DatagramChannel.
*/
......
......@@ -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)
......
......@@ -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")
......
......@@ -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
......
......@@ -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
......
......@@ -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] 配置
......
......@@ -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)
......
......@@ -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
......
......@@ -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" }
}
}
......@@ -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] 时出现的错误.
*/
......
/*
* 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.*
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
......
......@@ -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) }
\ No newline at end of file
TEA.encrypt(BytePacketBuilder().apply(encoder).build(), key) { decrypted -> writeFully(decrypted) }
\ No newline at end of file
......@@ -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") })
}
}
\ No newline at end of file
......@@ -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)
......
/*
* 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
import java.nio.channels.WritableByteChannel
actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
/**
* 多平台适配的 DatagramChannel.
*/
......
......@@ -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
......
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