Added logging to the websocket connections

This commit is contained in:
Marvin 2025-09-24 18:29:08 +03:00
parent f6d81e3c05
commit b571b08660
2 changed files with 411 additions and 147 deletions

View File

@ -1,53 +1,129 @@
package org.asamk.signal.manager.config;
import org.asamk.signal.manager.api.ServiceEnvironment;
import org.signal.libsignal.protocol.util.Medium;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import org.asamk.signal.manager.api.ServiceEnvironment;
import org.signal.libsignal.protocol.util.Medium;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.account.AccountAttributes;
public class ServiceConfig {
private static final Logger logger = LoggerFactory.getLogger(
ServiceConfig.class
);
public static final int PREKEY_MINIMUM_COUNT = 10;
public static final int PREKEY_BATCH_SIZE = 100;
public static final int PREKEY_MAXIMUM_ID = Medium.MAX_VALUE;
public static final long PREKEY_ARCHIVE_AGE = TimeUnit.DAYS.toMillis(30);
public static final long PREKEY_STALE_AGE = TimeUnit.DAYS.toMillis(90);
public static final long SIGNED_PREKEY_ROTATE_AGE = TimeUnit.DAYS.toMillis(2);
public static final long SIGNED_PREKEY_ROTATE_AGE = TimeUnit.DAYS.toMillis(
2
);
public static final int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
public static final long MAX_ENVELOPE_SIZE = 0;
public static final int MAX_MESSAGE_SIZE_BYTES = 2000;
public static final long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024;
public static final long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE =
10 * 1024 * 1024;
public static final boolean AUTOMATIC_NETWORK_RETRY = true;
public static final int GROUP_MAX_SIZE = 1001;
public static final int MAXIMUM_ONE_OFF_REQUEST_SIZE = 3;
public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30);
public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
public static AccountAttributes.Capabilities getCapabilities(
boolean isPrimaryDevice
) {
final var deleteSync = !isPrimaryDevice;
final var storageEncryptionV2 = !isPrimaryDevice;
final var attachmentBackfill = !isPrimaryDevice;
return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2, attachmentBackfill);
return new AccountAttributes.Capabilities(
true,
deleteSync,
true,
storageEncryptionV2,
attachmentBackfill
);
}
public static ServiceEnvironmentConfig getServiceEnvironmentConfig(
ServiceEnvironment serviceEnvironment,
String userAgent
ServiceEnvironment serviceEnvironment,
String userAgent
) {
final Interceptor userAgentInterceptor = chain -> chain.proceed(chain.request()
.newBuilder()
.header("User-Agent", userAgent)
.build());
final Interceptor userAgentInterceptor = chain ->
chain.proceed(
chain
.request()
.newBuilder()
.header("User-Agent", userAgent)
.build()
);
final var interceptors = List.of(userAgentInterceptor);
final Interceptor webSocketLoggingInterceptor = chain -> {
var request = chain.request();
var response = chain.proceed(request);
// Check if this is a WebSocket connection attempt by URL pattern or headers
String url = request.url().toString();
boolean isWebSocketRequest =
url.contains("/v1/websocket") ||
"websocket".equalsIgnoreCase(request.header("Upgrade")) ||
"Upgrade".equalsIgnoreCase(request.header("Connection"));
if (isWebSocketRequest) {
logger.info(
"=== WebSocket Connection HTTP Response ===\nURL: {}\nMethod: {}\nStatus: {} {}\nRequest Headers: {}\nResponse Headers: {}",
request.url(),
request.method(),
response.code(),
response.message(),
request.headers(),
response.headers()
);
// Log specific WebSocket-related headers
var upgradeHeader = response.header("Upgrade");
var connectionHeader = response.header("Connection");
var acceptHeader = response.header("Sec-WebSocket-Accept");
var protocolHeader = response.header("Sec-WebSocket-Protocol");
logger.info(
"WebSocket Upgrade Details:\n Upgrade: {}\n Connection: {}\n Sec-WebSocket-Accept: {}\n Sec-WebSocket-Protocol: {}",
upgradeHeader,
connectionHeader,
acceptHeader,
protocolHeader
);
// Log success or failure
if (response.code() == 101) {
logger.info("✅ WebSocket upgrade successful!");
} else {
logger.warn(
"❌ WebSocket upgrade failed with status: {} {}",
response.code(),
response.message()
);
}
logger.info("=== End WebSocket Connection Log ===");
}
return response;
};
final var interceptors = List.of(
userAgentInterceptor,
webSocketLoggingInterceptor
);
return switch (serviceEnvironment) {
case LIVE -> LiveConfig.getServiceEnvironmentConfig(interceptors);
case STAGING -> StagingConfig.getServiceEnvironmentConfig(interceptors);
case STAGING -> StagingConfig.getServiceEnvironmentConfig(
interceptors
);
};
}
}

