From 163fb9179fa5456659a73f1b1da44746916d1d50 Mon Sep 17 00:00:00 2001 From: Nicola Paola Date: Thu, 30 May 2024 12:08:59 +0200 Subject: [PATCH 1/4] added support for android concurrent connections --- README.md | 7 ++ .../react/tcpsocket/TcpSocketModule.java | 97 ++++++++++++++++--- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 865e35a..1419b1a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ React Native TCP socket API for Android, iOS & macOS with **SSL/TLS support**. I ## Table of Contents +- [Improvements on this forks](#improvements) - [Getting started](#getting-started) - [Overriding `net`](#overriding-net) - [Overriding `tls`](#overriding-tls) @@ -32,6 +33,12 @@ React Native TCP socket API for Android, iOS & macOS with **SSL/TLS support**. I - [Acknowledgments](#acknowledgments) - [License](#license) +## Impovements on this forks +For apps targeting Android 12 (API level 31) or higher, devices that support concurrent peer-to-peer and internet connections can maintain simultaneous Wi-Fi connections to both the peer device and the primary internet-providing network, making the user experience more seamless. +We add support for Android concurrent connections, routing all data traffic to the peer-to-peer network connection selecting the correct network based on the host DHCP address or subnet address. +This change is essential to open a tcp socket and send data towards ioT devices that expose a WiFi network without internet access. +See: [https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections](https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections). + ## Getting started Install the library using either Yarn: diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java index 6d3e7cb..24b6db4 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java @@ -4,6 +4,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; @@ -18,6 +20,9 @@ import com.facebook.react.bridge.ReadableMap; import java.io.IOException; +import java.net.Inet4Address; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -76,7 +81,9 @@ public void run() { // Get the network interface final String localAddress = options.hasKey("localAddress") ? options.getString("localAddress") : null; final String iface = options.hasKey("interface") ? options.getString("interface") : null; - selectNetwork(iface, localAddress); + // Get ioT device host to retreive correct network in android concurrent connections + final String iotDeviceHost = options.hasKey("host") ? options.getString("host") : null; + selectNetwork(iface, localAddress, iotDeviceHost); TcpSocketClient client = new TcpSocketClient(tcpEvtListener, cId, null); socketMap.put(cId, client); ReadableMap tlsOptions = pendingTLS.get(cId); @@ -213,23 +220,83 @@ public void removeListeners(Integer count) { // Keep: Required for RN built in Event Emitter Calls. } - private void requestNetwork(final int transportType) throws InterruptedException { + private void requestNetwork(final int transportType, @Nullable final String iotDeviceHost) throws InterruptedException { final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder(); requestBuilder.addTransportType(transportType); final CountDownLatch awaitingNetwork = new CountDownLatch(1); // only needs to be counted down once to release waiting threads final ConnectivityManager cm = (ConnectivityManager) mReactContext.getSystemService(Context.CONNECTIVITY_SERVICE); - cm.requestNetwork(requestBuilder.build(), new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - currentNetwork.setNetwork(network); - awaitingNetwork.countDown(); // Stop waiting - } - @Override - public void onUnavailable() { + // smartmedev - add support for for concurrent-connections: + // Route all data to the ioT device network interface if exist more than one concurrent network + // See: https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections + if (cm != null) { + // Get all connected networks + Network[] allNetworks = cm.getAllNetworks(); + List wifiNetworks = new ArrayList<>(); + + // Check exist at least one newtwork + if (allNetworks != null && allNetworks.length > 0) { + // Filter for retreive only networks based on selected transport type + for (Network network : allNetworks) { + NetworkCapabilities nc = cm.getNetworkCapabilities(network); + if (nc != null && nc.hasTransport(transportType)) { + wifiNetworks.add(network); + } + } + + // Check exist at least one newtwork based on selected transport type + if (!wifiNetworks.isEmpty()) { + boolean networkFound = false; + for (Network network : wifiNetworks) { + LinkProperties linkProperties = cm.getLinkProperties(network); + // Ensure linkProperties is not null + if (linkProperties == null) + continue; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + Inet4Address foundServerAddress = linkProperties.getDhcpServerAddress(); + if(iotDeviceHost.equals(foundServerAddress.getHostAddress())) { + // found ioT device network + currentNetwork.setNetwork(network); + cm.bindProcessToNetwork(network); + networkFound = true; + awaitingNetwork.countDown(); // Stop waiting + break; + } + } else { + List linkAddressList = linkProperties.getLinkAddresses(); + if(linkAddressList != null && !linkAddressList.isEmpty()) { + for (LinkAddress address : linkAddressList) { + int lastDotIndex = iotDeviceHost.lastIndexOf('.'); + String iotSubnetAddress = iotDeviceHost; + if(lastDotIndex>=0) + iotSubnetAddress = iotDeviceHost.substring(0, lastDotIndex); + if(address.getAddress().getHostAddress().startsWith(iotSubnetAddress)) { + // found ioT device network + currentNetwork.setNetwork(network); + cm.bindProcessToNetwork(network); + networkFound = true; + awaitingNetwork.countDown(); // Stop waiting + break; + } + } + } + } + } + if (!networkFound) { + awaitingNetwork.countDown(); // Stop waiting if no network was found + } + } else { + awaitingNetwork.countDown(); // Stop waiting + } + } else { awaitingNetwork.countDown(); // Stop waiting } - }); + } else { + awaitingNetwork.countDown(); // Stop waiting + } + // smartmedev - end + // Timeout if there the network is unreachable ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1); exec.schedule(new Runnable() { @@ -248,7 +315,7 @@ public void run() { * "cellular" -> Cellular * etc... */ - private void selectNetwork(@Nullable final String iface, @Nullable final String ipAddress) throws InterruptedException, IOException { + private void selectNetwork(@Nullable final String iface, @Nullable final String ipAddress, @Nullable final String iotDeviceHost) throws InterruptedException, IOException { currentNetwork.setNetwork(null); if (iface == null) return; if (ipAddress != null) { @@ -260,13 +327,13 @@ private void selectNetwork(@Nullable final String iface, @Nullable final String } switch (iface) { case "wifi": - requestNetwork(NetworkCapabilities.TRANSPORT_WIFI); + requestNetwork(NetworkCapabilities.TRANSPORT_WIFI, iotDeviceHost); break; case "cellular": - requestNetwork(NetworkCapabilities.TRANSPORT_CELLULAR); + requestNetwork(NetworkCapabilities.TRANSPORT_CELLULAR, iotDeviceHost); break; case "ethernet": - requestNetwork(NetworkCapabilities.TRANSPORT_ETHERNET); + requestNetwork(NetworkCapabilities.TRANSPORT_ETHERNET, iotDeviceHost); break; } if (currentNetwork.getNetwork() == null) { From 366ada2257cab6e346b662e6db624f6acb3449f0 Mon Sep 17 00:00:00 2001 From: Nicola Paola Date: Thu, 30 May 2024 18:51:21 +0200 Subject: [PATCH 2/4] added old behavior if host not specified on configuration array --- .../react/tcpsocket/TcpSocketModule.java | 128 ++++++++++-------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java index 24b6db4..f3aa699 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java @@ -226,65 +226,83 @@ private void requestNetwork(final int transportType, @Nullable final String iotD final CountDownLatch awaitingNetwork = new CountDownLatch(1); // only needs to be counted down once to release waiting threads final ConnectivityManager cm = (ConnectivityManager) mReactContext.getSystemService(Context.CONNECTIVITY_SERVICE); - // smartmedev - add support for for concurrent-connections: - // Route all data to the ioT device network interface if exist more than one concurrent network - // See: https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections - if (cm != null) { - // Get all connected networks - Network[] allNetworks = cm.getAllNetworks(); - List wifiNetworks = new ArrayList<>(); - - // Check exist at least one newtwork - if (allNetworks != null && allNetworks.length > 0) { - // Filter for retreive only networks based on selected transport type - for (Network network : allNetworks) { - NetworkCapabilities nc = cm.getNetworkCapabilities(network); - if (nc != null && nc.hasTransport(transportType)) { - wifiNetworks.add(network); - } + if(iotDeviceHost==null || Objects.equals(iotDeviceHost, "localhost")) { + // Use old behavior if "host" param not specified on configuration array - default value "localhost" used + cm.requestNetwork(requestBuilder.build(), new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + currentNetwork.setNetwork(network); + awaitingNetwork.countDown(); // Stop waiting } - // Check exist at least one newtwork based on selected transport type - if (!wifiNetworks.isEmpty()) { - boolean networkFound = false; - for (Network network : wifiNetworks) { - LinkProperties linkProperties = cm.getLinkProperties(network); - // Ensure linkProperties is not null - if (linkProperties == null) - continue; - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { - Inet4Address foundServerAddress = linkProperties.getDhcpServerAddress(); - if(iotDeviceHost.equals(foundServerAddress.getHostAddress())) { - // found ioT device network - currentNetwork.setNetwork(network); - cm.bindProcessToNetwork(network); - networkFound = true; - awaitingNetwork.countDown(); // Stop waiting - break; - } - } else { - List linkAddressList = linkProperties.getLinkAddresses(); - if(linkAddressList != null && !linkAddressList.isEmpty()) { - for (LinkAddress address : linkAddressList) { - int lastDotIndex = iotDeviceHost.lastIndexOf('.'); - String iotSubnetAddress = iotDeviceHost; - if(lastDotIndex>=0) - iotSubnetAddress = iotDeviceHost.substring(0, lastDotIndex); - if(address.getAddress().getHostAddress().startsWith(iotSubnetAddress)) { - // found ioT device network - currentNetwork.setNetwork(network); - cm.bindProcessToNetwork(network); - networkFound = true; - awaitingNetwork.countDown(); // Stop waiting - break; + @Override + public void onUnavailable() { + awaitingNetwork.countDown(); // Stop waiting + } + }); + } else { + // smartmedev - add support for for concurrent-connections: + // Route all data to the ioT device network interface if exist more than one concurrent network + // See: https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections + if (cm != null) { + // Get all connected networks + Network[] allNetworks = cm.getAllNetworks(); + List wifiNetworks = new ArrayList<>(); + + // Check exist at least one newtwork + if (allNetworks != null && allNetworks.length > 0) { + // Filter for retreive only networks based on selected transport type + for (Network network : allNetworks) { + NetworkCapabilities nc = cm.getNetworkCapabilities(network); + if (nc != null && nc.hasTransport(transportType)) { + wifiNetworks.add(network); + } + } + + // Check exist at least one newtwork based on selected transport type + if (!wifiNetworks.isEmpty()) { + boolean networkFound = false; + for (Network network : wifiNetworks) { + LinkProperties linkProperties = cm.getLinkProperties(network); + // Ensure linkProperties is not null + if (linkProperties == null) + continue; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + Inet4Address foundServerAddress = linkProperties.getDhcpServerAddress(); + if(iotDeviceHost.equals(foundServerAddress.getHostAddress())) { + // found ioT device network + currentNetwork.setNetwork(network); + cm.bindProcessToNetwork(network); + networkFound = true; + awaitingNetwork.countDown(); // Stop waiting + break; + } + } else { + List linkAddressList = linkProperties.getLinkAddresses(); + if(linkAddressList != null && !linkAddressList.isEmpty()) { + for (LinkAddress address : linkAddressList) { + int lastDotIndex = iotDeviceHost.lastIndexOf('.'); + String iotSubnetAddress = iotDeviceHost; + if(lastDotIndex>=0) + iotSubnetAddress = iotDeviceHost.substring(0, lastDotIndex); + if(address.getAddress().getHostAddress().startsWith(iotSubnetAddress)) { + // found ioT device network + currentNetwork.setNetwork(network); + cm.bindProcessToNetwork(network); + networkFound = true; + awaitingNetwork.countDown(); // Stop waiting + break; + } } } } } - } - if (!networkFound) { - awaitingNetwork.countDown(); // Stop waiting if no network was found + if (!networkFound) { + awaitingNetwork.countDown(); // Stop waiting if no network was found + } + } else { + awaitingNetwork.countDown(); // Stop waiting } } else { awaitingNetwork.countDown(); // Stop waiting @@ -292,10 +310,8 @@ private void requestNetwork(final int transportType, @Nullable final String iotD } else { awaitingNetwork.countDown(); // Stop waiting } - } else { - awaitingNetwork.countDown(); // Stop waiting + // smartmedev - end } - // smartmedev - end // Timeout if there the network is unreachable ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1); From 05a12946fdc124d561e090da6e8df3a1760f367a Mon Sep 17 00:00:00 2001 From: Nicola Paola Date: Fri, 31 May 2024 12:09:12 +0200 Subject: [PATCH 3/4] some fixes on imports to library works --- android/src/main/AndroidManifest.xml | 1 + .../main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java | 1 + 2 files changed, 2 insertions(+) diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index d414bb7..f5ce4b2 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java index f3aa699..3944542 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java @@ -23,6 +23,7 @@ import java.net.Inet4Address; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; From e559d4c724008fd682e08d55098eed4fd280ed51 Mon Sep 17 00:00:00 2001 From: Nicola Paola Date: Thu, 20 Jun 2024 11:55:09 +0200 Subject: [PATCH 4/4] remove edits on readme.md --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 1419b1a..865e35a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ React Native TCP socket API for Android, iOS & macOS with **SSL/TLS support**. I ## Table of Contents -- [Improvements on this forks](#improvements) - [Getting started](#getting-started) - [Overriding `net`](#overriding-net) - [Overriding `tls`](#overriding-tls) @@ -33,12 +32,6 @@ React Native TCP socket API for Android, iOS & macOS with **SSL/TLS support**. I - [Acknowledgments](#acknowledgments) - [License](#license) -## Impovements on this forks -For apps targeting Android 12 (API level 31) or higher, devices that support concurrent peer-to-peer and internet connections can maintain simultaneous Wi-Fi connections to both the peer device and the primary internet-providing network, making the user experience more seamless. -We add support for Android concurrent connections, routing all data traffic to the peer-to-peer network connection selecting the correct network based on the host DHCP address or subnet address. -This change is essential to open a tcp socket and send data towards ioT devices that expose a WiFi network without internet access. -See: [https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections](https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections). - ## Getting started Install the library using either Yarn: