This commit is contained in:
Him188 2020-01-07 19:26:49 +08:00
parent b6c1553615
commit 78ee01ded1
12 changed files with 281 additions and 118 deletions

View File

@ -14,7 +14,7 @@ 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.PlatformSocket
import net.mamoe.mirai.utils.io.ReadPacketInternalException
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.unsafeWeakRef
@ -25,16 +25,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
private val channel: PlatformDatagramChannel = PlatformDatagramChannel("wtlogin.qq.com", 8000)
private lateinit var channel: PlatformSocket
override suspend fun login() {
channel = PlatformSocket()
channel.connect("113.96.13.208", 8080)
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
println("Sending login")
LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()
println("Login sent")
}
private suspend inline fun processReceive() {
private suspend fun processReceive() {
while (channel.isOpen) {
val rawInput = try {
channel.read()
@ -52,21 +54,19 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
launch(CoroutineName("Incoming Packet handler")) {
try {
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
rawInput.debugPrint("Received").use {
if (it.remaining == 0L) {
bot.logger.error("Empty packet received. Consider if bad packet was sent.")
return@launch
}
packetListeners.forEach { listener ->
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet)
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
if (PacketReceivedEvent(packet).broadcast().cancelled) {
return@parseIncomingPacket
}
packetListeners.forEach { listener ->
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet)
}
}
}
}
@ -77,7 +77,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
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" }
//println(delegate.readBytes().toUHexString())
println("Sending length=" + delegate.remaining)
channel.send(delegate)//) { packetListeners.remove(handler); "Cannot send packet" }
println("Packet sent")
@Suppress("UNCHECKED_CAST")
return handler.await() as E
}

View File

@ -4,7 +4,10 @@ import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.qqandroid.utils.Context
import net.mamoe.mirai.qqandroid.utils.DeviceInfo
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.io.hexToBytes
@ -31,7 +34,7 @@ internal open class QQAndroidClient(
val ecdh: ECDH = ECDH(),
val device: DeviceInfo = SystemDeviceInfo(context)
) {
val tgtgtKey: ByteArray = generateTgtgtKey(device.guid)
val tgtgtKey: ByteArray = ByteArray(16) // generateTgtgtKey(device.guid)
var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
var mainSigMap: Int = 16724722

View File

@ -11,7 +11,6 @@ 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.*
/**
@ -47,6 +46,7 @@ private val EMPTY_BYTE_ARRAY = ByteArray(0)
*
* byte[] body encrypted by 16 zero
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
client: QQAndroidClient,
subAppId: Long,
@ -54,30 +54,33 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
name: String? = null,
id: PacketId = this.id,
ssoExtraData: ByteReadPacket = BRP_STUB,
sequenceId: Int = PacketFactory.atomicNextSequenceId(),
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0A)
writeByte(0x02)
extraData.let {
writeInt(it.size + 4)
writeFully(it)
}
writeByte(0x00)
): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId()
client.account.id.toString().let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
return OutgoingPacket(name, id, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0A)
writeByte(0x02)
extraData.let {
writeInt(it.size + 4)
writeFully(it)
}
writeByte(0x00)
encryptAndWrite(KEY_16_ZEROS) {
writeLoginSsoPacket(client, subAppId, id, ssoExtraData, sequenceId) {
body(sequenceId)
client.account.id.toString().let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
encryptAndWrite(KEY_16_ZEROS) {
writeLoginSsoPacket(client, subAppId, id, ssoExtraData, sequenceId) {
body(sequenceId)
}
}
}
}
})
})
}
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
@ -141,7 +144,7 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket(
writeInt(4)
client.device.ksid.let {
writeInt(it.length + 4)
writeShort((it.length + 2).toShort())
writeStringUtf8(it)
}
@ -190,7 +193,7 @@ internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket(
interface EncryptMethod {
val id: Int
fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket)
fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket
}
internal interface EncryptMethodSessionKey : EncryptMethod {
@ -208,14 +211,14 @@ internal interface EncryptMethodSessionKey : EncryptMethod {
* fully encrypted
* }
*/
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) {
override fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" }
writeByte(1) // const
writeByte(if (currentLoginState == 2) 3 else 2)
writeFully(sessionKey)
writeShort(258) // const
writeShort(0) // const, length of publicKey
body.encryptBy(sessionKey) { encrypted -> writeFully(encrypted) }
encryptAndWrite(sessionKey, body)
}
}
@ -248,13 +251,22 @@ internal interface EncryptMethodECDH : EncryptMethod {
* byte[] [ECDH.publicKey]
* byte[] encrypted `body()` by [ECDH.shareKey]
*/
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) = ecdh.run {
override fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
writeByte(1) // const
writeByte(1) // const
writeFully(keyPair.privateKey.getEncoded())
writeFully(ByteArray(16))
writeShort(258) // const
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) }
writeShortLVByteArray("04 CB 36 66 98 56 1E 93 6E 80 C1 57 E0 74 CA B1 3B 0B B6 8D DE B2 82 45 48 A1 B1 8D D4 FB 61 22 AF E1 2F E4 8C 52 66 D8 D7 26 9D 76 51 A8 EB 6F E7".hexToBytes())
/*writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also {
it.toUHexString().debugPrint("PUBLIC KEY")
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" }
//check(ecdh.calculateShareKeyByPeerPublicKey(it.adjustToPublicKey()).contentEquals(ecdh.keyPair.shareKey)) { "PublicKey Validation failed" }
})*/
encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body)
//encryptAndWrite(ecdh.keyPair.shareKey, body)
}
}
@ -278,36 +290,34 @@ internal interface EncryptMethodECDH : EncryptMethod {
* byte 3 // tail
*/
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeOicqRequestPacket(
internal fun BytePacketBuilder.writeOicqRequestPacket(
client: QQAndroidClient,
encryptMethod: EncryptMethod,
packetId: PacketId,
bodyBlock: BytePacketBuilder.() -> Unit
) {
val body = encryptMethod.run {
buildPacket { encryptAndWrite(buildPacket { bodyBlock() }) }
}
writeIntLVPacket(lengthOffset = { it + 4 }) {
// Head
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
writeShort(1) // const??
writeShort(packetId.commandId.toShort())
writeQQ(client.account.id)
writeByte(3) // originally const
writeByte(encryptMethod.id.toByte())
writeByte(0) // const8_always_0
writeInt(2) // originally const
writeInt(client.appClientVersion)
writeInt(0) // constp_always_0
val body = encryptMethod.makeBody(bodyBlock)
// writeIntLVPacket(lengthOffset = { it + 4 }) {
// Head
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
writeShort(packetId.commandId.toShort())
writeShort(1) // const??
writeQQ(client.account.id)
writeByte(3) // originally const
writeByte(encryptMethod.id.toByte())
writeByte(0) // const8_always_0
writeInt(2) // originally const
writeInt(client.appClientVersion)
writeInt(0) // constp_always_0
// Body
writePacket(body)
// Body
writePacket(body)
// Tail
writeByte(0x03) // tail
}
// Tail
writeByte(0x03) // tail
// }
}
/*
00 00 01 64

View File

@ -1,9 +1,7 @@
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.IoBuffer
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.data.Packet
@ -41,25 +39,11 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
* **解码**服务器的回复数据包
*/
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket
companion object {
private val sequenceId: AtomicInt = atomic(1)
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()
}
}
}
private val DECRYPTER_16_ZERO = ByteArray(16)
internal typealias PacketConsumer = (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit
internal typealias PacketConsumer = suspend (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit
internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListOf() {
@ -67,20 +51,25 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
fun findPacketFactory(commandId: Int): PacketFactory<*, *> = this.first { it.id.commandName == commandName }
suspend inline fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
// do not inline. Exceptions thrown will not be reported correctly
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
rawInput.debugPrintIfFail("Incoming packet") {
require(rawInput.remaining < Int.MAX_VALUE) { "rawInput is too long" }
require(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? " }
check(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 extraData = readIoBuffer(readInt() - 4).debugCopyUse {
this.debugPrint("Extra data")
}
val flag3 = readByte().toInt()
check(flag3 == 0) { "Illegal flag3. Expected 0, got $flag3" }
discardExact(readInt() - 4) // uinAccount
bot.logger.verbose(readString(readInt() - 4)) // uinAccount
//debugPrint("remaining")
parseLoginSsoPacket(bot, decryptBy(DECRYPTER_16_ZERO), consumer)
}
else -> error("Illegal flag2. Expected 0x02, got $flag2")
@ -90,7 +79,7 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private suspend inline fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
private suspend fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
rawInput.debugPrintIfFail("Login sso packet") {
val commandName: String
val ssoSequenceId: Int
@ -136,13 +125,13 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
}
@UseExperimental(ExperimentalContracts::class)
internal inline fun <I : Closeable, R> I.withUse(block: I.() -> R): R {
internal inline fun <I : IoBuffer, R> I.withUse(block: I.() -> R): R {
contract {
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
return try {
block(this)
} finally {
close()
this.release(IoBuffer.Pool)
}
}

View File

@ -37,7 +37,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: String) {
writeInt(Random.nextInt())
writeInt(uin.toInt())
writeTime()
writeIP(ip)
writeFully(ByteArray(4))
writeShort(0)
} shouldEqualsTo 20
}
@ -273,8 +273,8 @@ fun BytePacketBuilder.t109(
) {
writeShort(0x109)
writeShortLVPacket {
writeFully(androidId)
}
writeFully(md5(androidId))
} shouldEqualsTo 16
}
fun BytePacketBuilder.t52d(
@ -283,6 +283,9 @@ fun BytePacketBuilder.t52d(
writeShort(0x52d)
writeShortLVPacket {
writeFully(androidDevInfo)
// 0A 07 75 6E 6B 6E 6F 77 6E 12 7E 4C 69 6E 75 78 20 76 65 72 73 69 6F 6E 20 34 2E 39 2E 33 31 20 28 62 75 69 6C 64 40 42 75 69 6C 64 32 29 20 28 67 63 63 20 76 65 72 73 69 6F 6E 20 34 2E 39 20 32 30 31 35 30 31 32 33 20 28 70 72 65 72 65 6C 65 61 73 65 29 20 28 47 43 43 29 20 29 20 23 31 20 53 4D 50 20 50 52 45 45 4D 50 54 20 54 68 75 20 44 65 63 20 31 32 20 31 35 3A 33 30 3A 35 35 20 49 53 54 20 32 30 31 39 1A 03 52 45 4C 22 03 33 32 37 2A 41 4F 6E 65 50 6C 75 73 2F 4F 6E 65 50 6C 75 73 35 2F 4F 6E 65 50 6C 75 73 35 3A 37 2E 31 2E 31 2F 4E 4D 46 32 36 58 2F 31 30 31 37 31 36 31 37 3A 75 73 65 72 2F 72 65 6C 65 61 73 65 2D 6B 65 79 73 32 24 36 63 39 39 37 36 33 66 2D 66 62 34 32 2D 34 38 38 31 2D 62 37 32 65 2D 63 37 61 61 38 61 36 63 31 63 61 34 3A 10 65 38 63 37 30 35 34 64 30 32 66 33 36 33 64 30 42 0A 6E 6F 20 6D 65 73 73 61 67 65 4A 03 33 32 37
}
}
@ -301,7 +304,6 @@ fun BytePacketBuilder.t124(
writeShort(networkType.value.toShort())
writeShortLVByteArrayLimitedLength(simInfo, 16)
writeShortLVByteArrayLimitedLength(unknown, 32)
writeShort(0)
writeShortLVByteArrayLimitedLength(apn, 16)
}
}
@ -385,7 +387,7 @@ fun BytePacketBuilder.t147(
) {
writeShort(0x147)
writeShortLVPacket {
writeLong(appId)
writeInt(appId.toInt())
writeShortLVByteArrayLimitedLength(apkVersionName, 32)
writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
}
@ -516,16 +518,18 @@ fun BytePacketBuilder.t188(
writeShort(0x188)
writeShortLVPacket {
writeFully(md5(androidId))
}
} shouldEqualsTo 16
}
fun BytePacketBuilder.t194(
imsiMd5: ByteArray
) {
imsiMd5 requireSize 16
writeShort(0x194)
writeShortLVPacket {
writeFully(imsiMd5)
}
} shouldEqualsTo 16
}
fun BytePacketBuilder.t191(
@ -572,7 +576,7 @@ fun BytePacketBuilder.t177(
writeByte(1)
writeInt(unknown1.toInt())
writeShortLVString(unknown2)
}
} shouldEqualsTo 0x11
}
fun BytePacketBuilder.t516( // 1302
@ -581,7 +585,7 @@ fun BytePacketBuilder.t516( // 1302
writeShort(0x516)
writeShortLVPacket {
writeInt(sourceType)
}
} shouldEqualsTo 4
}
fun BytePacketBuilder.t521( // 1313
@ -592,7 +596,7 @@ fun BytePacketBuilder.t521( // 1313
writeShortLVPacket {
writeInt(productType)
writeShort(unknown)
}
} shouldEqualsTo 6
}
fun BytePacketBuilder.t536( // 1334
@ -626,8 +630,8 @@ fun BytePacketBuilder.t318(
private fun Boolean.toByte(): Byte = if (this) 1 else 0
private fun Boolean.toInt(): Int = if (this) 1 else 0
private infix fun Int.shouldEqualsTo(int: Int) = require(this == int) { "Required $int, but found $this" }
private fun ByteArray.requireSize(exactSize: Int) = require(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
private infix fun Int.shouldEqualsTo(int: Int) = check(this == int) { "Required $int, but found $this" }
private infix fun ByteArray.requireSize(exactSize: Int) = check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
fun randomAndroidId(): String = buildString(15) {
repeat(15) { append(Random.nextInt(10)) }

View File

@ -31,9 +31,10 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, subAppId) { sequenceId ->
writeOicqRequestPacket(client, EncryptMethodECDH135(client.ecdh), id) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), id) {
writeShort(9) // subCommand
writeShort(LoginType.PASSWORD.value.toShort())
writeShort(0x17)
//writeShort(LoginType.PASSWORD.value.toShort())
t18(appId, client.appClientVersion, client.account.id)
t1(client.account.id, client.device.ipAddress)
@ -91,6 +92,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
tgtgtKey = client.tgtgtKey
)
//this.build().debugPrint("傻逼")
t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5)
@ -156,6 +158,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
writeByte(0) // data count
}.readBytes())
})
// this.build().debugPrint("傻逼")
// ignored t318 because not logging in by QR
}

