Implement a libsignal-net shadowing web socket.

This commit is contained in:
moiseev-signal
2024-05-06 15:20:11 -07:00
committed by Alex Hart
parent 78bbab37fb
commit 9a0bb243cd
9 changed files with 427 additions and 42 deletions

View File

@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
import org.thoughtcrime.securesms.net.DefaultWebSocketShadowingBridge;
import org.thoughtcrime.securesms.net.SignalWebSocketHealthMonitor;
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
@@ -92,9 +93,11 @@ import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.websocket.LibSignalNetwork;
import org.whispersystems.signalservice.internal.websocket.ShadowingWebSocketConnection;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
import org.whispersystems.signalservice.internal.websocket.LibSignalChatConnection;
import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection;
import org.whispersystems.signalservice.internal.websocket.WebSocketShadowingBridge;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -294,7 +297,8 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
public @NonNull SignalWebSocket provideSignalWebSocket(@NonNull Supplier<SignalServiceConfiguration> signalServiceConfigurationSupplier, @NonNull Supplier<LibSignalNetwork> libSignalNetworkSupplier) {
SleepTimer sleepTimer = !SignalStore.account().isFcmEnabled() || SignalStore.internalValues().isWebsocketModeForced() ? new AlarmSleepTimer(context) : new UptimeSleepTimer() ;
SignalWebSocketHealthMonitor healthMonitor = new SignalWebSocketHealthMonitor(context, sleepTimer);
SignalWebSocket signalWebSocket = new SignalWebSocket(provideWebSocketFactory(signalServiceConfigurationSupplier, healthMonitor, libSignalNetworkSupplier));
WebSocketShadowingBridge bridge = new DefaultWebSocketShadowingBridge(context);
SignalWebSocket signalWebSocket = new SignalWebSocket(provideWebSocketFactory(signalServiceConfigurationSupplier, healthMonitor, libSignalNetworkSupplier, bridge));
healthMonitor.monitor(signalWebSocket);
@@ -401,7 +405,11 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
return provideClientZkOperations(signalServiceConfiguration).getReceiptOperations();
}
@NonNull WebSocketFactory provideWebSocketFactory(@NonNull Supplier<SignalServiceConfiguration> signalServiceConfigurationSupplier, @NonNull SignalWebSocketHealthMonitor healthMonitor, @NonNull Supplier<LibSignalNetwork> libSignalNetworkSupplier) {
@NonNull WebSocketFactory provideWebSocketFactory(@NonNull Supplier<SignalServiceConfiguration> signalServiceConfigurationSupplier,
@NonNull SignalWebSocketHealthMonitor healthMonitor,
@NonNull Supplier<LibSignalNetwork> libSignalNetworkSupplier,
@NonNull WebSocketShadowingBridge bridge)
{
return new WebSocketFactory() {
@Override
public WebSocketConnection createWebSocket() {
@@ -415,6 +423,20 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
@Override
public WebSocketConnection createUnidentifiedWebSocket() {
int shadowPercentage = FeatureFlags.libSignalWebSocketShadowingPercentage();
if (shadowPercentage > 0) {
return new ShadowingWebSocketConnection(
"unauth-shadow",
signalServiceConfigurationSupplier.get(),
Optional.empty(),
BuildConfig.SIGNAL_AGENT,
healthMonitor,
Stories.isFeatureEnabled(),
libSignalNetworkSupplier.get().createChatService(null),
shadowPercentage,
bridge
);
}
if (FeatureFlags.libSignalWebSocketEnabled()) {
LibSignalNetwork network = libSignalNetworkSupplier.get();
return new LibSignalChatConnection(

View File

@@ -32,6 +32,7 @@ public final class InternalValues extends SignalStoreValues {
public static final String LAST_SCROLL_POSITION = "internal.last_scroll_position";
public static final String CONVERSATION_ITEM_V2_MEDIA = "internal.conversation_item_v2_media";
public static final String FORCE_ENTER_RESTORE_V2_FLOW = "internal.force_enter_restore_v2_flow";
public static final String WEB_SOCKET_SHADOWING_STATS = "internal.web_socket_shadowing_stats";
InternalValues(KeyValueStore store) {
super(store);
@@ -219,4 +220,13 @@ public final class InternalValues extends SignalStoreValues {
public boolean enterRestoreV2Flow() {
return FeatureFlags.restoreAfterRegistration() && getBoolean(FORCE_ENTER_RESTORE_V2_FLOW, false);
}
public synchronized void setWebSocketShadowingStats(byte[] bytes) {
putBlob(WEB_SOCKET_SHADOWING_STATS, bytes);
}
public synchronized byte[] getWebSocketShadowingStats(byte[] defaultValue) {
return getBlob(WEB_SOCKET_SHADOWING_STATS, defaultValue);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.net
import android.app.Application
import android.app.Notification
import android.app.PendingIntent
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import org.signal.core.util.PendingIntentFlags
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.InternalValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.internal.websocket.WebSocketShadowingBridge
/**
* Implements a [WebSocketShadowingBridge] to provide shadowing-specific functionality to
* [org.whispersystems.signalservice.internal.websocket.ShadowingWebSocketConnection]
*/
class DefaultWebSocketShadowingBridge(private val context: Application) : WebSocketShadowingBridge {
private val store: InternalValues = SignalStore.internalValues()
override fun writeStatsSnapshot(bytes: ByteArray) {
store.setWebSocketShadowingStats(bytes)
}
override fun readStatsSnapshot(): ByteArray? {
return store.getWebSocketShadowingStats(null)
}
override fun triggerFailureNotification(message: String) {
if (!FeatureFlags.internalUser()) {
return
}
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("[Internal-only] $message")
.setContentText("Tap to send a debug log")
.setContentIntent(
PendingIntent.getActivity(
context,
0,
Intent(context, SubmitDebugLogActivity::class.java),
PendingIntentFlags.mutable()
)
)
.build()
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
}
}

View File

@@ -130,6 +130,7 @@ public final class FeatureFlags {
private static final String REGISTRATION_V2 = "android.registration.v2";
private static final String LIBSIGNAL_WEB_SOCKET_ENABLED = "android.libsignalWebSocketEnabled";
private static final String RESTORE_POST_REGISTRATION = "android.registration.restorePostRegistration";
private static final String LIBSIGNAL_WEB_SOCKET_SHADOW_PCT = "android.libsignalWebSocketShadowingPercentage";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@@ -209,7 +210,8 @@ public final class FeatureFlags {
RX_MESSAGE_SEND,
LINKED_DEVICE_LIFESPAN_SECONDS,
CAMERAX_CUSTOM_CONTROLLER,
LIBSIGNAL_WEB_SOCKET_ENABLED
LIBSIGNAL_WEB_SOCKET_ENABLED,
LIBSIGNAL_WEB_SOCKET_SHADOW_PCT
);
@VisibleForTesting
@@ -755,6 +757,15 @@ public final class FeatureFlags {
return getBoolean(RESTORE_POST_REGISTRATION, false);
}
/**
* Percentage [0, 100] of web socket requests that will be "shadowed" by sending
* an unauthenticated keep-alive via libsignal-net. Default: 0
*/
public static int libSignalWebSocketShadowingPercentage() {
int value = getInteger(LIBSIGNAL_WEB_SOCKET_SHADOW_PCT, 0);
return Math.max(0, Math.min(value, 100));
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);