mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-15 07:37:08 +08:00
Implement mirai-core
This commit is contained in:
parent
b7e4ed75d7
commit
b94d431cd8
buildSrc/src/main/kotlin
gradle.propertiesmirai-core-api/src/nativeMain/kotlin/utils
mirai-core-utils/src/nativeMain/kotlin
mirai-core
build.gradle.kts
src
commonMain/kotlin
commonTest/kotlin
jvmBaseMain/kotlin
jvmTest/kotlin/netinternalkit
mingwMain/kotlin
nativeMain/kotlin
nativeTest/kotlin/test
unixMain/kotlin
@ -28,10 +28,10 @@ object Versions {
|
||||
|
||||
const val kotlinCompilerForIdeaPlugin = "1.7.0-RC"
|
||||
|
||||
const val coroutines = "1.6.1"
|
||||
const val coroutines = "1.6.1-native-mt"
|
||||
const val atomicFU = "0.17.2"
|
||||
const val serialization = "1.3.2"
|
||||
const val ktor = "1.6.7"
|
||||
const val ktor = "1.6.8"
|
||||
|
||||
const val binaryValidator = "0.4.0"
|
||||
|
||||
@ -108,6 +108,8 @@ val `ktor-serialization` = ktor("serialization", Versions.ktor)
|
||||
|
||||
val `ktor-client-core` = ktor("client-core", Versions.ktor)
|
||||
val `ktor-client-cio` = ktor("client-cio", Versions.ktor)
|
||||
val `ktor-client-curl` = ktor("client-curl", Versions.ktor)
|
||||
val `ktor-client-ios` = ktor("client-ios", Versions.ktor)
|
||||
val `ktor-client-okhttp` = ktor("client-okhttp", Versions.ktor)
|
||||
val `ktor-client-android` = ktor("client-android", Versions.ktor)
|
||||
val `ktor-client-logging` = ktor("client-logging", Versions.ktor)
|
||||
|
@ -23,4 +23,5 @@ gnsp.disableApplyOnlyOnRootProjectEnforcement=true
|
||||
mirai.android.target.api.level=24
|
||||
# Enable if you want to use mavenLocal for both Gradle plugin and project dependencies resolutions.
|
||||
systemProp.use.maven.local=false
|
||||
org.gradle.caching=true
|
||||
org.gradle.caching=true
|
||||
kotlin.native.ignoreIncorrectDependencies=true
|
@ -75,7 +75,7 @@ public actual abstract class LoginSolver actual constructor() {
|
||||
* @return `SwingSolver` 或 `StandardCharImageLoginSolver` 或 `null`
|
||||
*/
|
||||
public actual val Default: LoginSolver?
|
||||
get() = TODO("Not yet implemented")
|
||||
get() = null
|
||||
|
||||
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@Suppress("unused")
|
||||
|
@ -215,5 +215,5 @@ internal class PosixInputForFile(val file: CPointer<FILE>) : AbstractInput() {
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
internal fun PosixException.wrapIO(): IOException =
|
||||
public fun PosixException.wrapIO(): IOException =
|
||||
IOException("I/O operation failed due to posix error code $errno", this)
|
||||
|
@ -44,6 +44,12 @@ public object Services {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public fun print(): String {
|
||||
lock.withLock {
|
||||
return registered.entries.joinToString { "${it.key}:${it.value}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -57,7 +63,7 @@ public actual fun <T : Any> loadService(
|
||||
clazz: KClass<out T>,
|
||||
fallbackImplementation: String?
|
||||
): T = loadServiceOrNull(clazz, fallbackImplementation)
|
||||
?: error("Could not load service '${clazz.qualifiedName ?: clazz}'")
|
||||
?: error("Could not load service '${clazz.qualifiedName ?: clazz}'. Current services: ${Services.print()}")
|
||||
|
||||
public actual fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> =
|
||||
Services.implementations(qualifiedNameOrFail(clazz))?.asSequence().orEmpty().castUp()
|
||||
|
@ -9,10 +9,12 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
private val properties: MutableMap<String, String> = ConcurrentHashMap()
|
||||
|
||||
internal actual fun getProperty(name: String, default: String): String? {
|
||||
TODO("Not yet implemented")
|
||||
return properties.getOrElse(name) { default }
|
||||
}
|
||||
|
||||
internal actual fun setProperty(name: String, value: String) {
|
||||
TODO("Not yet implemented")
|
||||
properties[name] = value
|
||||
}
|
@ -102,6 +102,29 @@ kotlin {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
val mingwMain by getting {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
configure((LINUX_TARGETS + WIN_TARGETS).map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
implementation(`ktor-client-curl`)
|
||||
}
|
||||
}
|
||||
|
||||
configure(MAC_TARGETS.map { getByName(it + "Main") }) {
|
||||
dependencies {
|
||||
implementation(`ktor-client-ios`)
|
||||
}
|
||||
}
|
||||
|
||||
// val unixMain by getting {
|
||||
// dependencies {
|
||||
// implementation(`ktor-client-cio`)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("MiraiImplKt_common")
|
||||
|
||||
package net.mamoe.mirai.internal
|
||||
|
||||
import io.ktor.client.*
|
||||
@ -31,25 +33,14 @@ import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
|
||||
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl.Companion.impl
|
||||
import net.mamoe.mirai.internal.event.EventChannelToEventDispatcherAdapter
|
||||
import net.mamoe.mirai.internal.event.InternalEventMechanism
|
||||
import net.mamoe.mirai.internal.message.*
|
||||
import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
|
||||
import net.mamoe.mirai.internal.message.EmptyRefineContext
|
||||
import net.mamoe.mirai.internal.message.RefineContext
|
||||
import net.mamoe.mirai.internal.message.SimpleRefineContext
|
||||
import net.mamoe.mirai.internal.message.data.*
|
||||
import net.mamoe.mirai.internal.message.data.FileMessageImpl
|
||||
import net.mamoe.mirai.internal.message.data.OfflineAudioImpl
|
||||
import net.mamoe.mirai.internal.message.data.OnlineAudioImpl
|
||||
import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl
|
||||
import net.mamoe.mirai.internal.message.image.*
|
||||
import net.mamoe.mirai.internal.message.image.OfflineGroupImage
|
||||
import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl
|
||||
import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl
|
||||
import net.mamoe.mirai.internal.message.source.*
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromFriendImpl
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromGroupImpl
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromStrangerImpl
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromTempImpl
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToStrangerImpl
|
||||
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToTempImpl
|
||||
import net.mamoe.mirai.internal.message.toMessageChainNoSource
|
||||
import net.mamoe.mirai.internal.network.components.EventDispatcher
|
||||
import net.mamoe.mirai.internal.network.highway.ChannelKind
|
||||
import net.mamoe.mirai.internal.network.highway.ResourceKind
|
||||
@ -77,14 +68,19 @@ import net.mamoe.mirai.message.MessageSerializers
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
internal fun getMiraiImpl() = Mirai as MiraiImpl
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal expect fun _MiraiImpl_static_init()
|
||||
|
||||
@OptIn(LowLevelApi::class)
|
||||
// not object for ServiceLoader.
|
||||
internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
companion object {
|
||||
init {
|
||||
_MiraiImpl_static_init()
|
||||
MessageSerializers.registerSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer())
|
||||
MessageSerializers.registerSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer())
|
||||
MessageSerializers.registerSerializer(OnlineFriendImageImpl::class, OnlineFriendImageImpl.serializer())
|
||||
@ -870,17 +866,21 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
return main.toForwardMessageNodes(bot, context)
|
||||
}
|
||||
|
||||
protected open suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
|
||||
private suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
|
||||
val msg = this
|
||||
|
||||
@Suppress("USELESS_CAST") // compiler bug, do not remove
|
||||
val senderName = (msg.msgHead.groupInfo?.groupCard
|
||||
?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
|
||||
?: msg.msgHead.fromUin.toString()) as String
|
||||
val chain = listOf(msg)
|
||||
.toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP)
|
||||
.refineDeep(bot, refineContext)
|
||||
return ForwardMessage.Node(
|
||||
senderId = msg.msgHead.fromUin,
|
||||
time = msg.msgHead.msgTime,
|
||||
senderName = msg.msgHead.groupInfo?.groupCard
|
||||
?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
|
||||
?: msg.msgHead.fromUin.toString(),
|
||||
messageChain = listOf(msg)
|
||||
.toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP)
|
||||
.refineDeep(bot, refineContext)
|
||||
senderName = senderName,
|
||||
messageChain = chain
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.internal.network.component.ComponentKey
|
||||
import net.mamoe.mirai.internal.network.components.ServerList.Companion.DEFAULT_SERVER_LIST
|
||||
import net.mamoe.mirai.internal.network.handler.SocketAddress
|
||||
import net.mamoe.mirai.internal.network.handler.createSocketAddress
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.TestOnly
|
||||
import net.mamoe.mirai.utils.info
|
||||
@ -33,7 +34,7 @@ internal data class ServerAddress(
|
||||
return "$host:$port"
|
||||
}
|
||||
|
||||
fun toSocketAddress(): SocketAddress = SocketAddress(host, port)
|
||||
fun toSocketAddress(): SocketAddress = createSocketAddress(host, port)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,9 +27,12 @@ internal expect fun interface NetworkHandlerFactory<out H : NetworkHandler> {
|
||||
}
|
||||
}
|
||||
|
||||
internal expect abstract class SocketAddress {
|
||||
val host: String
|
||||
val port: Int
|
||||
}
|
||||
internal expect abstract class SocketAddress
|
||||
|
||||
internal expect fun SocketAddress(host: String, port: Int): SocketAddress
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
internal expect fun SocketAddress.getHost(): String
|
||||
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
internal expect fun SocketAddress.getPort(): Int
|
||||
|
||||
internal expect fun createSocketAddress(host: String, port: Int): SocketAddress
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.utils.coroutineName
|
||||
import net.mamoe.mirai.utils.debug
|
||||
import net.mamoe.mirai.utils.systemProp
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.native.concurrent.ThreadLocal
|
||||
|
||||
internal class LoggingStateObserver(
|
||||
val logger: MiraiLogger,
|
||||
@ -72,6 +73,7 @@ internal class LoggingStateObserver(
|
||||
)
|
||||
}
|
||||
|
||||
@ThreadLocal
|
||||
companion object {
|
||||
/**
|
||||
* - `on`/`true` for simple logging
|
||||
|
@ -7,11 +7,17 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("PlatformSocketKt_common")
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.errors.*
|
||||
import net.mamoe.mirai.internal.network.handler.SocketAddress
|
||||
import net.mamoe.mirai.internal.network.handler.getHost
|
||||
import net.mamoe.mirai.internal.network.handler.getPort
|
||||
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* TCP Socket.
|
||||
@ -32,7 +38,6 @@ internal expect class PlatformSocket : Closeable, HighwayProtocolChannel {
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
override suspend fun read(): ByteReadPacket
|
||||
suspend fun connect(serverHost: String, serverPort: Int)
|
||||
|
||||
companion object {
|
||||
suspend fun connect(
|
||||
@ -48,6 +53,10 @@ internal expect class PlatformSocket : Closeable, HighwayProtocolChannel {
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend inline fun PlatformSocket.Companion.connect(address: SocketAddress): PlatformSocket {
|
||||
return connect(address.getHost(), address.getPort())
|
||||
}
|
||||
|
||||
|
||||
internal expect class SocketException : IOException {
|
||||
constructor()
|
||||
|
@ -22,20 +22,20 @@ internal class MessageProtocolFacadeTest : AbstractTest() {
|
||||
"""
|
||||
QuoteReplyProtocol
|
||||
CustomMessageProtocol
|
||||
FaceProtocol
|
||||
FileMessageProtocol
|
||||
FlashImageProtocol
|
||||
FaceProtocol
|
||||
ImageProtocol
|
||||
MarketFaceProtocol
|
||||
MusicShareProtocol
|
||||
PokeMessageProtocol
|
||||
IgnoredMessagesProtocol
|
||||
PttMessageProtocol
|
||||
RichMessageProtocol
|
||||
TextProtocol
|
||||
VipFaceProtocol
|
||||
ForwardMessageProtocol
|
||||
LongMessageProtocol
|
||||
IgnoredMessagesProtocol
|
||||
UnsupportedMessageProtocol
|
||||
GeneralMessageSenderProtocol
|
||||
""".trimIndent(),
|
||||
|
@ -24,11 +24,8 @@ import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
|
||||
import net.mamoe.mirai.internal.network.component.setAll
|
||||
import net.mamoe.mirai.internal.network.components.*
|
||||
import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||
import net.mamoe.mirai.internal.network.handler.*
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
|
||||
import net.mamoe.mirai.internal.network.handler.SocketAddress
|
||||
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
||||
import net.mamoe.mirai.internal.utils.subLogger
|
||||
@ -175,7 +172,7 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
|
||||
|
||||
//Use overrideComponents to avoid StackOverflowError when applying components
|
||||
open fun createAddress(): SocketAddress =
|
||||
overrideComponents[ServerList].pollAny().let { SocketAddress(it.host, it.port) }
|
||||
overrideComponents[ServerList].pollAny().let { createSocketAddress(it.host, it.port) }
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Assertions
|
||||
|
17
mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt
Normal file
17
mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("MiraiImplKt")
|
||||
|
||||
package net.mamoe.mirai.internal
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal actual fun _MiraiImpl_static_init() {
|
||||
// nop
|
||||
}
|
@ -12,8 +12,14 @@ package net.mamoe.mirai.internal.network.handler
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
|
||||
internal actual typealias SocketAddress = java.net.SocketAddress
|
||||
internal actual typealias SocketAddress = java.net.InetSocketAddress
|
||||
|
||||
internal actual fun SocketAddress(host: String, port: Int): SocketAddress {
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
internal actual fun SocketAddress.getHost(): String = hostString ?: error("Failed to get host from address '$this'.")
|
||||
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
internal actual fun SocketAddress.getPort(): Int = this.port
|
||||
|
||||
internal actual fun createSocketAddress(host: String, port: Int): SocketAddress {
|
||||
return InetSocketAddress.createUnresolved(host, port)
|
||||
}
|
@ -27,6 +27,7 @@ import net.mamoe.mirai.internal.network.handler.CommonNetworkHandler
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import net.mamoe.mirai.utils.debug
|
||||
import java.net.SocketAddress
|
||||
import io.netty.channel.Channel as NettyChannel
|
||||
@ -34,7 +35,7 @@ import io.netty.channel.Channel as NettyChannel
|
||||
internal open class NettyNetworkHandler(
|
||||
context: NetworkHandlerContext,
|
||||
address: SocketAddress,
|
||||
) : CommonNetworkHandler<NettyChannel>(context, address) {
|
||||
) : CommonNetworkHandler<NettyChannel>(context, address.cast()) {
|
||||
override fun toString(): String {
|
||||
return "NettyNetworkHandler(context=$context, address=$address)"
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ package net.mamoe.mirai.internal.network.impl.netty
|
||||
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
|
||||
import java.net.SocketAddress
|
||||
import net.mamoe.mirai.internal.network.handler.SocketAddress
|
||||
|
||||
internal object NettyNetworkHandlerFactory : NetworkHandlerFactory<NettyNetworkHandler> {
|
||||
override fun create(context: NetworkHandlerContext, address: SocketAddress): NettyNetworkHandler {
|
||||
|
@ -86,7 +86,7 @@ internal actual class PlatformSocket : Closeable, HighwayProtocolChannel {
|
||||
}
|
||||
}
|
||||
|
||||
actual suspend fun connect(serverHost: String, serverPort: Int) {
|
||||
suspend fun connect(serverHost: String, serverPort: Int) {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
socket = Socket(serverHost, serverPort)
|
||||
readChannel = socket.getInputStream().buffered()
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 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.
|
||||
@ -22,6 +22,7 @@ import net.mamoe.mirai.internal.network.components.ServerList
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
|
||||
import net.mamoe.mirai.internal.network.handler.SocketAddress
|
||||
import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector
|
||||
import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
|
||||
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler
|
||||
@ -31,7 +32,6 @@ import java.awt.Component
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.net.SocketAddress
|
||||
import javax.swing.*
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.full.declaredMembers
|
||||
|
10
mirai-core/src/mingwMain/kotlin/package.kt
Normal file
10
mirai-core/src/mingwMain/kotlin/package.kt
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal
|
146
mirai-core/src/mingwMain/kotlin/utils/PlatformSocket.kt
Normal file
146
mirai-core/src/mingwMain/kotlin/utils/PlatformSocket.kt
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.errors.*
|
||||
import kotlinx.cinterop.*
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.toIpV4Long
|
||||
import net.mamoe.mirai.utils.DEFAULT_BUFFER_SIZE
|
||||
import net.mamoe.mirai.utils.toReadPacket
|
||||
import net.mamoe.mirai.utils.wrapIO
|
||||
import platform.posix.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* TCP Socket.
|
||||
*/
|
||||
internal actual class PlatformSocket(
|
||||
private val socket: Int
|
||||
) : Closeable, HighwayProtocolChannel {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val dispatcher: CoroutineDispatcher = newSingleThreadContext("PlatformSocket#$socket.dispatcher")
|
||||
|
||||
private val readLock = Mutex()
|
||||
private val readBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
|
||||
private val writeLock = Mutex()
|
||||
private val writeBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
|
||||
|
||||
actual val isOpen: Boolean
|
||||
get() = write(socket, null, 0) != 0
|
||||
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
actual override fun close() {
|
||||
if (close(socket) != 0) {
|
||||
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
actual suspend fun send(packet: ByteArray, offset: Int, length: Int): Unit = readLock.withLock {
|
||||
withContext(dispatcher) {
|
||||
require(offset >= 0) { "offset must >= 0" }
|
||||
require(length >= 0) { "length must >= 0" }
|
||||
require(offset + length <= packet.size) { "It must follows offset + length <= packet.size" }
|
||||
packet.usePinned { pin ->
|
||||
if (write(socket, pin.addressOf(offset), length.convert()) != 0) {
|
||||
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SendPacketInternalException
|
||||
*/
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
actual override suspend fun send(packet: ByteReadPacket): Unit = readLock.withLock {
|
||||
withContext(dispatcher) {
|
||||
val writeBuffer = writeBuffer
|
||||
val length = packet.readAvailable(writeBuffer.get())
|
||||
if (write(socket, writeBuffer.addressOf(0), length.convert()) != 0) {
|
||||
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
actual override suspend fun read(): ByteReadPacket = writeLock.withLock {
|
||||
withContext(dispatcher) {
|
||||
val readBuffer = readBuffer
|
||||
val length = read(socket, readBuffer.addressOf(0), readBuffer.get().size.convert())
|
||||
readBuffer.get().toReadPacket(length = length)
|
||||
}
|
||||
}
|
||||
|
||||
actual companion object {
|
||||
|
||||
@OptIn(UnsafeNumber::class, ExperimentalIoApi::class)
|
||||
actual suspend fun connect(
|
||||
serverIp: String,
|
||||
serverPort: Int
|
||||
): PlatformSocket {
|
||||
val addr = memScoped {
|
||||
alloc<sockaddr_in>() {
|
||||
sin_family = AF_INET.convert()
|
||||
resolveIpFromHost(serverIp)
|
||||
sin_addr.S_un.S_addr = resolveIpFromHost(serverIp)
|
||||
}
|
||||
}.reinterpret<sockaddr>()
|
||||
|
||||
val id = socket(AF_INET, 1 /* SOCKET_STREAM */, IPPROTO_TCP)
|
||||
if (id != 0uL) throw PosixException.forErrno(posixFunctionName = "socket()")
|
||||
|
||||
val conn = connect(id, addr.ptr, sizeOf<sockaddr_in>().convert())
|
||||
if (conn != 0) throw PosixException.forErrno(posixFunctionName = "connect()")
|
||||
|
||||
return PlatformSocket(conn)
|
||||
}
|
||||
|
||||
private fun resolveIpFromHost(serverIp: String): UInt {
|
||||
val host = platform.windows.gethostbyname(serverIp)
|
||||
?: throw IllegalStateException("Failed to resolve IP from host. host=$serverIp")
|
||||
|
||||
// val str = try {
|
||||
// val hAddrList = host.pointed.h_addr_list
|
||||
// ?: throw IllegalStateException("Empty IP list resolved from host. host=$serverIp")
|
||||
//
|
||||
// hAddrList[0]!!.toKString()
|
||||
// } finally {
|
||||
// free(host)
|
||||
// }
|
||||
|
||||
// TODO: 2022/5/30 check memory
|
||||
|
||||
return serverIp.toIpV4Long().toUInt()
|
||||
// return str.toIpV4Long().toUInt()
|
||||
}
|
||||
|
||||
actual suspend inline fun <R> withConnection(
|
||||
serverIp: String,
|
||||
serverPort: Int,
|
||||
block: PlatformSocket.() -> R
|
||||
): R {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
return connect(serverIp, serverPort).use(block)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
mirai-core/src/nativeMain/kotlin/MiraiImpl.kt
Normal file
27
mirai-core/src/nativeMain/kotlin/MiraiImpl.kt
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal
|
||||
|
||||
import kotlinx.atomicfu.atomic
|
||||
import net.mamoe.mirai.internal.utils.MiraiCoreServices
|
||||
|
||||
|
||||
public fun initMirai() {
|
||||
_MiraiImpl_static_init()
|
||||
}
|
||||
|
||||
|
||||
private val initialized = atomic(false)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal actual fun _MiraiImpl_static_init() {
|
||||
if (!initialized.compareAndSet(expect = false, update = true)) return
|
||||
MiraiCoreServices.registerAll()
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.handler
|
||||
|
||||
import io.ktor.utils.io.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.internal.network.components.PacketCodec
|
||||
import net.mamoe.mirai.internal.network.components.SsoProcessor
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.internal.utils.PlatformSocket
|
||||
import net.mamoe.mirai.internal.utils.connect
|
||||
import net.mamoe.mirai.utils.childScope
|
||||
import net.mamoe.mirai.utils.readPacketExact
|
||||
import net.mamoe.mirai.utils.toLongUnsigned
|
||||
|
||||
internal class NativeNetworkHandler(
|
||||
context: NetworkHandlerContext,
|
||||
address: SocketAddress
|
||||
) : CommonNetworkHandler<NativeNetworkHandler.NativeConn>(context, address) {
|
||||
internal object Factory : NetworkHandlerFactory<NativeNetworkHandler> {
|
||||
override fun create(context: NetworkHandlerContext, address: SocketAddress): NativeNetworkHandler {
|
||||
return NativeNetworkHandler(context, address)
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class NativeConn(
|
||||
private val socket: PlatformSocket,
|
||||
) : Closeable, CoroutineScope by coroutineContext.childScope("NativeConn") {
|
||||
private val decodePipeline: PacketDecodePipeline = PacketDecodePipeline(this.coroutineContext)
|
||||
|
||||
private val packetCodec: PacketCodec by lazy { context[PacketCodec] }
|
||||
private val ssoProcessor: SsoProcessor by lazy { context[SsoProcessor] }
|
||||
|
||||
private val sendQueue: Channel<OutgoingPacket> = Channel(Channel.BUFFERED) { undelivered ->
|
||||
launch { write(undelivered) }
|
||||
}
|
||||
|
||||
private val lengthDelimitedPacketReader = LengthDelimitedPacketReader()
|
||||
|
||||
/**
|
||||
* Not thread-safe
|
||||
*/
|
||||
private inner class LengthDelimitedPacketReader : Closeable {
|
||||
private var missingLength: Long = 0
|
||||
private val bufferedPackets: MutableList<ByteReadPacket> = ArrayList(10)
|
||||
|
||||
fun offer(packet: ByteReadPacket) {
|
||||
missingLength -= packet.remaining
|
||||
if (missingLength <= 0) {
|
||||
emit()
|
||||
}
|
||||
}
|
||||
|
||||
fun emit() {
|
||||
when (bufferedPackets.size) {
|
||||
0 -> {}
|
||||
1 -> {
|
||||
val packet = bufferedPackets.first()
|
||||
if (missingLength == 0L) {
|
||||
packetCodec.decodeRaw(ssoProcessor.ssoSession, packet)
|
||||
} else {
|
||||
check(missingLength < 0L) { "Failed check: remainingLength < 0L" }
|
||||
|
||||
val previousPacketLength = missingLength + packet.remaining
|
||||
decodePipeline.send(
|
||||
packetCodec.decodeRaw(
|
||||
ssoProcessor.ssoSession,
|
||||
packet.readPacketExact(previousPacketLength.toInt())
|
||||
)
|
||||
)
|
||||
|
||||
// now packet contain new packet.
|
||||
missingLength = packet.readInt().toLongUnsigned() - 4
|
||||
bufferedPackets[0] = packet
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val combined: ByteReadPacket
|
||||
if (missingLength == 0L) {
|
||||
combined = buildPacket(bufferedPackets.sumOf { it.remaining }.toInt()) {
|
||||
bufferedPackets.forEach { writePacket(it) }
|
||||
}
|
||||
|
||||
bufferedPackets.clear()
|
||||
} else {
|
||||
val lastPacket = bufferedPackets.last()
|
||||
val previousPacketPartLength = missingLength + lastPacket.remaining
|
||||
val combinedLength =
|
||||
(bufferedPackets.sumOf { it.remaining } - lastPacket.remaining + previousPacketPartLength).toInt()
|
||||
|
||||
combined = buildPacket(combinedLength) {
|
||||
repeat(bufferedPackets.size - 1) { i ->
|
||||
writePacket(bufferedPackets[i])
|
||||
}
|
||||
writePacket(lastPacket, previousPacketPartLength)
|
||||
}
|
||||
|
||||
bufferedPackets.clear()
|
||||
|
||||
// now packet contain new packet.
|
||||
missingLength = lastPacket.readInt().toLongUnsigned() - 4
|
||||
bufferedPackets.add(lastPacket)
|
||||
}
|
||||
|
||||
decodePipeline.send(
|
||||
packetCodec.decodeRaw(
|
||||
ssoProcessor.ssoSession,
|
||||
combined
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
bufferedPackets.forEach { it.close() }
|
||||
}
|
||||
}
|
||||
|
||||
private val sender = launch {
|
||||
while (isActive) {
|
||||
val result = sendQueue.receiveCatching()
|
||||
result.onFailure { if (it is CancellationException) return@launch }
|
||||
|
||||
result.getOrNull()?.let { packet ->
|
||||
try {
|
||||
socket.send(packet.delegate, 0, packet.delegate.size)
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) return@launch
|
||||
logger.error("Error while sending packet '${packet.commandName}'", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val receiver = launch {
|
||||
while (isActive) {
|
||||
try {
|
||||
val packet = socket.read()
|
||||
lengthDelimitedPacketReader.offer(packet)
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) return@launch
|
||||
logger.error("Error while reading packet.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun write(packet: OutgoingPacket) {
|
||||
sendQueue.trySend(packet).onFailure {
|
||||
throw it
|
||||
?: throw IllegalStateException("Failed to send packet '${packet.commandName}' without reason.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createConnection(): NativeConn {
|
||||
return NativeConn(PlatformSocket.connect(address))
|
||||
}
|
||||
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
override fun NativeConn.close() {
|
||||
this.close()
|
||||
}
|
||||
|
||||
override fun NativeConn.writeAndFlushOrCloseAsync(packet: OutgoingPacket) {
|
||||
write(packet)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.handler
|
||||
|
||||
/**
|
||||
* Factory for a specific [NetworkHandler] implementation.
|
||||
*/
|
||||
internal actual fun interface NetworkHandlerFactory<out H : NetworkHandler> {
|
||||
actual fun create(
|
||||
context: NetworkHandlerContext,
|
||||
host: String,
|
||||
port: Int
|
||||
): H = create(context, SocketAddressImpl(host, port))
|
||||
|
||||
/**
|
||||
* Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED]
|
||||
*/
|
||||
actual fun create(
|
||||
context: NetworkHandlerContext,
|
||||
address: SocketAddress
|
||||
): H
|
||||
|
||||
actual companion object {
|
||||
actual fun getPlatformDefault(): NetworkHandlerFactory<*> = NativeNetworkHandler.Factory
|
||||
}
|
||||
}
|
@ -9,41 +9,19 @@
|
||||
|
||||
package net.mamoe.mirai.internal.network.handler
|
||||
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
|
||||
|
||||
internal actual abstract class SocketAddress(
|
||||
actual val host: String,
|
||||
actual val port: Int,
|
||||
val host: String,
|
||||
val port: Int,
|
||||
@Suppress("UNUSED_PARAMETER") constructorMarker: Unit?, // avoid ambiguity with function SocketAddress
|
||||
)
|
||||
|
||||
internal actual fun SocketAddress.getHost(): String = host
|
||||
internal actual fun SocketAddress.getPort(): Int = port
|
||||
|
||||
|
||||
internal class SocketAddressImpl(host: String, port: Int) : SocketAddress(host, port, null)
|
||||
|
||||
internal actual fun SocketAddress(host: String, port: Int): SocketAddress {
|
||||
internal actual fun createSocketAddress(host: String, port: Int): SocketAddress {
|
||||
return SocketAddressImpl(host, port)
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for a specific [NetworkHandler] implementation.
|
||||
*/
|
||||
internal actual fun interface NetworkHandlerFactory<out H : NetworkHandler> {
|
||||
actual fun create(
|
||||
context: NetworkHandlerContext,
|
||||
host: String,
|
||||
port: Int
|
||||
): H = create(context, SocketAddressImpl(host, port))
|
||||
|
||||
/**
|
||||
* Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED]
|
||||
*/
|
||||
actual fun create(
|
||||
context: NetworkHandlerContext,
|
||||
address: SocketAddress
|
||||
): H
|
||||
|
||||
actual companion object {
|
||||
actual fun getPlatformDefault(): NetworkHandlerFactory<*> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,58 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.errors.*
|
||||
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
|
||||
|
||||
/**
|
||||
* TCP Socket.
|
||||
*/
|
||||
internal actual class PlatformSocket : Closeable, HighwayProtocolChannel {
|
||||
actual val isOpen: Boolean
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
actual override fun close() {
|
||||
}
|
||||
|
||||
actual suspend fun send(packet: ByteArray, offset: Int, length: Int) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SendPacketInternalException
|
||||
*/
|
||||
actual override suspend fun send(packet: ByteReadPacket) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
actual override suspend fun read(): ByteReadPacket {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual suspend fun connect(serverHost: String, serverPort: Int) {
|
||||
}
|
||||
|
||||
actual companion object {
|
||||
actual suspend fun connect(
|
||||
serverIp: String,
|
||||
serverPort: Int
|
||||
): PlatformSocket {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual suspend inline fun <R> withConnection(
|
||||
serverIp: String,
|
||||
serverPort: Int,
|
||||
block: PlatformSocket.() -> R
|
||||
): R {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal actual class SocketException : IOException {
|
||||
actual constructor() : super("", null)
|
||||
@ -76,4 +25,4 @@ internal actual class NoRouteToHostException : IOException {
|
||||
internal actual class UnknownHostException : IOException {
|
||||
actual constructor() : super("")
|
||||
actual constructor(message: String) : super(message)
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,13 @@
|
||||
package net.mamoe.mirai.internal.test
|
||||
|
||||
import net.mamoe.mirai.IMirai
|
||||
import net.mamoe.mirai.internal.initMirai
|
||||
import net.mamoe.mirai.utils.setSystemProp
|
||||
import kotlin.test.Test
|
||||
|
||||
|
||||
internal actual fun initPlatform() {
|
||||
initMirai()
|
||||
}
|
||||
|
||||
internal actual class PlatformInitializationTest actual constructor() : AbstractTest() {
|
||||
@ -27,6 +29,9 @@ internal actual class PlatformInitializationTest actual constructor() : Abstract
|
||||
* All test classes should inherit from [AbstractTest]
|
||||
*/
|
||||
internal actual abstract class AbstractTest actual constructor() : CommonAbstractTest() {
|
||||
init {
|
||||
Companion
|
||||
}
|
||||
|
||||
actual companion object {
|
||||
init {
|
||||
|
10
mirai-core/src/unixMain/kotlin/package.kt
Normal file
10
mirai-core/src/unixMain/kotlin/package.kt
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal
|
146
mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt
Normal file
146
mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.errors.*
|
||||
import kotlinx.cinterop.*
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.toIpV4Long
|
||||
import net.mamoe.mirai.utils.DEFAULT_BUFFER_SIZE
|
||||
import net.mamoe.mirai.utils.toReadPacket
|
||||
import net.mamoe.mirai.utils.wrapIO
|
||||
import platform.posix.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* TCP Socket.
|
||||
*/
|
||||
@OptIn(UnsafeNumber::class)
|
||||
internal actual class PlatformSocket(
|
||||
private val socket: Int
|
||||
) : Closeable, HighwayProtocolChannel {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val dispatcher: CoroutineDispatcher = newSingleThreadContext("PlatformSocket#$socket.dispatcher")
|
||||
|
||||
private val readLock = Mutex()
|
||||
private val readBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
|
||||
private val writeLock = Mutex()
|
||||
private val writeBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
|
||||
|
||||
actual val isOpen: Boolean
|
||||
get() = write(socket, null, 0).convert<Long>() != 0L
|
||||
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
actual override fun close() {
|
||||
if (close(socket) != 0) {
|
||||
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
actual suspend fun send(packet: ByteArray, offset: Int, length: Int): Unit = readLock.withLock {
|
||||
withContext(dispatcher) {
|
||||
require(offset >= 0) { "offset must >= 0" }
|
||||
require(length >= 0) { "length must >= 0" }
|
||||
require(offset + length <= packet.size) { "It must follows offset + length <= packet.size" }
|
||||
packet.usePinned { pin ->
|
||||
if (write(socket, pin.addressOf(offset), length.convert()).convert<Long>() != 0L) {
|
||||
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SendPacketInternalException
|
||||
*/
|
||||
@OptIn(ExperimentalIoApi::class)
|
||||
actual override suspend fun send(packet: ByteReadPacket): Unit = readLock.withLock {
|
||||
withContext(dispatcher) {
|
||||
val writeBuffer = writeBuffer
|
||||
val length = packet.readAvailable(writeBuffer.get())
|
||||
if (write(socket, writeBuffer.addressOf(0), length.convert()).convert<Long>() != 0L) {
|
||||
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
actual override suspend fun read(): ByteReadPacket = writeLock.withLock {
|
||||
withContext(dispatcher) {
|
||||
val readBuffer = readBuffer
|
||||
val length = read(socket, readBuffer.addressOf(0), readBuffer.get().size.convert()).convert<Long>()
|
||||
readBuffer.get().toReadPacket(length = length.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
actual companion object {
|
||||
|
||||
@OptIn(UnsafeNumber::class, ExperimentalIoApi::class)
|
||||
actual suspend fun connect(
|
||||
serverIp: String,
|
||||
serverPort: Int
|
||||
): PlatformSocket {
|
||||
val addr = memScoped {
|
||||
alloc<sockaddr_in>() {
|
||||
sin_family = AF_INET.convert()
|
||||
resolveIpFromHost(serverIp)
|
||||
sin_addr.s_addr = resolveIpFromHost(serverIp)
|
||||
}
|
||||
}.reinterpret<sockaddr>()
|
||||
|
||||
val id = socket(AF_INET, 1 /* SOCKET_STREAM */, IPPROTO_TCP)
|
||||
if (id != 0) throw PosixException.forErrno(posixFunctionName = "socket()")
|
||||
|
||||
val conn = connect(id, addr.ptr, sizeOf<sockaddr_in>().convert())
|
||||
if (conn != 0) throw PosixException.forErrno(posixFunctionName = "connect()")
|
||||
|
||||
return PlatformSocket(conn)
|
||||
}
|
||||
|
||||
private fun resolveIpFromHost(serverIp: String): UInt {
|
||||
val host = gethostbyname(serverIp)
|
||||
?: throw IllegalStateException("Failed to resolve IP from host. host=$serverIp")
|
||||
|
||||
val str = try {
|
||||
val hAddrList = host.pointed.h_addr_list
|
||||
?: throw IllegalStateException("Empty IP list resolved from host. host=$serverIp")
|
||||
|
||||
hAddrList[0]!!.toKString()
|
||||
} finally {
|
||||
free(host)
|
||||
}
|
||||
|
||||
// TODO: 2022/5/30 check memory
|
||||
|
||||
return str.toIpV4Long().toUInt()
|
||||
}
|
||||
|
||||
actual suspend inline fun <R> withConnection(
|
||||
serverIp: String,
|
||||
serverPort: Int,
|
||||
block: PlatformSocket.() -> R
|
||||
): R {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
return connect(serverIp, serverPort).use(block)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user