View File

@ -0,0 +1,55 @@
package net.mamoe.mirai.utils.io
import io.ktor.network.sockets.Socket
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.io.readAvailable
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 多平台适配的 TCP Socket.
*/
@MiraiInternalAPI
actual class PlatformSocket : Closeable {
@UseExperimental(KtorExperimentalAPI::class)
lateinit var channel: Socket
@UseExperimental(KtorExperimentalAPI::class)
actual val isOpen: Boolean
get() = channel.socketContext.isActive
override fun close() = channel.dispose()
@PublishedApi
internal val writeChannel = channel.openWriteChannel(true)
@PublishedApi
internal val readChannel = channel.openReadChannel()
/**
* @throws SendPacketInternalException
*/
actual suspend inline fun send(packet: ByteReadPacket) {
writeChannel.writePacket(packet)
}
/**
* @throws ReadPacketInternalException
*/
actual suspend inline fun read(): ByteReadPacket {
// do not use readChannel.readRemaining() !!! this function never returns
ByteArrayPool.useInstance { buffer ->
val count = readChannel.readAvailable(buffer)
return buffer.toReadPacket(0, count)
}
}
@UseExperimental(KtorExperimentalAPI::class)
actual suspend fun connect(serverHost: String, serverPort: Int) {
channel = aSocket(io.ktor.network.selector.ActorSelectorManager(kotlinx.coroutines.Dispatchers.IO)).tcp().connect(serverHost, serverPort)
}
}

