X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/b810e303ec9d0fcc3ba948b7e65d57f85bffe437..d2251ccd14081b80a1c7bcc906ad866502ff921b:/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java index fef8351f..7a8ecc4b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java @@ -3,8 +3,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.signal.libsignal.metadata.certificate.CertificateValidator; -import org.signal.zkgroup.profiles.ClientZkProfileOperations; -import org.whispersystems.libsignal.util.guava.Optional; +import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.whispersystems.signalservice.api.KeyBackupService; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceDataStore; @@ -18,133 +17,228 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.services.ProfileService; -import org.whispersystems.signalservice.api.util.SleepTimer; +import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.websocket.WebSocketFactory; -import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; +import java.util.Collection; +import java.util.Optional; import java.util.concurrent.ExecutorService; - -import static org.asamk.signal.manager.config.ServiceConfig.capabilities; +import java.util.function.Supplier; public class SignalDependencies { - private final SignalServiceAccountManager accountManager; - private final GroupsV2Api groupsV2Api; - private final GroupsV2Operations groupsV2Operations; + private final Object LOCK = new Object(); + + private final ServiceEnvironmentConfig serviceEnvironmentConfig; + private final String userAgent; + private final CredentialsProvider credentialsProvider; + private final SignalServiceDataStore dataStore; + private final ExecutorService executor; + private final SignalSessionLock sessionLock; + + private boolean allowStories = true; - private final SignalWebSocket signalWebSocket; - private final SignalServiceMessageReceiver messageReceiver; - private final SignalServiceMessageSender messageSender; + private SignalServiceAccountManager accountManager; + private GroupsV2Api groupsV2Api; + private GroupsV2Operations groupsV2Operations; + private ClientZkOperations clientZkOperations; - private final KeyBackupService keyBackupService; - private final ProfileService profileService; - private final SignalServiceCipher cipher; + private SignalWebSocket signalWebSocket; + private SignalServiceMessageReceiver messageReceiver; + private SignalServiceMessageSender messageSender; - public SignalDependencies( - final SignalServiceAddress selfAddress, + private KeyBackupService keyBackupService; + private ProfileService profileService; + private SignalServiceCipher cipher; + + SignalDependencies( final ServiceEnvironmentConfig serviceEnvironmentConfig, final String userAgent, - final DynamicCredentialsProvider credentialsProvider, + final CredentialsProvider credentialsProvider, final SignalServiceDataStore dataStore, final ExecutorService executor, final SignalSessionLock sessionLock ) { - this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create( - serviceEnvironmentConfig.getSignalServiceConfiguration())) : null; - final SleepTimer timer = new UptimeSleepTimer(); - this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), - credentialsProvider, - userAgent, - groupsV2Operations, - ServiceConfig.AUTOMATIC_NETWORK_RETRY); - this.groupsV2Api = accountManager.getGroupsV2Api(); - this.keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(), - serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(), - serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(), - serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(), - 10); - final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create( - serviceEnvironmentConfig.getSignalServiceConfiguration()).getProfileOperations() : null; - this.messageReceiver = new SignalServiceMessageReceiver(serviceEnvironmentConfig.getSignalServiceConfiguration(), - credentialsProvider, - userAgent, - clientZkProfileOperations, - ServiceConfig.AUTOMATIC_NETWORK_RETRY); - - final var healthMonitor = new SignalWebSocketHealthMonitor(timer); - final WebSocketFactory webSocketFactory = new WebSocketFactory() { - @Override - public WebSocketConnection createWebSocket() { - return new WebSocketConnection("normal", - serviceEnvironmentConfig.getSignalServiceConfiguration(), - Optional.of(credentialsProvider), - userAgent, - healthMonitor); - } + this.serviceEnvironmentConfig = serviceEnvironmentConfig; + this.userAgent = userAgent; + this.credentialsProvider = credentialsProvider; + this.dataStore = dataStore; + this.executor = executor; + this.sessionLock = sessionLock; + } - @Override - public WebSocketConnection createUnidentifiedWebSocket() { - return new WebSocketConnection("unidentified", - serviceEnvironmentConfig.getSignalServiceConfiguration(), - Optional.absent(), - userAgent, - healthMonitor); - } - }; - this.signalWebSocket = new SignalWebSocket(webSocketFactory); - healthMonitor.monitor(signalWebSocket); - this.profileService = new ProfileService(clientZkProfileOperations, messageReceiver, signalWebSocket); - - final var certificateValidator = new CertificateValidator(serviceEnvironmentConfig.getUnidentifiedSenderTrustRoot()); - this.cipher = new SignalServiceCipher(selfAddress, dataStore, sessionLock, certificateValidator); - this.messageSender = new SignalServiceMessageSender(serviceEnvironmentConfig.getSignalServiceConfiguration(), - credentialsProvider, - dataStore, - sessionLock, - userAgent, - signalWebSocket, - Optional.absent(), - clientZkProfileOperations, - executor, - ServiceConfig.MAX_ENVELOPE_SIZE, - ServiceConfig.AUTOMATIC_NETWORK_RETRY); + public void resetAfterAddressChange() { + this.messageSender = null; + this.cipher = null; + getSignalWebSocket().forceNewWebSockets(); + } + + /** + * This method needs to be called before the first websocket is created + */ + public void setAllowStories(final boolean allowStories) { + this.allowStories = allowStories; + } + + public ServiceEnvironmentConfig getServiceEnvironmentConfig() { + return serviceEnvironmentConfig; + } + + public SignalSessionLock getSessionLock() { + return sessionLock; } public SignalServiceAccountManager getAccountManager() { - return accountManager; + return getOrCreate(() -> accountManager, + () -> accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), + credentialsProvider, + userAgent, + getGroupsV2Operations(), + ServiceConfig.AUTOMATIC_NETWORK_RETRY)); + } + + public SignalServiceAccountManager createUnauthenticatedAccountManager(String number, String password) { + return new SignalServiceAccountManager(getServiceEnvironmentConfig().getSignalServiceConfiguration(), + null, + null, + number, + SignalServiceAddress.DEFAULT_DEVICE_ID, + password, + userAgent, + ServiceConfig.AUTOMATIC_NETWORK_RETRY, + ServiceConfig.GROUP_MAX_SIZE); } public GroupsV2Api getGroupsV2Api() { - return groupsV2Api; + return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api()); } public GroupsV2Operations getGroupsV2Operations() { - return groupsV2Operations; + return getOrCreate(() -> groupsV2Operations, + () -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()), + ServiceConfig.GROUP_MAX_SIZE)); + } + + private ClientZkOperations getClientZkOperations() { + return getOrCreate(() -> clientZkOperations, + () -> clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())); + } + + private ClientZkProfileOperations getClientZkProfileOperations() { + final var clientZkOperations = getClientZkOperations(); + return clientZkOperations == null ? null : clientZkOperations.getProfileOperations(); } public SignalWebSocket getSignalWebSocket() { - return signalWebSocket; + return getOrCreate(() -> signalWebSocket, () -> { + final var timer = new UptimeSleepTimer(); + final var healthMonitor = new SignalWebSocketHealthMonitor(timer); + final var webSocketFactory = new WebSocketFactory() { + @Override + public WebSocketConnection createWebSocket() { + return new WebSocketConnection("normal", + serviceEnvironmentConfig.getSignalServiceConfiguration(), + Optional.of(credentialsProvider), + userAgent, + healthMonitor, + allowStories); + } + + @Override + public WebSocketConnection createUnidentifiedWebSocket() { + return new WebSocketConnection("unidentified", + serviceEnvironmentConfig.getSignalServiceConfiguration(), + Optional.empty(), + userAgent, + healthMonitor, + allowStories); + } + }; + signalWebSocket = new SignalWebSocket(webSocketFactory); + healthMonitor.monitor(signalWebSocket); + }); } public SignalServiceMessageReceiver getMessageReceiver() { - return messageReceiver; + return getOrCreate(() -> messageReceiver, + () -> messageReceiver = new SignalServiceMessageReceiver(serviceEnvironmentConfig.getSignalServiceConfiguration(), + credentialsProvider, + userAgent, + getClientZkProfileOperations(), + ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } public SignalServiceMessageSender getMessageSender() { - return messageSender; + return getOrCreate(() -> messageSender, + () -> messageSender = new SignalServiceMessageSender(serviceEnvironmentConfig.getSignalServiceConfiguration(), + credentialsProvider, + dataStore, + sessionLock, + userAgent, + getSignalWebSocket(), + Optional.empty(), + getClientZkProfileOperations(), + executor, + ServiceConfig.MAX_ENVELOPE_SIZE, + ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } public KeyBackupService getKeyBackupService() { - return keyBackupService; + return getOrCreate(() -> keyBackupService, + () -> keyBackupService = getAccountManager().getKeyBackupService(ServiceConfig.getIasKeyStore(), + serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(), + serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(), + serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(), + 10)); + } + + public Collection getFallbackKeyBackupServices() { + return serviceEnvironmentConfig.getFallbackKeyBackupConfigs() + .stream() + .map(config -> getAccountManager().getKeyBackupService(ServiceConfig.getIasKeyStore(), + config.getEnclaveName(), + config.getServiceId(), + config.getMrenclave(), + 10)) + .toList(); } public ProfileService getProfileService() { - return profileService; + return getOrCreate(() -> profileService, + () -> profileService = new ProfileService(getClientZkProfileOperations(), + getMessageReceiver(), + getSignalWebSocket())); } public SignalServiceCipher getCipher() { - return cipher; + return getOrCreate(() -> cipher, () -> { + final var certificateValidator = new CertificateValidator(serviceEnvironmentConfig.getUnidentifiedSenderTrustRoot()); + final var address = new SignalServiceAddress(credentialsProvider.getAci(), credentialsProvider.getE164()); + final var deviceId = credentialsProvider.getDeviceId(); + cipher = new SignalServiceCipher(address, deviceId, dataStore.aci(), sessionLock, certificateValidator); + }); + } + + private T getOrCreate(Supplier supplier, Callable creator) { + var value = supplier.get(); + if (value != null) { + return value; + } + + synchronized (LOCK) { + value = supplier.get(); + if (value != null) { + return value; + } + creator.call(); + return supplier.get(); + } + } + + private interface Callable { + + void call(); } }