mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-28 13:48:12 +00:00
committed by
jeffrey-signal
parent
37f67f9717
commit
38bc2b950f
@@ -1,181 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.conscrypt.ConscryptSignal;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public final class SignalProxyUtil {
|
||||
|
||||
private static final String TAG = Log.tag(SignalProxyUtil.class);
|
||||
|
||||
private static final String PROXY_LINK_HOST = "signal.tube";
|
||||
|
||||
private static final Pattern PROXY_LINK_PATTERN = Pattern.compile("^(https|sgnl)://" + PROXY_LINK_HOST + "/#([^:]+).*$");
|
||||
private static final Pattern HOST_PATTERN = Pattern.compile("^([^:]+).*$");
|
||||
|
||||
private SignalProxyUtil() {}
|
||||
|
||||
public static void startListeningToWebsocket() {
|
||||
if (SignalStore.proxy().isProxyEnabled() && AppDependencies.getAuthWebSocket().getState().firstOrError().blockingGet().isFailure()) {
|
||||
Log.w(TAG, "Proxy is in a failed state. Restarting.");
|
||||
AppDependencies.resetNetwork();
|
||||
}
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute(AppDependencies::startNetwork);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all things related to enabling a proxy, including saving it and resetting the relevant
|
||||
* network connections.
|
||||
*/
|
||||
public static void enableProxy(@NonNull SignalProxy proxy) {
|
||||
SignalStore.proxy().enableProxy(proxy);
|
||||
ConscryptSignal.setUseEngineSocketByDefault(true);
|
||||
AppDependencies.resetNetwork();
|
||||
startListeningToWebsocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all things related to disabling a proxy, including saving the change and resetting the
|
||||
* relevant network connections.
|
||||
*/
|
||||
public static void disableProxy() {
|
||||
SignalStore.proxy().disableProxy();
|
||||
ConscryptSignal.setUseEngineSocketByDefault(false);
|
||||
AppDependencies.resetNetwork();
|
||||
startListeningToWebsocket();
|
||||
}
|
||||
|
||||
public static void disableAndClearProxy(){
|
||||
disableProxy();
|
||||
SignalStore.proxy().setProxy(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A blocking call that will wait until the websocket either successfully connects, or fails.
|
||||
* It is assumed that the app state is already configured how you would like it, e.g. you've
|
||||
* already configured a proxy if relevant.
|
||||
*
|
||||
* @return True if the connection is successful within the specified timeout, otherwise false.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static boolean testWebsocketConnection(long timeout) {
|
||||
startListeningToWebsocket();
|
||||
|
||||
if (SignalStore.account().getE164() == null) {
|
||||
Log.i(TAG, "User is unregistered! Doing simple check.");
|
||||
return testWebsocketConnectionUnregistered(timeout);
|
||||
}
|
||||
|
||||
return AppDependencies.getAuthWebSocket()
|
||||
.getState()
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(Schedulers.trampoline())
|
||||
.timeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.skipWhile(state -> state != WebSocketConnectionState.CONNECTED && !state.isFailure())
|
||||
.firstOrError()
|
||||
.flatMap(state -> Single.just(state == WebSocketConnectionState.CONNECTED))
|
||||
.onErrorReturn(t -> false)
|
||||
.blockingGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a valid proxy deep link, this will return the embedded host. If not, it will return
|
||||
* null.
|
||||
*/
|
||||
public static @Nullable String parseHostFromProxyDeepLink(@Nullable String proxyLink) {
|
||||
if (proxyLink == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher matcher = PROXY_LINK_PATTERN.matcher(proxyLink);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(2);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in an address that could be in various formats, and converts it to the format we should
|
||||
* be storing and connecting to.
|
||||
*/
|
||||
public static @NonNull String convertUserEnteredAddressToHost(@NonNull String host) {
|
||||
String parsedHost = SignalProxyUtil.parseHostFromProxyDeepLink(host);
|
||||
if (parsedHost != null) {
|
||||
return parsedHost;
|
||||
}
|
||||
|
||||
Matcher matcher = HOST_PATTERN.matcher(host);
|
||||
|
||||
if (matcher.matches()) {
|
||||
String result = matcher.group(1);
|
||||
return result != null ? result : "";
|
||||
} else {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull String generateProxyUrl(@NonNull String link) {
|
||||
String host = link;
|
||||
String parsed = parseHostFromProxyDeepLink(link);
|
||||
|
||||
if (parsed != null) {
|
||||
host = parsed;
|
||||
}
|
||||
|
||||
Matcher matcher = HOST_PATTERN.matcher(host);
|
||||
|
||||
if (matcher.matches()) {
|
||||
host = matcher.group(1);
|
||||
}
|
||||
|
||||
return "https://" + PROXY_LINK_HOST + "/#" + host;
|
||||
}
|
||||
|
||||
private static boolean testWebsocketConnectionUnregistered(long timeout) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean success = new AtomicBoolean(false);
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.getInstance().createUnauthenticated(AppDependencies.getApplication(), "", SignalServiceAddress.DEFAULT_DEVICE_ID, "");
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
accountManager.checkNetworkConnection();
|
||||
success.set(true);
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Interrupted!", e);
|
||||
}
|
||||
|
||||
return success.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.functions.Function
|
||||
import io.reactivex.rxjava3.functions.Predicate
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.conscrypt.ConscryptSignal
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.Log.tag
|
||||
import org.signal.core.util.logging.Log.w
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalProxy
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object SignalProxyUtil {
|
||||
private val TAG = tag(SignalProxyUtil::class.java)
|
||||
|
||||
private const val PROXY_LINK_HOST = "signal.tube"
|
||||
|
||||
private val PROXY_LINK_PATTERN: Pattern = Pattern.compile("^(https|sgnl)://$PROXY_LINK_HOST/#([^:]+).*$")
|
||||
private val HOST_PATTERN: Pattern = Pattern.compile("^([^:]+).*$")
|
||||
|
||||
@JvmStatic
|
||||
fun startListeningToWebsocket() {
|
||||
if (SignalStore.proxy.isProxyEnabled && AppDependencies.authWebSocket.state.firstOrError().blockingGet().isFailure) {
|
||||
Log.w(TAG, "Proxy is in a failed state. Restarting.")
|
||||
AppDependencies.resetNetwork()
|
||||
}
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute { AppDependencies.startNetwork() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all things related to enabling a proxy, including saving it and resetting the relevant
|
||||
* network connections.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun enableProxy(proxy: SignalProxy) {
|
||||
SignalStore.proxy.enableProxy(proxy)
|
||||
ConscryptSignal.setUseEngineSocketByDefault(true)
|
||||
AppDependencies.resetNetwork()
|
||||
startListeningToWebsocket()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all things related to disabling a proxy, including saving the change and resetting the
|
||||
* relevant network connections.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun disableProxy() {
|
||||
SignalStore.proxy.disableProxy()
|
||||
ConscryptSignal.setUseEngineSocketByDefault(false)
|
||||
AppDependencies.resetNetwork()
|
||||
startListeningToWebsocket()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun disableAndClearProxy() {
|
||||
disableProxy()
|
||||
SignalStore.proxy.proxy = null
|
||||
}
|
||||
|
||||
/**
|
||||
* A blocking call that will wait until the websocket either successfully connects, or fails.
|
||||
* It is assumed that the app state is already configured how you would like it, e.g. you've
|
||||
* already configured a proxy if relevant.
|
||||
*
|
||||
* @return True if the connection is successful within the specified timeout, otherwise false.
|
||||
*/
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun testWebsocketConnection(timeout: Long): Boolean {
|
||||
startListeningToWebsocket()
|
||||
|
||||
if (SignalStore.account.e164 == null) {
|
||||
Log.i(TAG, "User is unregistered! Doing simple check.")
|
||||
return testWebsocketConnectionUnregistered(timeout)
|
||||
}
|
||||
|
||||
return AppDependencies.authWebSocket
|
||||
.state
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(Schedulers.trampoline())
|
||||
.timeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.skipWhile(Predicate { state: WebSocketConnectionState -> state != WebSocketConnectionState.CONNECTED && !state.isFailure })
|
||||
.firstOrError()
|
||||
.flatMap<Boolean>(Function { state: WebSocketConnectionState? -> Single.just(state == WebSocketConnectionState.CONNECTED) })
|
||||
.onErrorReturn(Function { _: Throwable? -> false })
|
||||
.blockingGet()
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a valid proxy deep link, this will return the embedded host. If not, it will return
|
||||
* null.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun parseHostFromProxyDeepLink(proxyLink: String?): String? {
|
||||
if (proxyLink == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val matcher = PROXY_LINK_PATTERN.matcher(proxyLink)
|
||||
|
||||
return when {
|
||||
matcher.matches() -> matcher.group(2)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in an address that could be in various formats, and converts it to the format we should
|
||||
* be storing and connecting to.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun convertUserEnteredAddressToHost(host: String): String {
|
||||
val parsedHost = parseHostFromProxyDeepLink(host)
|
||||
if (parsedHost != null) {
|
||||
return parsedHost
|
||||
}
|
||||
|
||||
val matcher = HOST_PATTERN.matcher(host)
|
||||
|
||||
return when {
|
||||
matcher.matches() -> matcher.group(1) ?: ""
|
||||
else -> host
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun generateProxyUrl(link: String): String {
|
||||
var host: String = link
|
||||
val parsed = parseHostFromProxyDeepLink(link)
|
||||
|
||||
if (parsed != null) {
|
||||
host = parsed
|
||||
}
|
||||
|
||||
val matcher = HOST_PATTERN.matcher(host)
|
||||
|
||||
if (matcher.matches()) {
|
||||
host = matcher.group(1)!!
|
||||
}
|
||||
|
||||
return "https://$PROXY_LINK_HOST/#$host"
|
||||
}
|
||||
|
||||
private fun testWebsocketConnectionUnregistered(timeout: Long): Boolean {
|
||||
val latch = CountDownLatch(1)
|
||||
val success = AtomicBoolean(false)
|
||||
val accountManager = AccountManagerFactory.getInstance()
|
||||
.createUnauthenticated(AppDependencies.application, "", SignalServiceAddress.DEFAULT_DEVICE_ID, "")
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute {
|
||||
try {
|
||||
accountManager.checkNetworkConnection()
|
||||
success.set(true)
|
||||
latch.countDown()
|
||||
} catch (_: IOException) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await(timeout, TimeUnit.MILLISECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
w(TAG, "Interrupted!", e)
|
||||
}
|
||||
|
||||
return success.get()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user