From a0123b3c1d017b4d60c3eeec9d560cd106fe1ec9 Mon Sep 17 00:00:00 2001 From: Jorge Antonio Diaz-Benito Soriano Date: Fri, 29 Mar 2024 15:50:32 +0100 Subject: [PATCH] Add workaround for Network Framework param misinterpretation bug --- gradle.properties | 1 + library/build.gradle.kts | 1 + .../internal/NTPUDPSocketOperations.kt | 69 +++++++++++-------- .../cinterop/NetworkFrameworkWorkaround.def | 31 +++++++++ 4 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 library/src/nativeInterop/cinterop/NetworkFrameworkWorkaround.def diff --git a/gradle.properties b/gradle.properties index fab5384d..78cb8b27 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,4 @@ android.useAndroidX=true org.gradle.jvmargs=-Xmx16g org.jetbrains.compose.experimental.uikit.enabled=true org.jetbrains.compose.experimental.macos.enabled=true +kotlin.mpp.enableCInteropCommonization=true diff --git a/library/build.gradle.kts b/library/build.gradle.kts index fd0b4a11..1662278c 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { binaryOption("bundleId", "com.tidal.networktime") isStatic = true } + it.compilations.configureEach { cinterops.create("NetworkFrameworkWorkaround") } } applyDefaultHierarchyTemplate() 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 7e204723..e58d8e1e 100644 --- a/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt +++ b/library/src/appleMain/kotlin/com/tidal/networktime/internal/NTPUDPSocketOperations.kt @@ -1,22 +1,17 @@ package com.tidal.networktime.internal -import kotlinx.cinterop.ByteVar +import com.tidal.networktime.internal.network_framework_workaround.nw_connection_send_with_default_context +import com.tidal.networktime.internal.network_framework_workaround.nw_parameters_create_secure_udp_workaround import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.allocArray +import kotlinx.cinterop.addressOf import kotlinx.cinterop.convert -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.refTo -import kotlinx.cinterop.reinterpret -import kotlinx.cinterop.set +import kotlinx.cinterop.pin +import kotlinx.cinterop.usePinned import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeout -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 @@ -27,8 +22,6 @@ 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 @@ -38,67 +31,85 @@ import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.time.Duration +@OptIn(ExperimentalForeignApi::class) @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal actual class NTPUDPSocketOperations { private var connection: nw_connection_t = null 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, - ) + println("BEFORE CREATESECUREUDP") + val parameters = nw_parameters_create_secure_udp_workaround() + println("BEFORE CREATEHOST") + println("Endpoint is $address/$portNumber") val endpoint = nw_endpoint_create_host(address, portNumber.toString()) + println("BEFORE CONNCREATE") connection = nw_connection_create(endpoint, parameters) + println("BEFORE SETQUEUE") nw_connection_set_queue(connection, dispatch_get_current_queue()) val connectionStateDeferred = CompletableDeferred() + println("BEFORE SETHANDLER") nw_connection_set_state_changed_handler(connection) { state: nw_connection_state_t, _ -> + println("State is $state") when (state) { nw_connection_state_ready, nw_connection_state_failed, nw_connection_state_cancelled -> connectionStateDeferred.complete(state) } } + println("Print test") + println("BEFORE START") nw_connection_start(connection) + println("BEFORE WITHTIMEOUT") withTimeout(connectTimeout) { assertEquals(nw_connection_state_ready, connectionStateDeferred.await()) } + println("AFTER WITHTIMEOUT") } - @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 + println("EXCHANGE") + val data = buffer.pin().run { + dispatch_data_create( + addressOf(0), + buffer.size.convert(), + dispatch_get_current_queue(), + ({ unpin() }), + ) } - nw_connection_send( + println("BEFORE SEND") + nw_connection_send_with_default_context( connection, - dispatch_data_create(toSendData, buffer.size.convert(), null, _dispatch_data_destructor_free), - NW_CONNECTION_FINAL_MESSAGE_CONTEXT, + data, true, ) { + println("SEND CB, ERROR IS $it") assertNull(it) } val connectionReceptionDeferred = CompletableDeferred() + println("BEFORE RECEIVE") nw_connection_receive( connection, 1.convert(), buffer.size.convert(), ) { content: dispatch_data_t, _, _, error: nw_error_t -> + println("RECEIVE CB, ERROR IS $error") assertNull(error) connectionReceptionDeferred.complete(content) } + println("BEFORE RECEIVE TIMEOUT") val receivedData = withTimeout(readTimeout) { connectionReceptionDeferred.await() } - dispatch_data_apply(receivedData) { _, _, regionPointer, _ -> - memcpy(buffer.refTo(0), regionPointer!!.reinterpret(), buffer.size.convert()) - false + println("BEFORE USEPINNED FOR RECEIVED") + buffer.usePinned { + dispatch_data_apply(receivedData) { _, offset, src, size -> + memcpy(it.addressOf(offset.toInt()), src, size) + true + } } } actual fun tearDown() { + println("TEARDOWN") nw_connection_force_cancel(connection) connection = null } diff --git a/library/src/nativeInterop/cinterop/NetworkFrameworkWorkaround.def b/library/src/nativeInterop/cinterop/NetworkFrameworkWorkaround.def new file mode 100644 index 00000000..f2e1de5c --- /dev/null +++ b/library/src/nativeInterop/cinterop/NetworkFrameworkWorkaround.def @@ -0,0 +1,31 @@ +package = com.tidal.networktime.internal.network_framework_workaround +language = Objective-C + +--- + +#include +#import + +// https://stackoverflow.com/a/63050804 +NW_RETURNS_RETAINED nw_parameters_t nw_parameters_create_secure_udp_workaround() { + return nw_parameters_create_secure_udp( + NW_PARAMETERS_DISABLE_PROTOCOL, + NW_PARAMETERS_DEFAULT_CONFIGURATION + ); +} + +// https://youtrack.jetbrains.com/issue/KT-62102/ +void nw_connection_send_with_default_context( + nw_connection_t connection, + _Nullable dispatch_data_t content, + bool is_complete, + nw_connection_send_completion_t completion +) { + nw_connection_send( + connection, + content, + NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, + is_complete, + completion + ); +}