Skip to content

Commit

Permalink
Add workaround for Network Framework param misinterpretation bug
Browse files Browse the repository at this point in the history
  • Loading branch information
stoyicker committed Mar 29, 2024
1 parent 3a1098b commit 72bcc5a
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 28 deletions.
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ kotlin {
binaryOption("bundleId", "com.tidal.networktime")
isStatic = true
}
it.compilations.configureEach { cinterops.create("NetworkFrameworkWorkaround") }
}

applyDefaultHierarchyTemplate()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -42,63 +35,82 @@ import kotlin.time.Duration
internal actual class NTPUDPSocketOperations {
private var connection: nw_connection_t = null

@OptIn(ExperimentalForeignApi::class)
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<nw_connection_state_t>()
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<ByteVar>(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<dispatch_data_t>()
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<ByteVar>(), 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
}
Expand Down
31 changes: 31 additions & 0 deletions library/src/nativeInterop/cinterop/NetworkFrameworkWorkaround.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package = com.tidal.networktime.internal.network_framework_workaround
language = Objective-C

---

#include <Network/Network.h>
#import <Network/connection.h>

// 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
);
}

0 comments on commit 72bcc5a

Please sign in to comment.