mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-03 05:32:30 +08:00
Fix bugs
This commit is contained in:
parent
b6c1553615
commit
78ee01ded1
@ -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.LockFreeLinkedList
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.io.ClosedChannelException
|
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.ReadPacketInternalException
|
||||||
import net.mamoe.mirai.utils.io.debugPrint
|
import net.mamoe.mirai.utils.io.debugPrint
|
||||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
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 bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||||
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
|
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() {
|
override suspend fun login() {
|
||||||
|
channel = PlatformSocket()
|
||||||
|
channel.connect("113.96.13.208", 8080)
|
||||||
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
|
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
|
||||||
|
|
||||||
|
println("Sending login")
|
||||||
LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()
|
LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()
|
||||||
println("Login sent")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend inline fun processReceive() {
|
private suspend fun processReceive() {
|
||||||
while (channel.isOpen) {
|
while (channel.isOpen) {
|
||||||
val rawInput = try {
|
val rawInput = try {
|
||||||
channel.read()
|
channel.read()
|
||||||
@ -52,17 +54,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch(CoroutineName("Incoming Packet handler")) {
|
launch(CoroutineName("Incoming Packet handler")) {
|
||||||
try {
|
rawInput.debugPrint("Received").use {
|
||||||
rawInput.debugPrint("Received")
|
if (it.remaining == 0L) {
|
||||||
} catch (e: Exception) {
|
bot.logger.error("Empty packet received. Consider if bad packet was sent.")
|
||||||
bot.logger.error(e)
|
return@launch
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rawInput.use {
|
|
||||||
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
|
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
|
||||||
if (PacketReceivedEvent(packet).broadcast().cancelled) {
|
if (PacketReceivedEvent(packet).broadcast().cancelled) {
|
||||||
return
|
return@parseIncomingPacket
|
||||||
}
|
}
|
||||||
packetListeners.forEach { listener ->
|
packetListeners.forEach { listener ->
|
||||||
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
|
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
|
||||||
@ -73,11 +72,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
||||||
val handler = PacketListener(packetId = packetId, sequenceId = sequenceId)
|
val handler = PacketListener(packetId = packetId, sequenceId = sequenceId)
|
||||||
packetListeners.addLast(handler)
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return handler.await() as E
|
return handler.await() as E
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,10 @@ import kotlinx.atomicfu.AtomicInt
|
|||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.io.core.toByteArray
|
import kotlinx.io.core.toByteArray
|
||||||
import net.mamoe.mirai.BotAccount
|
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.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.cryptor.ECDH
|
import net.mamoe.mirai.utils.cryptor.ECDH
|
||||||
import net.mamoe.mirai.utils.io.hexToBytes
|
import net.mamoe.mirai.utils.io.hexToBytes
|
||||||
@ -31,7 +34,7 @@ internal open class QQAndroidClient(
|
|||||||
val ecdh: ECDH = ECDH(),
|
val ecdh: ECDH = ECDH(),
|
||||||
val device: DeviceInfo = SystemDeviceInfo(context)
|
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 miscBitMap: Int = 184024956 // 也可能是 150470524 ?
|
||||||
var mainSigMap: Int = 16724722
|
var mainSigMap: Int = 16724722
|
||||||
|
@ -11,7 +11,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
|||||||
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
|
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
|
||||||
import net.mamoe.mirai.utils.cryptor.ECDH
|
import net.mamoe.mirai.utils.cryptor.ECDH
|
||||||
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
|
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
|
||||||
import net.mamoe.mirai.utils.cryptor.encryptBy
|
|
||||||
import net.mamoe.mirai.utils.io.*
|
import net.mamoe.mirai.utils.io.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,6 +46,7 @@ private val EMPTY_BYTE_ARRAY = ByteArray(0)
|
|||||||
*
|
*
|
||||||
* byte[] body encrypted by 16 zero
|
* byte[] body encrypted by 16 zero
|
||||||
*/
|
*/
|
||||||
|
@UseExperimental(MiraiInternalAPI::class)
|
||||||
internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
|
internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
|
||||||
client: QQAndroidClient,
|
client: QQAndroidClient,
|
||||||
subAppId: Long,
|
subAppId: Long,
|
||||||
@ -54,9 +54,11 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
|
|||||||
name: String? = null,
|
name: String? = null,
|
||||||
id: PacketId = this.id,
|
id: PacketId = this.id,
|
||||||
ssoExtraData: ByteReadPacket = BRP_STUB,
|
ssoExtraData: ByteReadPacket = BRP_STUB,
|
||||||
sequenceId: Int = PacketFactory.atomicNextSequenceId(),
|
|
||||||
body: BytePacketBuilder.(sequenceId: Int) -> Unit
|
body: BytePacketBuilder.(sequenceId: Int) -> Unit
|
||||||
): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket {
|
): OutgoingPacket {
|
||||||
|
val sequenceId: Int = client.nextSsoSequenceId()
|
||||||
|
|
||||||
|
return OutgoingPacket(name, id, sequenceId, buildPacket {
|
||||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||||
writeInt(0x00_00_00_0A)
|
writeInt(0x00_00_00_0A)
|
||||||
writeByte(0x02)
|
writeByte(0x02)
|
||||||
@ -77,7 +79,8 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
|
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
|
||||||
|
|
||||||
@ -141,7 +144,7 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket(
|
|||||||
writeInt(4)
|
writeInt(4)
|
||||||
|
|
||||||
client.device.ksid.let {
|
client.device.ksid.let {
|
||||||
writeInt(it.length + 4)
|
writeShort((it.length + 2).toShort())
|
||||||
writeStringUtf8(it)
|
writeStringUtf8(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +193,7 @@ internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket(
|
|||||||
interface EncryptMethod {
|
interface EncryptMethod {
|
||||||
val id: Int
|
val id: Int
|
||||||
|
|
||||||
fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket)
|
fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface EncryptMethodSessionKey : EncryptMethod {
|
internal interface EncryptMethodSessionKey : EncryptMethod {
|
||||||
@ -208,14 +211,14 @@ internal interface EncryptMethodSessionKey : EncryptMethod {
|
|||||||
* fully encrypted
|
* 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" }
|
require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" }
|
||||||
writeByte(1) // const
|
writeByte(1) // const
|
||||||
writeByte(if (currentLoginState == 2) 3 else 2)
|
writeByte(if (currentLoginState == 2) 3 else 2)
|
||||||
writeFully(sessionKey)
|
writeFully(sessionKey)
|
||||||
writeShort(258) // const
|
writeShort(258) // const
|
||||||
writeShort(0) // const, length of publicKey
|
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[] [ECDH.publicKey]
|
||||||
* byte[] encrypted `body()` by [ECDH.shareKey]
|
* 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
|
||||||
writeByte(1) // const
|
writeByte(1) // const
|
||||||
writeFully(keyPair.privateKey.getEncoded())
|
writeFully(ByteArray(16))
|
||||||
writeShort(258) // const
|
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,22 +290,20 @@ internal interface EncryptMethodECDH : EncryptMethod {
|
|||||||
* byte 3 // tail
|
* byte 3 // tail
|
||||||
*/
|
*/
|
||||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||||
internal inline fun BytePacketBuilder.writeOicqRequestPacket(
|
internal fun BytePacketBuilder.writeOicqRequestPacket(
|
||||||
client: QQAndroidClient,
|
client: QQAndroidClient,
|
||||||
encryptMethod: EncryptMethod,
|
encryptMethod: EncryptMethod,
|
||||||
packetId: PacketId,
|
packetId: PacketId,
|
||||||
bodyBlock: BytePacketBuilder.() -> Unit
|
bodyBlock: BytePacketBuilder.() -> Unit
|
||||||
) {
|
) {
|
||||||
val body = encryptMethod.run {
|
val body = encryptMethod.makeBody(bodyBlock)
|
||||||
buildPacket { encryptAndWrite(buildPacket { bodyBlock() }) }
|
// writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||||
}
|
|
||||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
|
||||||
// Head
|
// Head
|
||||||
writeByte(0x02) // head
|
writeByte(0x02) // head
|
||||||
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
|
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
|
||||||
writeShort(client.protocolVersion)
|
writeShort(client.protocolVersion)
|
||||||
writeShort(1) // const??
|
|
||||||
writeShort(packetId.commandId.toShort())
|
writeShort(packetId.commandId.toShort())
|
||||||
|
writeShort(1) // const??
|
||||||
writeQQ(client.account.id)
|
writeQQ(client.account.id)
|
||||||
writeByte(3) // originally const
|
writeByte(3) // originally const
|
||||||
writeByte(encryptMethod.id.toByte())
|
writeByte(encryptMethod.id.toByte())
|
||||||
@ -307,7 +317,7 @@ internal inline fun BytePacketBuilder.writeOicqRequestPacket(
|
|||||||
|
|
||||||
// Tail
|
// Tail
|
||||||
writeByte(0x03) // tail
|
writeByte(0x03) // tail
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
00 00 01 64
|
00 00 01 64
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package net.mamoe.mirai.qqandroid.network.protocol.packet
|
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.ByteReadPacket
|
||||||
import kotlinx.io.core.Closeable
|
import kotlinx.io.core.IoBuffer
|
||||||
import kotlinx.io.core.discardExact
|
import kotlinx.io.core.discardExact
|
||||||
import kotlinx.io.core.readBytes
|
import kotlinx.io.core.readBytes
|
||||||
import net.mamoe.mirai.data.Packet
|
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
|
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)
|
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() {
|
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 }
|
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") {
|
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
|
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
|
// login
|
||||||
when (val flag1 = readInt()) {
|
when (val flag1 = readInt()) {
|
||||||
0x0A -> when (val flag2 = readByte().toInt()) {
|
0x0A -> when (val flag2 = readByte().toInt()) {
|
||||||
0x02 -> {
|
0x02 -> {
|
||||||
|
val extraData = readIoBuffer(readInt() - 4).debugCopyUse {
|
||||||
|
this.debugPrint("Extra data")
|
||||||
|
}
|
||||||
val flag3 = readByte().toInt()
|
val flag3 = readByte().toInt()
|
||||||
check(flag3 == 0) { "Illegal flag3. Expected 0, got $flag3" }
|
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)
|
parseLoginSsoPacket(bot, decryptBy(DECRYPTER_16_ZERO), consumer)
|
||||||
}
|
}
|
||||||
else -> error("Illegal flag2. Expected 0x02, got $flag2")
|
else -> error("Illegal flag2. Expected 0x02, got $flag2")
|
||||||
@ -90,7 +79,7 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
@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") {
|
rawInput.debugPrintIfFail("Login sso packet") {
|
||||||
val commandName: String
|
val commandName: String
|
||||||
val ssoSequenceId: Int
|
val ssoSequenceId: Int
|
||||||
@ -136,13 +125,13 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseExperimental(ExperimentalContracts::class)
|
@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 {
|
contract {
|
||||||
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
|
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
block(this)
|
block(this)
|
||||||
} finally {
|
} finally {
|
||||||
close()
|
this.release(IoBuffer.Pool)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,7 +37,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: String) {
|
|||||||
writeInt(Random.nextInt())
|
writeInt(Random.nextInt())
|
||||||
writeInt(uin.toInt())
|
writeInt(uin.toInt())
|
||||||
writeTime()
|
writeTime()
|
||||||
writeIP(ip)
|
writeFully(ByteArray(4))
|
||||||
writeShort(0)
|
writeShort(0)
|
||||||
} shouldEqualsTo 20
|
} shouldEqualsTo 20
|
||||||
}
|
}
|
||||||
@ -273,8 +273,8 @@ fun BytePacketBuilder.t109(
|
|||||||
) {
|
) {
|
||||||
writeShort(0x109)
|
writeShort(0x109)
|
||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeFully(androidId)
|
writeFully(md5(androidId))
|
||||||
}
|
} shouldEqualsTo 16
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytePacketBuilder.t52d(
|
fun BytePacketBuilder.t52d(
|
||||||
@ -283,6 +283,9 @@ fun BytePacketBuilder.t52d(
|
|||||||
writeShort(0x52d)
|
writeShort(0x52d)
|
||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeFully(androidDevInfo)
|
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())
|
writeShort(networkType.value.toShort())
|
||||||
writeShortLVByteArrayLimitedLength(simInfo, 16)
|
writeShortLVByteArrayLimitedLength(simInfo, 16)
|
||||||
writeShortLVByteArrayLimitedLength(unknown, 32)
|
writeShortLVByteArrayLimitedLength(unknown, 32)
|
||||||
writeShort(0)
|
|
||||||
writeShortLVByteArrayLimitedLength(apn, 16)
|
writeShortLVByteArrayLimitedLength(apn, 16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,7 +387,7 @@ fun BytePacketBuilder.t147(
|
|||||||
) {
|
) {
|
||||||
writeShort(0x147)
|
writeShort(0x147)
|
||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeLong(appId)
|
writeInt(appId.toInt())
|
||||||
writeShortLVByteArrayLimitedLength(apkVersionName, 32)
|
writeShortLVByteArrayLimitedLength(apkVersionName, 32)
|
||||||
writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
|
writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
|
||||||
}
|
}
|
||||||
@ -516,16 +518,18 @@ fun BytePacketBuilder.t188(
|
|||||||
writeShort(0x188)
|
writeShort(0x188)
|
||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeFully(md5(androidId))
|
writeFully(md5(androidId))
|
||||||
}
|
} shouldEqualsTo 16
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytePacketBuilder.t194(
|
fun BytePacketBuilder.t194(
|
||||||
imsiMd5: ByteArray
|
imsiMd5: ByteArray
|
||||||
) {
|
) {
|
||||||
|
imsiMd5 requireSize 16
|
||||||
|
|
||||||
writeShort(0x194)
|
writeShort(0x194)
|
||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeFully(imsiMd5)
|
writeFully(imsiMd5)
|
||||||
}
|
} shouldEqualsTo 16
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytePacketBuilder.t191(
|
fun BytePacketBuilder.t191(
|
||||||
@ -572,7 +576,7 @@ fun BytePacketBuilder.t177(
|
|||||||
writeByte(1)
|
writeByte(1)
|
||||||
writeInt(unknown1.toInt())
|
writeInt(unknown1.toInt())
|
||||||
writeShortLVString(unknown2)
|
writeShortLVString(unknown2)
|
||||||
}
|
} shouldEqualsTo 0x11
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytePacketBuilder.t516( // 1302
|
fun BytePacketBuilder.t516( // 1302
|
||||||
@ -581,7 +585,7 @@ fun BytePacketBuilder.t516( // 1302
|
|||||||
writeShort(0x516)
|
writeShort(0x516)
|
||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeInt(sourceType)
|
writeInt(sourceType)
|
||||||
}
|
} shouldEqualsTo 4
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytePacketBuilder.t521( // 1313
|
fun BytePacketBuilder.t521( // 1313
|
||||||
@ -592,7 +596,7 @@ fun BytePacketBuilder.t521( // 1313
|
|||||||
writeShortLVPacket {
|
writeShortLVPacket {
|
||||||
writeInt(productType)
|
writeInt(productType)
|
||||||
writeShort(unknown)
|
writeShort(unknown)
|
||||||
}
|
} shouldEqualsTo 6
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BytePacketBuilder.t536( // 1334
|
fun BytePacketBuilder.t536( // 1334
|
||||||
@ -626,8 +630,8 @@ fun BytePacketBuilder.t318(
|
|||||||
private fun Boolean.toByte(): Byte = if (this) 1 else 0
|
private fun Boolean.toByte(): Byte = if (this) 1 else 0
|
||||||
private fun Boolean.toInt(): Int = 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 infix fun Int.shouldEqualsTo(int: Int) = check(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 ByteArray.requireSize(exactSize: Int) = check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
|
||||||
|
|
||||||
fun randomAndroidId(): String = buildString(15) {
|
fun randomAndroidId(): String = buildString(15) {
|
||||||
repeat(15) { append(Random.nextInt(10)) }
|
repeat(15) { append(Random.nextInt(10)) }
|
||||||
|
@ -31,9 +31,10 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
|
|||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
client: QQAndroidClient
|
client: QQAndroidClient
|
||||||
): OutgoingPacket = buildLoginOutgoingPacket(client, subAppId) { sequenceId ->
|
): OutgoingPacket = buildLoginOutgoingPacket(client, subAppId) { sequenceId ->
|
||||||
writeOicqRequestPacket(client, EncryptMethodECDH135(client.ecdh), id) {
|
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), id) {
|
||||||
writeShort(9) // subCommand
|
writeShort(9) // subCommand
|
||||||
writeShort(LoginType.PASSWORD.value.toShort())
|
writeShort(0x17)
|
||||||
|
//writeShort(LoginType.PASSWORD.value.toShort())
|
||||||
|
|
||||||
t18(appId, client.appClientVersion, client.account.id)
|
t18(appId, client.appClientVersion, client.account.id)
|
||||||
t1(client.account.id, client.device.ipAddress)
|
t1(client.account.id, client.device.ipAddress)
|
||||||
@ -91,6 +92,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
|
|||||||
tgtgtKey = client.tgtgtKey
|
tgtgtKey = client.tgtgtKey
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//this.build().debugPrint("傻逼")
|
||||||
t145(client.device.guid)
|
t145(client.device.guid)
|
||||||
t147(appId, client.apkVersionName, client.apkSignatureMd5)
|
t147(appId, client.apkVersionName, client.apkSignatureMd5)
|
||||||
|
|
||||||
@ -156,6 +158,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
|
|||||||
writeByte(0) // data count
|
writeByte(0) // data count
|
||||||
}.readBytes())
|
}.readBytes())
|
||||||
})
|
})
|
||||||
|
// this.build().debugPrint("傻逼")
|
||||||
|
|
||||||
// ignored t318 because not logging in by QR
|
// ignored t318 because not logging in by QR
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -94,17 +94,17 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain
|
|||||||
|
|
||||||
// region ByteReadPacket extension
|
// 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 {
|
ByteArrayPool.useInstance {
|
||||||
readFully(it, offset, length)
|
readFully(it, offset, length)
|
||||||
consumer(it.decryptBy(key, length))
|
consumer(it.decryptBy(key, length))
|
||||||
}.also { close() }
|
}.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 {
|
ByteArrayPool.useInstance {
|
||||||
readFully(it, offset, length)
|
readFully(it, offset, length)
|
||||||
consumer(it.decryptBy(key, length))
|
consumer(it.decryptBy(key, length))
|
||||||
|
@ -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
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package net.mamoe.mirai.utils.cryptor
|
package net.mamoe.mirai.utils.cryptor
|
||||||
|
|
||||||
import net.mamoe.mirai.utils.md5
|
import net.mamoe.mirai.utils.md5
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import java.security.*
|
import java.security.*
|
||||||
|
import java.security.spec.ECGenParameterSpec
|
||||||
import java.security.spec.X509EncodedKeySpec
|
import java.security.spec.X509EncodedKeySpec
|
||||||
import javax.crypto.KeyAgreement
|
import javax.crypto.KeyAgreement
|
||||||
|
|
||||||
@ -15,7 +17,7 @@ actual class ECDHKeyPair(
|
|||||||
actual val privateKey: ECDHPrivateKey get() = delegate.private
|
actual val privateKey: ECDHPrivateKey get() = delegate.private
|
||||||
actual val publicKey: ECDHPublicKey get() = delegate.public
|
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")
|
@Suppress("FunctionName")
|
||||||
@ -23,8 +25,12 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair())
|
|||||||
|
|
||||||
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
|
init {
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
}
|
||||||
|
|
||||||
actual fun generateKeyPair(): ECDHKeyPair {
|
actual fun generateKeyPair(): ECDHKeyPair {
|
||||||
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair())
|
return ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair())
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun calculateShareKey(
|
actual fun calculateShareKey(
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
class TypeConversionTest {
|
class TypeConversionTest {
|
||||||
@ -13,6 +17,7 @@ class TypeConversionTest {
|
|||||||
assertEquals("7F", byteArrayOf(0x7F).toUHexString())
|
assertEquals("7F", byteArrayOf(0x7F).toUHexString())
|
||||||
assertEquals("FF", ubyteArrayOf(0xffu).toUHexString())
|
assertEquals("FF", ubyteArrayOf(0xffu).toUHexString())
|
||||||
assertEquals("7F", ubyteArrayOf(0x7fu).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(byteArrayOf(0, 0, 0, 0x01).toUHexString(), 1.toByteArray().toUHexString())
|
||||||
assertEquals(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString())
|
assertEquals(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString())
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user