diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index 587fc89b..8163d02d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -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 + ); }; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 0bc895cb..5dbc73d2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -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 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 getOrCreate(Supplier supplier, Callable creator) { @@ -382,7 +571,6 @@ public class SignalDependencies { } private interface Callable { - void call(); } }