View File

@ -94,17 +94,17 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain
// region ByteReadPacket extension
fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = key.size - offset): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
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 = key.readRemaining - offset): 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 = key.size - offset, consumer: (ByteArray) -> R): R =
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 = key.readRemaining - offset, consumer: (ByteArray) -> R): R =
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))

View File

@ -0,0 +1,26 @@
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
/**
* 多平台适配的 TCP Socket.
*/
@MiraiInternalAPI
expect class PlatformSocket() : Closeable {
suspend fun connect(serverHost: String, serverPort: Int)
/**
* @throws SendPacketInternalException
*/
suspend inline fun send(packet: ByteReadPacket)
/**
* @throws ReadPacketInternalException
*/
suspend inline fun read(): ByteReadPacket
val isOpen: Boolean
}

View File

@ -1,7 +1,9 @@
package net.mamoe.mirai.utils.cryptor
import net.mamoe.mirai.utils.md5
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.*
import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.KeyAgreement
@ -15,7 +17,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, publicKey)
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
}
@Suppress("FunctionName")
@ -23,8 +25,12 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object {
init {
Security.addProvider(BouncyCastleProvider())
}
actual fun generateKeyPair(): ECDHKeyPair {
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair())
return ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair())
}
actual fun calculateShareKey(

View File

@ -0,0 +1,59 @@
package net.mamoe.mirai.utils.io
import io.ktor.network.sockets.Socket
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.readAvailable
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 多平台适配的 TCP Socket.
*/
@MiraiInternalAPI
actual class PlatformSocket : Closeable {
@UseExperimental(KtorExperimentalAPI::class)
lateinit var socket: Socket
@UseExperimental(KtorExperimentalAPI::class)
actual val isOpen: Boolean
get() = socket.socketContext.isActive
override fun close() = socket.dispose()
@PublishedApi
internal lateinit var writeChannel: ByteWriteChannel
@PublishedApi
internal lateinit var readChannel: ByteReadChannel
/**
* @throws SendPacketInternalException
*/
actual suspend inline fun send(packet: ByteReadPacket) {
writeChannel.writePacket(packet)
}
/**
* @throws ReadPacketInternalException
*/
actual suspend inline fun read(): ByteReadPacket {
// do not use readChannel.readRemaining() !!! this function never returns
ByteArrayPool.useInstance { buffer ->
val count = readChannel.readAvailable(buffer)
return buffer.toReadPacket(0, count)
}
}
@UseExperimental(KtorExperimentalAPI::class)
actual suspend fun connect(serverHost: String, serverPort: Int) {
socket = aSocket(io.ktor.network.selector.ActorSelectorManager(kotlinx.coroutines.Dispatchers.IO)).tcp().connect(serverHost, serverPort)
writeChannel = socket.openWriteChannel(true)
readChannel = socket.openReadChannel()
}
}

View File

@ -1,7 +1,11 @@
package net.mamoe.mirai.utils.io
package net.mamoe.mirai.utils
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class TypeConversionTest {
@ -13,6 +17,7 @@ class TypeConversionTest {
assertEquals("7F", byteArrayOf(0x7F).toUHexString())
assertEquals("FF", ubyteArrayOf(0xffu).toUHexString())
assertEquals("7F", ubyteArrayOf(0x7fu).toUHexString())
assertTrue { 1994701021.toByteArray().contentEquals("76 E4 B8 DD".hexToBytes()) }
assertEquals(byteArrayOf(0, 0, 0, 0x01).toUHexString(), 1.toByteArray().toUHexString())
assertEquals(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString())
}