View File

@ -1,5 +1,13 @@
package org.asamk.signal.manager.internal;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.util.Utils;
@ -41,18 +49,11 @@ import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class SignalDependencies {
private static final Logger logger = LoggerFactory.getLogger(SignalDependencies.class);
private static final Logger logger = LoggerFactory.getLogger(
SignalDependencies.class
);
private final Object LOCK = new Object();
@ -93,12 +94,12 @@ public class SignalDependencies {
private ProfileApi profileApi;
SignalDependencies(
final ServiceEnvironmentConfig serviceEnvironmentConfig,
final String userAgent,
final CredentialsProvider credentialsProvider,
final SignalServiceDataStore dataStore,
final ExecutorService executor,
final SignalSessionLock sessionLock
final ServiceEnvironmentConfig serviceEnvironmentConfig,
final String userAgent,
final CredentialsProvider credentialsProvider,
final SignalServiceDataStore dataStore,
final ExecutorService executor,
final SignalSessionLock sessionLock
) {
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent;
@ -144,36 +145,58 @@ public class SignalDependencies {
}
public PushServiceSocket getPushServiceSocket() {
return getOrCreate(() -> pushServiceSocket,
() -> pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(),
credentialsProvider,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY));
return getOrCreate(
() -> pushServiceSocket,
() ->
pushServiceSocket = new PushServiceSocket(
serviceEnvironmentConfig.signalServiceConfiguration(),
credentialsProvider,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY
)
);
}
public Network getLibSignalNetwork() {
return getOrCreate(() -> libSignalNetwork, () -> {
libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent);
setSignalNetworkProxy(libSignalNetwork);
});
return getOrCreate(
() -> libSignalNetwork,
() -> {
libSignalNetwork = new Network(
serviceEnvironmentConfig.netEnvironment(),
userAgent
);
setSignalNetworkProxy(libSignalNetwork);
}
);
}
private void setSignalNetworkProxy(Network libSignalNetwork) {
final var proxy = Utils.getHttpsProxy();
if (proxy.address() instanceof InetSocketAddress addr) {
switch (proxy.type()) {
case Proxy.Type.DIRECT -> {
}
case Proxy.Type.DIRECT -> {}
case Proxy.Type.HTTP -> {
try {
libSignalNetwork.setProxy("http", addr.getHostName(), addr.getPort(), null, null);
libSignalNetwork.setProxy(
"http",
addr.getHostName(),
addr.getPort(),
null,
null
);
} catch (IOException e) {
logger.warn("Failed to set http proxy", e);
}
}
case Proxy.Type.SOCKS -> {
try {
libSignalNetwork.setProxy("socks", addr.getHostName(), addr.getPort(), null, null);
libSignalNetwork.setProxy(
"socks",
addr.getHostName(),
addr.getPort(),
null,
null
);
} catch (IOException e) {
logger.warn("Failed to set socks proxy", e);
}
@ -183,59 +206,102 @@ public class SignalDependencies {
}
public SignalServiceAccountManager getAccountManager() {
return getOrCreate(() -> accountManager,
() -> accountManager = new SignalServiceAccountManager(getAuthenticatedSignalWebSocket(),
getAccountApi(),
getPushServiceSocket(),
getGroupsV2Operations()));
return getOrCreate(
() -> accountManager,
() ->
accountManager = new SignalServiceAccountManager(
getAuthenticatedSignalWebSocket(),
getAccountApi(),
getPushServiceSocket(),
getGroupsV2Operations()
)
);
}
public SignalServiceAccountManager createUnauthenticatedAccountManager(String number, String password) {
return SignalServiceAccountManager.createWithStaticCredentials(getServiceEnvironmentConfig().signalServiceConfiguration(),
null,
null,
number,
SignalServiceAddress.DEFAULT_DEVICE_ID,
password,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY,
ServiceConfig.GROUP_MAX_SIZE);
public SignalServiceAccountManager createUnauthenticatedAccountManager(
String number,
String password
) {
return SignalServiceAccountManager.createWithStaticCredentials(
getServiceEnvironmentConfig().signalServiceConfiguration(),
null,
null,
number,
SignalServiceAddress.DEFAULT_DEVICE_ID,
password,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY,
ServiceConfig.GROUP_MAX_SIZE
);
}
public AccountApi getAccountApi() {
return getOrCreate(() -> accountApi, () -> accountApi = new AccountApi(getAuthenticatedSignalWebSocket()));
return getOrCreate(
() -> accountApi,
() -> accountApi = new AccountApi(getAuthenticatedSignalWebSocket())
);
}
public RateLimitChallengeApi getRateLimitChallengeApi() {
return getOrCreate(() -> rateLimitChallengeApi,
() -> rateLimitChallengeApi = new RateLimitChallengeApi(getAuthenticatedSignalWebSocket()));
return getOrCreate(
() -> rateLimitChallengeApi,
() ->
rateLimitChallengeApi = new RateLimitChallengeApi(
getAuthenticatedSignalWebSocket()
)
);
}
public CdsApi getCdsApi() {
return getOrCreate(() -> cdsApi, () -> cdsApi = new CdsApi(getAuthenticatedSignalWebSocket()));
return getOrCreate(
() -> cdsApi,
() -> cdsApi = new CdsApi(getAuthenticatedSignalWebSocket())
);
}
public UsernameApi getUsernameApi() {
return getOrCreate(() -> usernameApi, () -> usernameApi = new UsernameApi(getUnauthenticatedSignalWebSocket()));
return getOrCreate(
() -> usernameApi,
() ->
usernameApi = new UsernameApi(
getUnauthenticatedSignalWebSocket()
)
);
}
public GroupsV2Api getGroupsV2Api() {
return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api());
return getOrCreate(
() -> groupsV2Api,
() -> groupsV2Api = getAccountManager().getGroupsV2Api()
);
}
public RegistrationApi getRegistrationApi() {
return getOrCreate(() -> registrationApi, () -> registrationApi = getAccountManager().getRegistrationApi());
return getOrCreate(
() -> registrationApi,
() -> registrationApi = getAccountManager().getRegistrationApi()
);
}
public LinkDeviceApi getLinkDeviceApi() {
return getOrCreate(() -> linkDeviceApi,
() -> linkDeviceApi = new LinkDeviceApi(getAuthenticatedSignalWebSocket()));
return getOrCreate(
() -> linkDeviceApi,
() ->
linkDeviceApi = new LinkDeviceApi(
getAuthenticatedSignalWebSocket()
)
);
}
private StorageServiceApi getStorageServiceApi() {
return getOrCreate(() -> storageServiceApi,
() -> storageServiceApi = new StorageServiceApi(getAuthenticatedSignalWebSocket(),
getPushServiceSocket()));
return getOrCreate(
() -> storageServiceApi,
() ->
storageServiceApi = new StorageServiceApi(
getAuthenticatedSignalWebSocket(),
getPushServiceSocket()
)
);
}
public StorageServiceRepository getStorageServiceRepository() {
@ -243,35 +309,69 @@ public class SignalDependencies {
}
public CertificateApi getCertificateApi() {
return getOrCreate(() -> certificateApi,
() -> certificateApi = new CertificateApi(getAuthenticatedSignalWebSocket()));
return getOrCreate(
() -> certificateApi,
() ->
certificateApi = new CertificateApi(
getAuthenticatedSignalWebSocket()
)
);
}
public AttachmentApi getAttachmentApi() {
return getOrCreate(() -> attachmentApi,
() -> attachmentApi = new AttachmentApi(getAuthenticatedSignalWebSocket(), getPushServiceSocket()));
return getOrCreate(
() -> attachmentApi,
() ->
attachmentApi = new AttachmentApi(
getAuthenticatedSignalWebSocket(),
getPushServiceSocket()
)
);
}
public MessageApi getMessageApi() {
return getOrCreate(() -> messageApi,
() -> messageApi = new MessageApi(getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket()));
return getOrCreate(
() -> messageApi,
() ->
messageApi = new MessageApi(
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket()
)
);
}
public KeysApi getKeysApi() {
return getOrCreate(() -> keysApi,
() -> keysApi = new KeysApi(getAuthenticatedSignalWebSocket(), getUnauthenticatedSignalWebSocket()));
return getOrCreate(
() -> keysApi,
() ->
keysApi = new KeysApi(
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket()
)
);
}
public GroupsV2Operations getGroupsV2Operations() {
return getOrCreate(() -> groupsV2Operations,
() -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()),
ServiceConfig.GROUP_MAX_SIZE));
return getOrCreate(
() -> groupsV2Operations,
() ->
groupsV2Operations = new GroupsV2Operations(
ClientZkOperations.create(
serviceEnvironmentConfig.signalServiceConfiguration()
),
ServiceConfig.GROUP_MAX_SIZE
)
);
}
private ClientZkOperations getClientZkOperations() {
return getOrCreate(() -> clientZkOperations,
() -> clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()));
return getOrCreate(
() -> clientZkOperations,
() ->
clientZkOperations = ClientZkOperations.create(
serviceEnvironmentConfig.signalServiceConfiguration()
)
);
}
private ClientZkProfileOperations getClientZkProfileOperations() {
@ -280,89 +380,178 @@ public class SignalDependencies {
}
public SignalWebSocket.AuthenticatedWebSocket getAuthenticatedSignalWebSocket() {
return getOrCreate(() -> authenticatedSignalWebSocket, () -> {
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
return getOrCreate(
() -> authenticatedSignalWebSocket,
() -> {
logger.info(
"Creating new authenticated WebSocket connection to Signal server..."
);
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(
timer
);
authenticatedSignalWebSocket = new SignalWebSocket.AuthenticatedWebSocket(() -> new OkHttpWebSocketConnection(
"normal",
serviceEnvironmentConfig.signalServiceConfiguration(),
Optional.of(credentialsProvider),
userAgent,
healthMonitor,
allowStories), () -> true, timer, TimeUnit.SECONDS.toMillis(10));
healthMonitor.monitor(authenticatedSignalWebSocket);
});
authenticatedSignalWebSocket =
new SignalWebSocket.AuthenticatedWebSocket(
() -> {
logger.info(
"Establishing authenticated WebSocket connection - URL: {}",
serviceEnvironmentConfig
.signalServiceConfiguration()
.getSignalServiceUrls()[0].getUrl()
);
return new OkHttpWebSocketConnection(
"normal",
serviceEnvironmentConfig.signalServiceConfiguration(),
Optional.of(credentialsProvider),
userAgent,
healthMonitor,
allowStories
);
},
() -> true,
timer,
TimeUnit.SECONDS.toMillis(10)
);
healthMonitor.monitor(authenticatedSignalWebSocket);
logger.info(
"Authenticated WebSocket connection created and health monitoring started"
);
}
);
}
public SignalWebSocket.UnauthenticatedWebSocket getUnauthenticatedSignalWebSocket() {
return getOrCreate(() -> unauthenticatedSignalWebSocket, () -> {
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
return getOrCreate(
() -> unauthenticatedSignalWebSocket,
() -> {
logger.info(
"Creating new unauthenticated WebSocket connection to Signal server..."
);
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(
timer
);
unauthenticatedSignalWebSocket = new SignalWebSocket.UnauthenticatedWebSocket(() -> new OkHttpWebSocketConnection(
"unidentified",
serviceEnvironmentConfig.signalServiceConfiguration(),
Optional.empty(),
userAgent,
healthMonitor,
allowStories), () -> true, timer, TimeUnit.SECONDS.toMillis(10));
healthMonitor.monitor(unauthenticatedSignalWebSocket);
});
unauthenticatedSignalWebSocket =
new SignalWebSocket.UnauthenticatedWebSocket(
() -> {
logger.info(
"Establishing unauthenticated WebSocket connection - URL: {}",
serviceEnvironmentConfig
.signalServiceConfiguration()
.getSignalServiceUrls()[0].getUrl()
);
return new OkHttpWebSocketConnection(
"unidentified",
serviceEnvironmentConfig.signalServiceConfiguration(),
Optional.empty(),
userAgent,
healthMonitor,
allowStories
);
},
() -> true,
timer,
TimeUnit.SECONDS.toMillis(10)
);
healthMonitor.monitor(unauthenticatedSignalWebSocket);
logger.info(
"Unauthenticated WebSocket connection created and health monitoring started"
);
}
);
}
public SignalServiceMessageReceiver getMessageReceiver() {
return getOrCreate(() -> messageReceiver,
() -> messageReceiver = new SignalServiceMessageReceiver(getPushServiceSocket()));
return getOrCreate(
() -> messageReceiver,
() ->
messageReceiver = new SignalServiceMessageReceiver(
getPushServiceSocket()
)
);
}
public SignalServiceMessageSender getMessageSender() {
return getOrCreate(() -> messageSender,
() -> messageSender = new SignalServiceMessageSender(getPushServiceSocket(),
dataStore,
sessionLock,
getAttachmentApi(),
getMessageApi(),
getKeysApi(),
Optional.empty(),
executor,
ServiceConfig.MAX_ENVELOPE_SIZE,
() -> true,
UsePqRatchet.NO));
return getOrCreate(
() -> messageSender,
() ->
messageSender = new SignalServiceMessageSender(
getPushServiceSocket(),
dataStore,
sessionLock,
getAttachmentApi(),
getMessageApi(),
getKeysApi(),
Optional.empty(),
executor,
ServiceConfig.MAX_ENVELOPE_SIZE,
() -> true,
UsePqRatchet.NO
)
);
}
public List<SecureValueRecovery> getSecureValueRecovery() {
return getOrCreate(() -> secureValueRecovery,
() -> secureValueRecovery = serviceEnvironmentConfig.svr2Mrenclaves()
.stream()
.map(mr -> (SecureValueRecovery) getAccountManager().getSecureValueRecoveryV2(mr))
.toList());
return getOrCreate(
() -> secureValueRecovery,
() ->
secureValueRecovery = serviceEnvironmentConfig
.svr2Mrenclaves()
.stream()
.map(mr ->
(SecureValueRecovery) getAccountManager().getSecureValueRecoveryV2(
mr
)
)
.toList()
);
}
public ProfileApi getProfileApi() {
return getOrCreate(() -> profileApi,
() -> profileApi = new ProfileApi(getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket(),
getPushServiceSocket(),
getClientZkProfileOperations()));
return getOrCreate(
() -> profileApi,
() ->
profileApi = new ProfileApi(
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket(),
getPushServiceSocket(),
getClientZkProfileOperations()
)
);
}
public ProfileService getProfileService() {
return getOrCreate(() -> profileService,
() -> profileService = new ProfileService(getClientZkProfileOperations(),
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket()));
return getOrCreate(
() -> profileService,
() ->
profileService = new ProfileService(
getClientZkProfileOperations(),
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket()
)
);
}
public SignalServiceCipher getCipher(ServiceIdType serviceIdType) {
final var certificateValidator = new CertificateValidator(serviceEnvironmentConfig.unidentifiedSenderTrustRoot());
final var address = new SignalServiceAddress(credentialsProvider.getAci(), credentialsProvider.getE164());
final var certificateValidator = new CertificateValidator(
serviceEnvironmentConfig.unidentifiedSenderTrustRoot()
);
final var address = new SignalServiceAddress(
credentialsProvider.getAci(),
credentialsProvider.getE164()
);
final var deviceId = credentialsProvider.getDeviceId();
return new SignalServiceCipher(address,
deviceId,
serviceIdType == ServiceIdType.ACI ? dataStore.aci() : dataStore.pni(),
sessionLock,
certificateValidator);
return new SignalServiceCipher(
address,
deviceId,
serviceIdType == ServiceIdType.ACI
? dataStore.aci()
: dataStore.pni(),
sessionLock,
certificateValidator
);
}
private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
@ -382,7 +571,6 @@ public class SignalDependencies {
}
private interface Callable {
void call();
}
}