diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 26103dd55..4e56c89eb 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -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) diff --git a/gradle.properties b/gradle.properties index 4fb2fcd07..a305e6101 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file +org.gradle.caching=true +kotlin.native.ignoreIncorrectDependencies=true \ No newline at end of file diff --git a/mirai-core-api/src/nativeMain/kotlin/utils/LoginSolver.kt b/mirai-core-api/src/nativeMain/kotlin/utils/LoginSolver.kt index 6e7361c6a..6ce474a5c 100644 --- a/mirai-core-api/src/nativeMain/kotlin/utils/LoginSolver.kt +++ b/mirai-core-api/src/nativeMain/kotlin/utils/LoginSolver.kt @@ -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") diff --git a/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt b/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt index f652d61cd..308bd3b86 100644 --- a/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt +++ b/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt @@ -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) diff --git a/mirai-core-utils/src/nativeMain/kotlin/Service.kt b/mirai-core-utils/src/nativeMain/kotlin/Service.kt index 9530e4144..76a549459 100644 --- a/mirai-core-utils/src/nativeMain/kotlin/Service.kt +++ b/mirai-core-utils/src/nativeMain/kotlin/Service.kt @@ -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() diff --git a/mirai-core-utils/src/nativeMain/kotlin/getProperty.kt b/mirai-core-utils/src/nativeMain/kotlin/getProperty.kt index 43a7583b1..1ee367d81 100644 --- a/mirai-core-utils/src/nativeMain/kotlin/getProperty.kt +++ b/mirai-core-utils/src/nativeMain/kotlin/getProperty.kt @@ -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 } \ No newline at end of file diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts index b08e71316..e8bc330c6 100644 --- a/mirai-core/build.gradle.kts +++ b/mirai-core/build.gradle.kts @@ -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`) +// } +// } } } diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 5b68ae763..8d9399b3c 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -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 ) } diff --git a/mirai-core/src/commonMain/kotlin/network/components/ServerList.kt b/mirai-core/src/commonMain/kotlin/network/components/ServerList.kt index 0d41f8260..16c3adc1a 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/ServerList.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/ServerList.kt @@ -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) } /** diff --git a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerFactory.kt b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerFactory.kt index 9fa66d317..6bc293a08 100644 --- a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerFactory.kt @@ -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 diff --git a/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt b/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt index 35e537efd..6fbbd4ea0 100644 --- a/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt +++ b/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt @@ -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 diff --git a/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt index 32e8f5882..985b47d57 100644 --- a/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt +++ b/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt @@ -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() diff --git a/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt b/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt index 0aed8969b..33a60b7e4 100644 --- a/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt @@ -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(), diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt index f4828ea71..b85710329 100644 --- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt @@ -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 diff --git a/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt b/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt new file mode 100644 index 000000000..076666aac --- /dev/null +++ b/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt @@ -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 +} diff --git a/mirai-core/src/jvmBaseMain/kotlin/network/handler/SocketAddress.kt b/mirai-core/src/jvmBaseMain/kotlin/network/handler/SocketAddress.kt index ae732d448..5e692a13a 100644 --- a/mirai-core/src/jvmBaseMain/kotlin/network/handler/SocketAddress.kt +++ b/mirai-core/src/jvmBaseMain/kotlin/network/handler/SocketAddress.kt @@ -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) } \ No newline at end of file diff --git a/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandler.kt b/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandler.kt index a22c16a30..e1baad02b 100644 --- a/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandler.kt +++ b/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandler.kt @@ -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)" } diff --git a/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandlerFactory.kt b/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandlerFactory.kt index ec6e5173e..f66b65ae2 100644 --- a/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandlerFactory.kt +++ b/mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandlerFactory.kt @@ -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 { diff --git a/mirai-core/src/jvmBaseMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/jvmBaseMain/kotlin/utils/PlatformSocket.kt index 1909a9364..d484f97da 100644 --- a/mirai-core/src/jvmBaseMain/kotlin/utils/PlatformSocket.kt +++ b/mirai-core/src/jvmBaseMain/kotlin/utils/PlatformSocket.kt @@ -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() diff --git a/mirai-core/src/jvmTest/kotlin/netinternalkit/NetReplayHelper.kt b/mirai-core/src/jvmTest/kotlin/netinternalkit/NetReplayHelper.kt index 455bb859b..178342a66 100644 --- a/mirai-core/src/jvmTest/kotlin/netinternalkit/NetReplayHelper.kt +++ b/mirai-core/src/jvmTest/kotlin/netinternalkit/NetReplayHelper.kt @@ -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 diff --git a/mirai-core/src/mingwMain/kotlin/package.kt b/mirai-core/src/mingwMain/kotlin/package.kt new file mode 100644 index 000000000..7df5cebc4 --- /dev/null +++ b/mirai-core/src/mingwMain/kotlin/package.kt @@ -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 \ No newline at end of file diff --git a/mirai-core/src/mingwMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/mingwMain/kotlin/utils/PlatformSocket.kt new file mode 100644 index 000000000..203840af5 --- /dev/null +++ b/mirai-core/src/mingwMain/kotlin/utils/PlatformSocket.kt @@ -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) + } + } + +} diff --git a/mirai-core/src/nativeMain/kotlin/MiraiImpl.kt b/mirai-core/src/nativeMain/kotlin/MiraiImpl.kt new file mode 100644 index 000000000..52f44d2e0 --- /dev/null +++ b/mirai-core/src/nativeMain/kotlin/MiraiImpl.kt @@ -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() +} diff --git a/mirai-core/src/nativeMain/kotlin/network/handler/NativeNetworkHandler.kt b/mirai-core/src/nativeMain/kotlin/network/handler/NativeNetworkHandler.kt new file mode 100644 index 000000000..99d0f3124 --- /dev/null +++ b/mirai-core/src/nativeMain/kotlin/network/handler/NativeNetworkHandler.kt @@ -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) + } +} \ No newline at end of file diff --git a/mirai-core/src/nativeMain/kotlin/network/handler/NetworkHandlerFactory.kt b/mirai-core/src/nativeMain/kotlin/network/handler/NetworkHandlerFactory.kt new file mode 100644 index 000000000..4eb675111 --- /dev/null +++ b/mirai-core/src/nativeMain/kotlin/network/handler/NetworkHandlerFactory.kt @@ -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 + } +} \ No newline at end of file diff --git a/mirai-core/src/nativeMain/kotlin/network/handler/SocketAddress.kt b/mirai-core/src/nativeMain/kotlin/network/handler/SocketAddress.kt index 44fb18548..3a8156283 100644 --- a/mirai-core/src/nativeMain/kotlin/network/handler/SocketAddress.kt +++ b/mirai-core/src/nativeMain/kotlin/network/handler/SocketAddress.kt @@ -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") - } - } -} \ No newline at end of file diff --git a/mirai-core/src/nativeMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/nativeMain/kotlin/utils/PlatformSocket.kt index e60e08d15..e32e0fbb9 100644 --- a/mirai-core/src/nativeMain/kotlin/utils/PlatformSocket.kt +++ b/mirai-core/src/nativeMain/kotlin/utils/PlatformSocket.kt @@ -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) -} \ No newline at end of file +} diff --git a/mirai-core/src/nativeTest/kotlin/test/PlatformInitializationTest.kt b/mirai-core/src/nativeTest/kotlin/test/PlatformInitializationTest.kt index 8f967cd69..36b4c7816 100644 --- a/mirai-core/src/nativeTest/kotlin/test/PlatformInitializationTest.kt +++ b/mirai-core/src/nativeTest/kotlin/test/PlatformInitializationTest.kt @@ -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 { diff --git a/mirai-core/src/unixMain/kotlin/package.kt b/mirai-core/src/unixMain/kotlin/package.kt new file mode 100644 index 000000000..7df5cebc4 --- /dev/null +++ b/mirai-core/src/unixMain/kotlin/package.kt @@ -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 \ No newline at end of file diff --git a/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt new file mode 100644 index 000000000..d4f1f2b7a --- /dev/null +++ b/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt @@ -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) + } + } + +}