diff --git a/.idea/artifacts/library_jvm.xml b/.idea/artifacts/library_jvm.xml index 757db4f6..3c630d1e 100644 --- a/.idea/artifacts/library_jvm.xml +++ b/.idea/artifacts/library_jvm.xml @@ -1,8 +1,6 @@ $PROJECT_DIR$/library/build/libs - - - + \ No newline at end of file diff --git a/.idea/artifacts/samples_multiplatform_kotlin_shared_jvm.xml b/.idea/artifacts/samples_multiplatform_kotlin_shared_jvm.xml index 036e9d10..5f695921 100644 --- a/.idea/artifacts/samples_multiplatform_kotlin_shared_jvm.xml +++ b/.idea/artifacts/samples_multiplatform_kotlin_shared_jvm.xml @@ -1,8 +1,6 @@ $PROJECT_DIR$/samples/multiplatform-kotlin/shared/build/libs - - - + \ No newline at end of file diff --git a/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt b/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt index 6951eb24..585a7322 100644 --- a/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt +++ b/library/src/appleMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt @@ -6,6 +6,7 @@ import kotlinx.cinterop.ByteVar import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.alloc import kotlinx.cinterop.allocArray +import kotlinx.cinterop.convert import kotlinx.cinterop.memScoped import kotlinx.cinterop.pointed import kotlinx.cinterop.ptr @@ -51,8 +52,8 @@ internal actual class HostNameResolver { timeout: Duration, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> { - var ret: Iterable>? = emptySet() + ): Iterable { + var ret: Iterable? = emptySet() try { ret = withTimeoutOrNull(timeout) { invokeInternal(hostName, includeINET, includeINET6) } } finally { @@ -70,7 +71,7 @@ internal actual class HostNameResolver { hostName: String, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> { + ): Iterable { hostReference = CFBridgingRetain(hostName as NSString) cfHost = CFHostCreateWithName(kCFAllocatorDefault, hostReference as CFStringRef) CFHostStartInfoResolution(cfHost, kCFHostAddresses, null) @@ -82,11 +83,11 @@ internal actual class HostNameResolver { addresses.takeIf { hasResolved.value } addresses ?: return emptySet() val count = CFArrayGetCount(addresses) - val ret = mutableSetOf>() + val ret = mutableSetOf() (0 until count).forEach { val socketAddressData = CFArrayGetValueAtIndex(addresses, it) as CFDataRef val sockAddr = CFDataGetBytePtr(socketAddressData)!!.reinterpret().pointed - val addrPrettyToProtocolFamily = when (sockAddr.sa_family.toInt()) { + val addrPretty = when (sockAddr.sa_family.toInt()) { AF_INET -> { if (includeINET) { val buffer = allocArray(INET_ADDRSTRLEN) @@ -94,9 +95,9 @@ internal actual class HostNameResolver { AF_INET, sockAddr.reinterpret().sin_addr.readValue(), buffer, - INET_ADDRSTRLEN.toUInt(), + INET_ADDRSTRLEN.convert(), ) - buffer.toKString() to ProtocolFamily.INET + buffer.toKString() } else { null } @@ -109,9 +110,9 @@ internal actual class HostNameResolver { AF_INET6, sockAddr.reinterpret().sin6_addr.readValue(), buffer, - INET6_ADDRSTRLEN.toUInt(), + INET6_ADDRSTRLEN.convert(), ) - buffer.toKString() to ProtocolFamily.INET6 + buffer.toKString() } else { null } @@ -121,8 +122,8 @@ internal actual class HostNameResolver { null } } - if (addrPrettyToProtocolFamily != null) { - ret.add(addrPrettyToProtocolFamily) + if (addrPretty != null) { + ret.add(addrPretty) } } ret @@ -130,6 +131,7 @@ internal actual class HostNameResolver { } private fun clear() { + cfHost?.let { CFRelease(it) } cfHost = null hostReference?.let { CFRelease(it) } hostReference = null diff --git a/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt b/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt index ce2f74b8..b34f6c76 100644 --- a/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,158 +1,106 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.StableRef -import kotlinx.cinterop.alloc -import kotlinx.cinterop.asStableRef +import kotlinx.cinterop.allocArray +import kotlinx.cinterop.convert import kotlinx.cinterop.memScoped -import kotlinx.cinterop.pointed -import kotlinx.cinterop.ptr import kotlinx.cinterop.refTo import kotlinx.cinterop.reinterpret -import kotlinx.cinterop.sizeOf -import kotlinx.cinterop.staticCFunction -import kotlinx.cinterop.toCPointer -import kotlinx.cinterop.toLong -import kotlinx.cinterop.value -import kotlinx.coroutines.delay +import kotlinx.cinterop.set +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeout -import platform.CoreFoundation.CFDataCreate -import platform.CoreFoundation.CFDataGetBytes -import platform.CoreFoundation.CFDataGetLength -import platform.CoreFoundation.CFDataRefVar -import platform.CoreFoundation.CFRangeMake -import platform.CoreFoundation.CFSocketCallBack -import platform.CoreFoundation.CFSocketConnectToAddress -import platform.CoreFoundation.CFSocketContext -import platform.CoreFoundation.CFSocketCreate -import platform.CoreFoundation.CFSocketInvalidate -import platform.CoreFoundation.CFSocketRef -import platform.CoreFoundation.CFSocketSendData -import platform.CoreFoundation.kCFAllocatorDefault -import platform.CoreFoundation.kCFSocketDataCallBack -import platform.darwin.inet_aton -import platform.darwin.inet_pton -import platform.posix.AF_INET -import platform.posix.AF_INET6 -import platform.posix.IPPROTO_UDP -import platform.posix.PF_INET -import platform.posix.PF_INET6 -import platform.posix.SIGPIPE -import platform.posix.SIG_IGN -import platform.posix.SOCK_DGRAM -import platform.posix.signal -import platform.posix.sockaddr_in -import platform.posix.sockaddr_in6 +import platform.Network.NW_CONNECTION_FINAL_MESSAGE_CONTEXT +import platform.Network.NW_PARAMETERS_DEFAULT_CONFIGURATION +import platform.Network.NW_PARAMETERS_DISABLE_PROTOCOL +import platform.Network.nw_connection_create +import platform.Network.nw_connection_force_cancel +import platform.Network.nw_connection_receive +import platform.Network.nw_connection_send +import platform.Network.nw_connection_set_queue +import platform.Network.nw_connection_set_state_changed_handler +import platform.Network.nw_connection_start +import platform.Network.nw_connection_state_cancelled +import platform.Network.nw_connection_state_failed +import platform.Network.nw_connection_state_ready +import platform.Network.nw_connection_state_t +import platform.Network.nw_connection_t +import platform.Network.nw_endpoint_create_host +import platform.Network.nw_error_t +import platform.Network.nw_parameters_create_secure_udp +import platform.darwin._dispatch_data_destructor_free +import platform.darwin.dispatch_data_apply +import platform.darwin.dispatch_data_create +import platform.darwin.dispatch_data_t +import platform.darwin.dispatch_get_current_queue +import platform.posix.memcpy +import kotlin.test.assertEquals +import kotlin.test.assertNull import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import kotlin.time.DurationUnit -@OptIn(ExperimentalForeignApi::class) @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal actual class NTPUDPSocketOperations { - private var cfSocket: CFSocketRef? = null - private var userDataRef: StableRef? = null + private var connection: nw_connection_t = null - actual suspend fun prepareSocket( - address: String, - protocolFamily: ProtocolFamily, - portNumber: Int, - connectTimeout: Duration, - ) { - userDataRef = StableRef.create(UserData()) - val callback: CFSocketCallBack = staticCFunction { _, callbackType, _, data, info -> - val userData = info!!.asStableRef().get() - if (callbackType != kCFSocketDataCallBack) { - return@staticCFunction - } - val reinterpretedData = data!!.reinterpret().pointed.value - val length = CFDataGetLength(reinterpretedData) - val range = CFRangeMake(0, length) - val bridgeBuffer = UByteArray(length.toInt()) - CFDataGetBytes(reinterpretedData, range, bridgeBuffer.refTo(0)) - userData.apply { - buffer = bridgeBuffer.toByteArray() - exchangeCompleted = true + actual suspend fun prepare(address: String, portNumber: Int, connectTimeout: Duration) { + val parameters = nw_parameters_create_secure_udp( + NW_PARAMETERS_DISABLE_PROTOCOL, + NW_PARAMETERS_DEFAULT_CONFIGURATION + ) + val endpoint = nw_endpoint_create_host(address, portNumber.toString()) + connection = nw_connection_create(endpoint, parameters) + nw_connection_set_queue(connection, dispatch_get_current_queue()) + val connectionStateDeferred = CompletableDeferred() + nw_connection_set_state_changed_handler(connection) { state: nw_connection_state_t, _ -> + when (state) { + nw_connection_state_ready, nw_connection_state_failed, nw_connection_state_cancelled -> + connectionStateDeferred.complete(state) } } - signal(SIGPIPE, SIG_IGN) - cfSocket = memScoped { - val socketContext = alloc { - version = 0 - info = userDataRef!!.asCPointer() - retain = null - release = null - copyDescription = null - } - val socket = CFSocketCreate( - kCFAllocatorDefault, - when (protocolFamily) { - ProtocolFamily.INET -> PF_INET - ProtocolFamily.INET6 -> PF_INET6 - }, - SOCK_DGRAM, - IPPROTO_UDP, - kCFSocketDataCallBack, - callback, - socketContext.ptr, - ) - val addrCFDataRef = when (protocolFamily) { - ProtocolFamily.INET -> alloc { - sin_family = AF_INET.toUByte() - sin_port = portNumber.toUShort() - inet_aton(address, sin_addr.ptr) - }.run { - CFDataCreate(kCFAllocatorDefault, ptr.toLong().toCPointer(), sizeOf()) - } - - ProtocolFamily.INET6 -> { - alloc { - sin6_family = AF_INET6.toUByte() - sin6_port = portNumber.toUShort() - inet_pton(AF_INET6, address, sin6_addr.ptr) - }.run { - CFDataCreate(kCFAllocatorDefault, ptr.toLong().toCPointer(), sizeOf()) - } - } - } - CFSocketConnectToAddress( - socket, - addrCFDataRef, - connectTimeout.toDouble(DurationUnit.MILLISECONDS), - ) - socket + nw_connection_start(connection) + withTimeout(connectTimeout) { + assertEquals(nw_connection_state_ready, connectionStateDeferred.await()) } } - actual suspend fun exchangeInPlace(buffer: ByteArray, readTimeout: Duration) { - val bufferCFDataRef = CFDataCreate( - kCFAllocatorDefault, - buffer.asUByteArray().refTo(0), - buffer.size.toLong(), - ) - CFSocketSendData( - cfSocket, - null, - bufferCFDataRef, - Duration.INFINITE.toDouble(DurationUnit.MILLISECONDS), - ) - withTimeout(readTimeout) { - while (!userDataRef!!.get().exchangeCompleted) { - delay(1.seconds) + @OptIn(ExperimentalForeignApi::class) + actual suspend fun exchange(buffer: ByteArray, readTimeout: Duration) { + val toSendData = memScoped { + val cArray = allocArray(buffer.size) + buffer.forEachIndexed { i, it -> + cArray[i] = it } + cArray + } + nw_connection_send( + connection, + dispatch_data_create(toSendData, buffer.size.convert(), null, _dispatch_data_destructor_free), + NW_CONNECTION_FINAL_MESSAGE_CONTEXT, + true + ) { + assertNull(it) + } + val connectionReceptionDeferred = CompletableDeferred() + nw_connection_receive( + connection, + 1.convert(), + buffer.size.convert() + ) { content: dispatch_data_t, _, _, error: nw_error_t -> + assertNull(error) + connectionReceptionDeferred.complete(content) + } + val receivedData = withTimeout(readTimeout) { + connectionReceptionDeferred.await() + } + dispatch_data_apply(receivedData) { _, _, regionPointer, _ -> + memcpy(buffer.refTo(0), regionPointer!!.reinterpret(), buffer.size.convert()) + false } } - actual fun closeSocket() { - CFSocketInvalidate(cfSocket) - cfSocket = null - userDataRef?.dispose() - userDataRef = null - } - - private class UserData { - var exchangeCompleted = false - var buffer: ByteArray = byteArrayOf() + actual fun tearDown() { + nw_connection_force_cancel(connection) + connection = null } } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt b/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt index 6107da81..ca0b6404 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/SNTPClient.kt @@ -1,9 +1,6 @@ package com.tidal.networktime import com.tidal.networktime.internal.SNTPClientImpl -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import okio.Path.Companion.toPath import kotlin.time.Duration @@ -14,7 +11,6 @@ import kotlin.time.Duration.Companion.seconds * [ntpServers] to obtain information about their provided time. * * @param ntpServers Representation of supported unicast NTP sources. - * @param coroutineScope The scope where synchronization will run on. * @param synchronizationInterval The amount of time to wait between a sync finishing and the next * one being started. * @param backupFilePath A path to a file that will be used to save the selected received NTP @@ -23,17 +19,13 @@ import kotlin.time.Duration.Companion.seconds * packet has been received and processed. If not `null` but writing or reading fail when attempted, * program execution will continue as if it had been `null` until the next attempt. */ -class SNTPClient -@OptIn(DelicateCoroutinesApi::class) -constructor( +class SNTPClient( vararg val ntpServers: NTPServer, - val coroutineScope: CoroutineScope = GlobalScope, val synchronizationInterval: Duration = 64.seconds, val backupFilePath: String? = null, ) { private val delegate = SNTPClientImpl( ntpServers, - coroutineScope, backupFilePath?.toPath(), synchronizationInterval, ) diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt index acec66cf..f798f5b2 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt @@ -1,6 +1,5 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlin.time.Duration @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") @@ -10,5 +9,5 @@ internal expect class HostNameResolver() { timeout: Duration, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> + ): Iterable } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt index f37940be..9173b30a 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPExchanger.kt @@ -1,6 +1,5 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlin.time.Duration internal class NTPExchanger( @@ -10,25 +9,24 @@ internal class NTPExchanger( ) { suspend operator fun invoke( address: String, - protocolFamily: ProtocolFamily, connectTimeout: Duration, queryReadTimeout: Duration, ntpVersion: UByte, ): NTPExchangeResult? { val ntpUdpSocketOperations = NTPUDPSocketOperations() return try { - ntpUdpSocketOperations.prepareSocket(address, protocolFamily, NTP_PORT_NUMBER, connectTimeout) + ntpUdpSocketOperations.prepare(address, NTP_PORT_NUMBER, connectTimeout) val ntpPacket = NTPPacket(versionNumber = ntpVersion.toInt(), mode = NTP_MODE_CLIENT) val requestTime = referenceClock.referenceEpochTime ntpPacket.transmitEpochTimestamp = EpochTimestamp(requestTime).asNTPTimestamp val buffer = ntpPacketSerializer(ntpPacket) - ntpUdpSocketOperations.exchangeInPlace(buffer, queryReadTimeout) + ntpUdpSocketOperations.exchange(buffer, queryReadTimeout) val returnTime = referenceClock.referenceEpochTime ntpPacketDeserializer(buffer)?.let { NTPExchangeResult(returnTime, it) } } catch (_: Throwable) { null } finally { - ntpUdpSocketOperations.closeSocket() + ntpUdpSocketOperations.tearDown() } } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt index d31e8945..49d1df4c 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,18 +1,12 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily import kotlin.time.Duration @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal expect class NTPUDPSocketOperations() { - suspend fun prepareSocket( - address: String, - protocolFamily: ProtocolFamily, - portNumber: Int, - connectTimeout: Duration, - ) + suspend fun prepare(address: String, portNumber: Int, connectTimeout: Duration) - suspend fun exchangeInPlace(buffer: ByteArray, readTimeout: Duration) + suspend fun exchange(buffer: ByteArray, readTimeout: Duration) - fun closeSocket() + fun tearDown() } diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt index 9d31a207..a4f9386c 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SNTPClientImpl.kt @@ -1,15 +1,15 @@ package com.tidal.networktime.internal import com.tidal.networktime.NTPServer -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.IO import okio.Path import kotlin.time.Duration -internal class SNTPClientImpl( +internal class SNTPClientImpl @OptIn(DelicateCoroutinesApi::class) constructor( ntpServers: Array, - coroutineScope: CoroutineScope, backupFilePath: Path?, syncInterval: Duration, private val referenceClock: KotlinXDateTimeSystemClock = KotlinXDateTimeSystemClock(), @@ -23,7 +23,7 @@ internal class SNTPClientImpl( OperationCoordinator( mutableState, synchronizationResultProcessor, - coroutineScope, + GlobalScope, Dispatchers.IO, syncInterval, ntpServers.asIterable(), diff --git a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt index 302672a8..c8b28991 100644 --- a/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt +++ b/library/src/commonMain/kotlin/com/tidal/networktime/internal/SyncSingular.kt @@ -3,9 +3,8 @@ package com.tidal.networktime.internal import com.tidal.networktime.NTPServer import com.tidal.networktime.NTPVersion import com.tidal.networktime.ProtocolFamily -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.async +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @@ -18,7 +17,7 @@ internal class SyncSingular( ) { suspend operator fun invoke() { val selectedResult = ntpServers.map { - withContext(Dispatchers.IO) { + withContext(currentCoroutineContext()) { async { pickNTPPacketWithShortestRoundTrip(it) } } }.flatMap { @@ -45,11 +44,10 @@ internal class SyncSingular( dnsResolutionTimeout, ProtocolFamily.INET in protocolFamilies, ProtocolFamily.INET6 in protocolFamilies, - ).map { (resolvedName, resolvedProtocolFamily) -> + ).map { resolvedName -> (1..queriesPerResolvedAddress).mapNotNull { val ret = ntpExchanger( resolvedName, - resolvedProtocolFamily, queryConnectTimeout, queryReadTimeout, when (ntpVersion) { diff --git a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt index 8c7c8449..4c52465b 100644 --- a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt +++ b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/HostNameResolver.kt @@ -14,7 +14,7 @@ internal actual class HostNameResolver { timeout: Duration, includeINET: Boolean, includeINET6: Boolean, - ): Iterable> = withTimeoutOrNull(timeout) { + ): Iterable = withTimeoutOrNull(timeout) { InetAddress.getAllByName(hostName) }?.mapNotNull { val protocolFamily = when (it) { @@ -24,7 +24,7 @@ internal actual class HostNameResolver { } when { protocolFamily == ProtocolFamily.INET && includeINET || - protocolFamily == ProtocolFamily.INET6 && includeINET6 -> it.hostAddress to protocolFamily + protocolFamily == ProtocolFamily.INET6 && includeINET6 -> it.hostAddress else -> null } diff --git a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt index 9dfcdcfe..8874155f 100644 --- a/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/jvmMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,8 +1,5 @@ package com.tidal.networktime.internal -import com.tidal.networktime.ProtocolFamily -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import java.net.DatagramPacket import java.net.DatagramSocket @@ -10,30 +7,25 @@ import java.net.InetAddress import kotlin.time.Duration import kotlin.time.DurationUnit -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "BlockingMethodInNonBlockingContext") internal actual class NTPUDPSocketOperations { - private lateinit var datagramSocket: DatagramSocket + private var datagramSocket: DatagramSocket? = null - actual suspend fun prepareSocket( - address: String, - protocolFamily: ProtocolFamily, - portNumber: Int, - connectTimeout: Duration, - ) = withTimeout(connectTimeout) { + actual suspend fun prepare(address: String, portNumber: Int, connectTimeout: Duration) { datagramSocket = DatagramSocket() - datagramSocket.connect(InetAddress.getByName(address), portNumber) + withTimeout(connectTimeout) { + datagramSocket!!.connect(InetAddress.getByName(address), portNumber) + } } - actual suspend fun exchangeInPlace(buffer: ByteArray, readTimeout: Duration) { + actual suspend fun exchange(buffer: ByteArray, readTimeout: Duration) { val exchangePacket = DatagramPacket(buffer, buffer.size) - withContext(Dispatchers.IO) { - datagramSocket.send(exchangePacket) - datagramSocket.soTimeout = readTimeout.toInt(DurationUnit.MILLISECONDS) - datagramSocket.receive(exchangePacket) - } + datagramSocket!!.send(exchangePacket) + datagramSocket!!.soTimeout = readTimeout.toInt(DurationUnit.MILLISECONDS) + datagramSocket!!.receive(exchangePacket) } - actual fun closeSocket() { - datagramSocket.close() + actual fun tearDown() { + datagramSocket?.close() } } diff --git a/samples/multiplatform-kotlin/iOS.xcodeproj/project.xcworkspace/xcuserdata/jantonio.xcuserdatad/UserInterfaceState.xcuserstate b/samples/multiplatform-kotlin/iOS.xcodeproj/project.xcworkspace/xcuserdata/jantonio.xcuserdatad/UserInterfaceState.xcuserstate index 1d9413c7..178ec82d 100644 Binary files a/samples/multiplatform-kotlin/iOS.xcodeproj/project.xcworkspace/xcuserdata/jantonio.xcuserdatad/UserInterfaceState.xcuserstate and b/samples/multiplatform-kotlin/iOS.xcodeproj/project.xcworkspace/xcuserdata/jantonio.xcuserdatad/UserInterfaceState.xcuserstate differ