Pass compilation

This commit is contained in:
Him188 2020-01-06 21:29:57 +08:00
parent e43120742c
commit 48850d7389
11 changed files with 217 additions and 113 deletions

View File

@ -0,0 +1,10 @@
package net.mamoe.mirai.qqandroid.event
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.Cancellable
import net.mamoe.mirai.event.Event
/**
* 接收到数据包
*/
class PacketReceivedEvent(val packet: Packet) : Event(), Cancellable

View File

@ -1,40 +1,41 @@
package net.mamoe.mirai.qqandroid.network
import kotlinx.coroutines.*
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import kotlinx.io.core.use
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ClosedChannelException
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
import net.mamoe.mirai.utils.io.ReadPacketInternalException
import net.mamoe.mirai.utils.io.debugPrint
import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class)
internal class QQAndroidBotNetworkHandler(override val bot: QQAndroidBot) : BotNetworkHandler() {
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
private val channel: PlatformDatagramChannel = PlatformDatagramChannel("wtlogin.qq.com", 8000)
override suspend fun login() {
launch { processReceive() }
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
val buffer = IoBuffer.Pool.borrow()
buffer.writePacket(LoginPacket(bot.client).delegate)
val shouldBeSent = buffer.readRemaining
check(channel.send(buffer) == shouldBeSent) {
"Buffer is not entirely sent. " +
"Required sent length=$shouldBeSent, but after channel.send, " +
"buffer remains ${buffer.readBytes().toUHexString()}"
}
buffer.release(IoBuffer.Pool)
LoginPacket(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()
println("Login sent")
}
private suspend fun processReceive() {
private suspend inline fun processReceive() {
while (channel.isOpen) {
val buffer = IoBuffer.Pool.borrow()
try {
channel.read(buffer)// JVM: withContext(IO)
val rawInput = try {
channel.read()
} catch (e: ClosedChannelException) {
dispose()
return
@ -46,34 +47,49 @@ internal class QQAndroidBotNetworkHandler(override val bot: QQAndroidBot) : BotN
} catch (e: Throwable) {
bot.logger.error("Caught unexpected exceptions", e)
continue
} finally {
if (!buffer.canRead() || buffer.readRemaining == 0) {//size==0
//bot.logger.debug("processReceive: Buffer cannot be read")
buffer.release(IoBuffer.Pool)
continue
}// sometimes exceptions are thrown without this `if` clause
}
//buffer.resetForRead()
launch(CoroutineName("handleServerPacket")) {
// `.use`: Ensure that the packet is consumed **totally**
// so that all the buffers are released
ByteArrayPool.useInstance {
val length = buffer.readRemaining - 1
buffer.readFully(it, 0, length)
buffer.resetForWrite()
buffer.writeFully(it, 0, length)
}
ByteReadPacket(buffer, IoBuffer.Pool).use { input ->
launch(CoroutineName("Incoming Packet handler")) {
try {
input.debugPrint("Received")
rawInput.debugPrint("Received")
} catch (e: Exception) {
bot.logger.error(e)
}
}
rawInput.use {
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
if (PacketReceivedEvent(packet).broadcast().cancelled) {
return
}
packetListeners.forEach { listener ->
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet)
}
}
}
}
}
}
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
val handler = PacketListener(packetId = packetId, sequenceId = sequenceId)
packetListeners.addLast(handler)
check(channel.send(delegate)) { packetListeners.remove(handler); "Cannot send packet" }
@Suppress("UNCHECKED_CAST")
return handler.await() as E
}
@PublishedApi
internal val packetListeners = LockFreeLinkedList<PacketListener>()
@PublishedApi
internal inner class PacketListener(
val packetId: PacketId,
val sequenceId: Int
) : CompletableDeferred<Packet> by CompletableDeferred(supervisor) {
fun filter(packetId: PacketId, sequenceId: Int) = this.packetId == packetId && this.sequenceId == sequenceId
}
override suspend fun awaitDisconnection() {
while (true) {

View File

@ -7,9 +7,9 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
import net.mamoe.mirai.qqandroid.utils.ECDH
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
import net.mamoe.mirai.utils.cryptor.encryptBy
import net.mamoe.mirai.utils.io.*
@ -21,7 +21,12 @@ import net.mamoe.mirai.utils.io.*
internal class OutgoingPacket constructor(
name: String?,
val packetId: PacketId,
val sequenceId: Short,
val sequenceId: Int,
// TODO: 2020/1/6 这个 sequenceId 设计有问题.
// 02 03 包里面的那个应该并不是 sequenceId.
// 它应该是固定的 0x001.
// 应该在这里填入 SSO 的 sequenceId.
// 同时考虑修改名称. 这可能不应该叫做 SSO. 它在全程都有
val delegate: ByteReadPacket
) {
val name: String by lazy {
@ -52,7 +57,7 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
extraData: ByteArray = EMPTY_BYTE_ARRAY,
name: String? = null,
id: PacketId = this.id,
sequenceId: Short = PacketFactory.atomicNextSequenceId(),
sequenceId: Int = PacketFactory.atomicNextSequenceId(),
body: BytePacketBuilder.() -> Unit
): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
@ -69,11 +74,6 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
}
})
internal class CommandId(val stringValue: String, val id: Int) {
override fun toString(): String = stringValue
}
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
/**
@ -85,7 +85,7 @@ private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
internal inline fun BytePacketBuilder.writeLoginSsoPacket(
client: QQAndroidClient,
subAppId: Long,
commandId: CommandId,
packetId: PacketId,
extraData: ByteReadPacket = BRP_STUB,
body: BytePacketBuilder.(ssoSequenceId: Int) -> Unit
) {
@ -102,21 +102,25 @@ internal inline fun BytePacketBuilder.writeLoginSsoPacket(
writeInt((extraData.remaining + 4).toInt())
writePacket(extraData)
}
writeInt(commandId.stringValue.length + 4)
writeStringUtf8(commandId.stringValue)
packetId.commandName.let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
writeInt(4 + 4)
writeInt(45112203) // 02 B0 5B 8B
val imei = client.device.imei
writeInt(imei.length + 4)
writeStringUtf8(imei)
client.device.imei.let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
writeInt(4)
val ksid = client.device.ksid
writeShort((ksid.length + 2).toShort())
writeStringUtf8(ksid)
client.device.ksid.let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
writeInt(4)
}
@ -226,10 +230,10 @@ internal interface EncryptMethodECDH : EncryptMethod {
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) = ecdh.run {
writeByte(1) // const
writeByte(1) // const
writeFully(privateKey)
writeFully(keyPair.privateKey.getEncoded())
writeShort(258) // const
writeShortLVByteArray(publicKey)
body.encryptBy(shareKey) { encrypted -> writeFully(encrypted) }
writeShortLVByteArray(keyPair.publicKey.getEncoded().drop(23).toByteArray().also { check(it.size == 49) { "Bad publicKey generated. Expected size=49, got${it.size}" } })
body.encryptBy(keyPair.shareKey) { encrypted -> writeFully(encrypted) }
}
}
@ -256,8 +260,7 @@ internal interface EncryptMethodECDH : EncryptMethod {
internal inline fun BytePacketBuilder.writeRequestPacket(
client: QQAndroidClient,
encryptMethod: EncryptMethod,
commandId: CommandId,
sequenceId: Short = PacketFactory.atomicNextSequenceId(),
packetId: PacketId,
bodyBlock: BytePacketBuilder.() -> Unit
) {
val body = encryptMethod.run {
@ -268,8 +271,8 @@ internal inline fun BytePacketBuilder.writeRequestPacket(
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
writeShort(sequenceId)
writeShort(commandId.id.toShort())
writeShort(1)
writeShort(packetId.commandId.toShort())
writeQQ(client.account.id)
writeByte(3) // originally const
writeByte(encryptMethod.id.toByte())

View File

@ -3,13 +3,21 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.NullPacketId
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.NullPacketId.commandName
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.cryptor.Decrypter
import net.mamoe.mirai.utils.cryptor.DecrypterType
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
/**
* 一种数据包的处理工厂. 它可以解密解码服务器发来的这个包, 也可以编码加密要发送给服务器的这个包
@ -32,21 +40,109 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
/**
* **解码**服务器的回复数据包
*/
abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: Short, handler: BotNetworkHandler): TPacket
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket
companion object {
private val sequenceId: AtomicInt = atomic(1)
fun atomicNextSequenceId(): Short {
fun atomicNextSequenceId(): Int {
TODO("使用 SSO ")
val id = sequenceId.getAndAdd(1)
if (id > Short.MAX_VALUE.toInt() * 2) {
sequenceId.value = 0
return atomicNextSequenceId()
}
return id.toShort()
// return id.toShort()
}
}
}
internal class KnownPacketFactories : LockFreeLinkedList<PacketFactory<*, *>>() {
private val DECRYPTER_16_ZERO = ByteArray(16)
internal typealias PacketConsumer = (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit
internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListOf() {
fun findPacketFactory(commandName: String): PacketFactory<*, *> = this.first { it.id.commandName == commandName }
fun findPacketFactory(commandId: Int): PacketFactory<*, *> = this.first { it.id.commandName == commandName }
suspend inline fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
rawInput.debugPrintIfFail("Incoming packet") {
require(rawInput.remaining < Int.MAX_VALUE) { "rawInput is too long" }
val expectedLength = readInt() - 4
check(rawInput.remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " }
// login
when (val flag1 = readInt()) {
0x0A -> when (val flag2 = readByte().toInt()) {
0x02 -> {
val flag3 = readByte().toInt()
check(flag3 == 0) { "Illegal flag3. Expected 0, got $flag3" }
discardExact(readInt() - 4) // uinAccount
parseLoginSsoPacket(bot, decryptBy(DECRYPTER_16_ZERO), consumer)
}
else -> error("Illegal flag2. Expected 0x02, got $flag2")
}
else -> error("Illegal flag1. Expected 0x0A or 0x0B, got $flag1")
}
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private suspend inline fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
rawInput.debugPrintIfFail("Login sso packet") {
val commandName: String
val ssoSequenceId: Int
readIoBuffer(readInt() - 4).withUse {
ssoSequenceId = readInt()
check(readInt() == 0)
val loginExtraData = readIoBuffer(readInt() - 4)
commandName = readString(readInt() - 4)
val unknown = readBytes(readInt() - 4)
if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: $unknown")
check(readInt() == 0)
}
val packetFactory = findPacketFactory(commandName)
val qq: Long
val subCommandId: Int
readIoBuffer(readInt() - 4).withUse {
check(readByte().toInt() == 2)
discardExact(2) // 27 + 2 + body.size
discardExact(2) // const, =8001
readShort() // commandId
readShort() // innerSequenceId
qq = readInt().toLong()
discardExact(1) // const = 0
val packet = when (val encryptionMethod = readByte().toInt()) {
4 -> { // peer public key, ECDH
packetFactory.run {
bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey()).read {
decode(bot)
}
}
}
else -> error("Illegal encryption method. expected 4, got $encryptionMethod")
}
consumer(packet, packetFactory.id, ssoSequenceId)
}
}
}
@UseExperimental(ExperimentalContracts::class)
internal inline fun <I : Closeable, R> I.withUse(block: I.() -> R): R {
contract {
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
return try {
block(this)
} finally {
close()
}
}

View File

@ -5,7 +5,7 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.utils.GuidSource
@ -21,7 +21,7 @@ class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray {
internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, LoginPacketDecrypter>(LoginPacketDecrypter) {
init {
this._id = PacketId(CommandId("wtlogin.login", 0x0810), 9)
this._id = PacketId(commandId = 0x0810, commandName = "wtlogin.login", subCommandId = 9)
}
operator fun invoke(
@ -30,8 +30,8 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
val appId = 16L
val subAppId = 537062845L
writeLoginSsoPacket(client, subAppId, _id.commandId) { ssoSequenceId ->
writeRequestPacket(client, EncryptMethodECDH135(client.ecdh), _id.commandId) {
writeLoginSsoPacket(client, subAppId, id) { ssoSequenceId ->
writeRequestPacket(client, EncryptMethodECDH135(client.ecdh), id) {
writeShort(9) // subCommand
writeShort(LoginType.PASSWORD.value.toShort())
@ -163,30 +163,27 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
class LoginPacketResponse : Packet
@ExperimentalUnsignedTypes
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: Short, handler: BotNetworkHandler): LoginPacketResponse {
TODO()
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse {
TODO("not implemented")
}
}
@Suppress("FunctionName")
internal fun PacketId(commandId: CommandId, subCommandId: Int) = object : PacketId {
override val commandId: CommandId
get() = commandId
override val subCommandId: Int
get() = subCommandId
internal fun PacketId(commandId: Int, commandName: String, subCommandId: Int) = object : PacketId {
override val commandId: Int get() = commandId
override val commandName: String get() = commandName
override val subCommandId: Int get() = subCommandId
}
internal interface PacketId {
val commandId: CommandId // ushort actually
val commandId: Int // ushort actually
val commandName: String
val subCommandId: Int // ushort actually
}
internal object NullPacketId : PacketId {
override val commandId: CommandId
get() = error("uninitialized")
override val subCommandId: Int
get() = error("uninitialized")
override val commandId: Int get() = error("uninitialized")
override val commandName: String get() = error("uninitialized")
override val subCommandId: Int get() = error("uninitialized")
}

View File

@ -1,22 +0,0 @@
package net.mamoe.mirai.qqandroid.utils
/**
* From QQAndroid 8.2.0
* `oicq.wlogin_sdk.tools.EcdhCrypt`
*
* Constant to avoid calculations
*/
interface ECDH {
/*
object Default : ECDH {
override val publicKey: ByteArray = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128".chunkedHexToBytes()
override val shareKey: ByteArray = "4da0f614fc9f29c2054c77048a6566d7".chunkedHexToBytes()
override val privateKey: ByteArray = ByteArray(16)
}*/
val publicKey: ByteArray
val shareKey: ByteArray
val privateKey: ByteArray
}

View File

@ -4,6 +4,7 @@ package net.mamoe.mirai.timpc.network.handler
import kotlinx.io.core.Closeable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/**
@ -25,6 +26,7 @@ interface DataPacketSocketAdapter : Closeable {
/**
* UDP 通道
*/
@MiraiInternalAPI
val channel: PlatformDatagramChannel
/**

View File

@ -26,7 +26,7 @@ internal inline class PrivateKey(override val value: ByteArray) : DecrypterByteA
}
internal inline class SubmitPasswordResponseDecrypter(private val privateKey: PrivateKey) : Decrypter {
override fun decrypt(input: ByteReadPacket): ByteReadPacket {
override fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket {
var decrypted = ShareKey.decrypt(input)
(decrypted.remaining).let {
if (it.toInt() % 8 == 0 && it >= 16) {
@ -263,7 +263,7 @@ internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginR
}
internal inline class SessionResponseDecryptionKey(private val delegate: IoBuffer) : Decrypter {
override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(delegate)
override fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket = input.decryptBy(delegate)
override fun toString(): String = "SessionResponseDecryptionKey"

View File

@ -21,7 +21,7 @@ actual class ECDHKeyPair(
@Suppress("FunctionName")
actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(val keyPair: ECDHKeyPair) {
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object {
actual fun generateKeyPair(): ECDHKeyPair {
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair())

View File

@ -19,6 +19,8 @@ expect class ECDHKeyPair {
}
expect class ECDH(keyPair: ECDHKeyPair) {
val keyPair: ECDHKeyPair
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray
companion object {
@ -47,5 +49,5 @@ fun ByteArray.adjustToPublicKey(): ECDHPublicKey {
else (byteArray_04 + this)
} else this
return ECDH.constructPublicKey(this)
return ECDH.constructPublicKey(key)
}

View File

@ -21,7 +21,7 @@ actual class ECDHKeyPair(
@Suppress("FunctionName")
actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(val keyPair: ECDHKeyPair) {
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object {
actual fun generateKeyPair(): ECDHKeyPair {
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair())