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
) {
final Interceptor userAgentInterceptor = chain -> chain.proceed(chain.request()
final Interceptor userAgentInterceptor = chain ->
chain.proceed(
chain
.request()
.newBuilder()
.header("User-Agent", userAgent)
.build());
.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();
@ -144,36 +145,58 @@ public class SignalDependencies {
}
public PushServiceSocket getPushServiceSocket() {
return getOrCreate(() -> pushServiceSocket,
() -> pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(),
return getOrCreate(
() -> pushServiceSocket,
() ->
pushServiceSocket = new PushServiceSocket(
serviceEnvironmentConfig.signalServiceConfiguration(),
credentialsProvider,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY));
ServiceConfig.AUTOMATIC_NETWORK_RETRY
)
);
}
public Network getLibSignalNetwork() {
return getOrCreate(() -> libSignalNetwork, () -> {
libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent);
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,15 +206,24 @@ public class SignalDependencies {
}
public SignalServiceAccountManager getAccountManager() {
return getOrCreate(() -> accountManager,
() -> accountManager = new SignalServiceAccountManager(getAuthenticatedSignalWebSocket(),
return getOrCreate(
() -> accountManager,
() ->
accountManager = new SignalServiceAccountManager(
getAuthenticatedSignalWebSocket(),
getAccountApi(),
getPushServiceSocket(),
getGroupsV2Operations()));
getGroupsV2Operations()
)
);
}
public SignalServiceAccountManager createUnauthenticatedAccountManager(String number, String password) {
return SignalServiceAccountManager.createWithStaticCredentials(getServiceEnvironmentConfig().signalServiceConfiguration(),
public SignalServiceAccountManager createUnauthenticatedAccountManager(
String number,
String password
) {
return SignalServiceAccountManager.createWithStaticCredentials(
getServiceEnvironmentConfig().signalServiceConfiguration(),
null,
null,
number,
@ -199,43 +231,77 @@ public class SignalDependencies {
password,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY,
ServiceConfig.GROUP_MAX_SIZE);
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,45 +380,105 @@ public class SignalDependencies {
}
public SignalWebSocket.AuthenticatedWebSocket getAuthenticatedSignalWebSocket() {
return getOrCreate(() -> authenticatedSignalWebSocket, () -> {
return getOrCreate(
() -> authenticatedSignalWebSocket,
() -> {
logger.info(
"Creating new authenticated WebSocket connection to Signal server..."
);
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
final var healthMonitor = new SignalWebSocketHealthMonitor(
timer
);
authenticatedSignalWebSocket = new SignalWebSocket.AuthenticatedWebSocket(() -> new OkHttpWebSocketConnection(
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));
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, () -> {
return getOrCreate(
() -> unauthenticatedSignalWebSocket,
() -> {
logger.info(
"Creating new unauthenticated WebSocket connection to Signal server..."
);
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
final var healthMonitor = new SignalWebSocketHealthMonitor(
timer
);
unauthenticatedSignalWebSocket = new SignalWebSocket.UnauthenticatedWebSocket(() -> new OkHttpWebSocketConnection(
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));
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(),
return getOrCreate(
() -> messageSender,
() ->
messageSender = new SignalServiceMessageSender(
getPushServiceSocket(),
dataStore,
sessionLock,
getAttachmentApi(),
@ -328,41 +488,70 @@ public class SignalDependencies {
executor,
ServiceConfig.MAX_ENVELOPE_SIZE,
() -> true,
UsePqRatchet.NO));
UsePqRatchet.NO
)
);
}
public List<SecureValueRecovery> getSecureValueRecovery() {
return getOrCreate(() -> secureValueRecovery,
() -> secureValueRecovery = serviceEnvironmentConfig.svr2Mrenclaves()
return getOrCreate(
() -> secureValueRecovery,
() ->
secureValueRecovery = serviceEnvironmentConfig
.svr2Mrenclaves()
.stream()
.map(mr -> (SecureValueRecovery) getAccountManager().getSecureValueRecoveryV2(mr))
.toList());
.map(mr ->
(SecureValueRecovery) getAccountManager().getSecureValueRecoveryV2(
mr
)
)
.toList()
);
}
public ProfileApi getProfileApi() {
return getOrCreate(() -> profileApi,
() -> profileApi = new ProfileApi(getAuthenticatedSignalWebSocket(),
return getOrCreate(
() -> profileApi,
() ->
profileApi = new ProfileApi(
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket(),
getPushServiceSocket(),
getClientZkProfileOperations()));
getClientZkProfileOperations()
)
);
}
public ProfileService getProfileService() {
return getOrCreate(() -> profileService,
() -> profileService = new ProfileService(getClientZkProfileOperations(),
return getOrCreate(
() -> profileService,
() ->
profileService = new ProfileService(
getClientZkProfileOperations(),
getAuthenticatedSignalWebSocket(),
getUnauthenticatedSignalWebSocket()));
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,
return new SignalServiceCipher(
address,
deviceId,
serviceIdType == ServiceIdType.ACI ? dataStore.aci() : dataStore.pni(),
serviceIdType == ServiceIdType.ACI
? dataStore.aci()
: dataStore.pni(),
sessionLock,
certificateValidator);
certificateValidator
);
}
private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
@ -382,7 +571,6 @@ public class SignalDependencies {
}
private interface Callable {
void call();
}
}