iOS _nw_parameters_configure_protocol_disable called directly

569 views Asked by At

I'm trying to use a TCP Socket in an iOS Kotlin/Native common module.

According Apple's documentation, to open a nw_connection_t, you simply need to:

val connection = 
    nw_parameters_create_secure_tcp(
        NW_PARAMETERS_DISABLE_PROTOCOL,      // No TLS
        NW_PARAMETERS_DEFAULT_CONFIGURATION  // Default TCP config
    )

However, when I run this module in an iOS Application, I get the following error:

_nw_parameters_configure_protocol_disable_block_invoke _nw_parameters_configure_protocol_disable called directly, dumping backtrace:
[x86_64] libnetcore-1880.120.4
    0   libnetwork.dylib                    0x00007fff5118d1f8 __nw_create_backtrace_string + 120
    1   libnetwork.dylib                    0x00007fff5100a898 _nw_parameters_configure_protocol_disable_block_invoke + 120
    2   PhoenixShared                       0x00000001099af1b5 _70686f656e69783a70686f656e69782d736861726564_knbridge41 + 37
    3   PhoenixShared                       0x0000000109972eac kfun:fr.acinq.phoenix.io.BlockFunctionImpl16.invoke#internal + 220
    4   PhoenixShared                       0x0000000109972fbf kfun:fr.acinq.phoenix.io.BlockFunctionImpl16.$<bridge-UNNN>invoke(platform.darwin.NSObject?){}#internal + 95
    5   PhoenixShared                       0x000000010997342b _70686f656e69783a70686f656e69782d736861726564_knbridge47 + 251
    6   libnetwork.dylib                    0x00007fff5100d7b6 nw_parameters_create_secure_tcp + 342
...

The parameters.h header in Apple's Network.framework contains:

#define NW_PARAMETERS_DISABLE_PROTOCOL (_nw_parameters_configure_protocol_disable)

...so of course _nw_parameters_configure_protocol_disable is called directly.

Any idea of what I'm doing wrong?

1

There are 1 answers

0
Salomon BRYS On

So, it turns out that neither NW_PARAMETERS_DISABLE_PROTOCOL nor NW_PARAMETERS_DEFAULT_CONFIGURATION are meant to be invoked. Even though they are of block type ^(nw_protocol_options_t), the nw_parameters_create_secure_tcp function uses their pointer adresses as special markers and never actually invokes them.

This is a problem because the Kotlin/Native ObjC-interop layer:

  1. Transformed NW_PARAMETERS_DISABLE_PROTOCOL and NW_PARAMETERS_DEFAULT_CONFIGURATION blocks into Kotlin lambdas.
  2. Wraps the lambdas to convert them back into ObjC blocks when passing them as arguments (as we can see in the question's stack trace lines numbered 3 & 4).

As a result, the actual pointer address of these special blocks is lost, the blocks are called (which they should not), and the failure occurs.

There is no way to address this problem in Kotlin, as the Kotlin version of nw_parameters_create_secure_tcp requests lambda parameters (and not pointers).

A very simple workaround is to create our own layer of interoperability using our own C-interop def file:

package = fr.acinq.phoenix.io.network_framework
language = Objective-C

---

#include <Network/Network.h>

NW_RETURNS_RETAINED nw_parameters_t nw_k_parameters_create_secure_tcp(bool withTls) {
    return nw_parameters_create_secure_tcp(
        withTls ? NW_PARAMETERS_DEFAULT_CONFIGURATION : NW_PARAMETERS_DISABLE_PROTOCOL,
        NW_PARAMETERS_DEFAULT_CONFIGURATION
    );
}

This creates an Objective-C nw_k_parameters_create_secure_tcp function (note the nw_k_ prefix) that directly calls the original nw_parameters_create_secure_tcp with the correct parameters, without Kotlin block-to-lambda-to-block layer, which can be correctly called from Kotlin.