]> nmode's Git Repositories - signal-cli/commitdiff
Update libsignal-service
authorAsamK <asamk@gmx.de>
Sun, 16 Mar 2025 21:06:58 +0000 (22:06 +0100)
committerAsamK <asamk@gmx.de>
Sun, 16 Mar 2025 21:06:58 +0000 (22:06 +0100)
18 files changed:
gradle/libs.versions.toml
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/api/InvalidNumberException.java
lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java
lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java
lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/internal/ReentrantSignalSessionLock.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java
lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java
lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java
lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java
lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV2RecordProcessor.java
lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/util/Utils.java

index d0ac967c21b1c588c180add094ee7a88df8f2df7..b8d57a12e72f5ee73337823cb7e5a429c496e05c 100644 (file)
@@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
 slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
 logback = "ch.qos.logback:logback-classic:1.5.17"
 
 slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
 logback = "ch.qos.logback:logback-classic:1.5.17"
 
-signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_118"
+signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_119"
 sqlite = "org.xerial:sqlite-jdbc:3.49.1.0"
 hikari = "com.zaxxer:HikariCP:6.2.1"
 junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0"
 sqlite = "org.xerial:sqlite-jdbc:3.49.1.0"
 hikari = "com.zaxxer:HikariCP:6.2.1"
 junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0"
index bdb3dbc97f4d144f2caad406285261c9fe413b83..affbaa9de5993f607d61c468cc689dffa663faf0 100644 (file)
@@ -1,5 +1,7 @@
 package org.asamk.signal.manager;
 
 package org.asamk.signal.manager;
 
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+
 import org.asamk.signal.manager.api.AlreadyReceivingException;
 import org.asamk.signal.manager.api.AttachmentInvalidException;
 import org.asamk.signal.manager.api.CaptchaRejectedException;
 import org.asamk.signal.manager.api.AlreadyReceivingException;
 import org.asamk.signal.manager.api.AttachmentInvalidException;
 import org.asamk.signal.manager.api.CaptchaRejectedException;
@@ -49,7 +51,6 @@ import org.asamk.signal.manager.api.UsernameStatus;
 import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 
 import java.io.Closeable;
 import java.io.File;
 
 import java.io.Closeable;
 import java.io.File;
@@ -65,7 +66,7 @@ import java.util.Set;
 public interface Manager extends Closeable {
 
     static boolean isValidNumber(final String e164Number, final String countryCode) {
 public interface Manager extends Closeable {
 
     static boolean isValidNumber(final String e164Number, final String countryCode) {
-        return PhoneNumberFormatter.isValidNumber(e164Number, countryCode);
+        return PhoneNumberUtil.getInstance().isPossibleNumber(e164Number, countryCode);
     }
 
     static boolean isSignalClientAvailable() {
     }
 
     static boolean isSignalClientAvailable() {
index 8e6a806472268674de69705aa28517fc26ddcd16..14f37966395f49d67238f5c4d036f670abc257e3 100644 (file)
@@ -2,7 +2,7 @@ package org.asamk.signal.manager.api;
 
 public class InvalidNumberException extends Exception {
 
 
 public class InvalidNumberException extends Exception {
 
-    InvalidNumberException(String message) {
+    public InvalidNumberException(String message) {
         super(message);
     }
 
         super(message);
     }
 
index 53da2aade3e712fc3833fbcf9737e03376d379c5..b1a3ad61d04c98f90ecf349268926320657b4805 100644 (file)
@@ -1,8 +1,8 @@
 package org.asamk.signal.manager.api;
 
 package org.asamk.signal.manager.api;
 
+import org.asamk.signal.manager.util.PhoneNumberFormatter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 import org.whispersystems.signalservice.api.util.UuidUtil;
 
 import java.util.UUID;
 import org.whispersystems.signalservice.api.util.UuidUtil;
 
 import java.util.UUID;
@@ -24,32 +24,28 @@ public sealed interface RecipientIdentifier {
     sealed interface Single extends RecipientIdentifier {
 
         static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
     sealed interface Single extends RecipientIdentifier {
 
         static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
-            try {
-                if (UuidUtil.isUuid(identifier)) {
-                    return new Uuid(UUID.fromString(identifier));
-                }
+            if (UuidUtil.isUuid(identifier)) {
+                return new Uuid(UUID.fromString(identifier));
+            }
 
 
-                if (identifier.startsWith("PNI:")) {
-                    final var pni = identifier.substring(4);
-                    if (!UuidUtil.isUuid(pni)) {
-                        throw new InvalidNumberException("Invalid PNI");
-                    }
-                    return new Pni(UUID.fromString(pni));
+            if (identifier.startsWith("PNI:")) {
+                final var pni = identifier.substring(4);
+                if (!UuidUtil.isUuid(pni)) {
+                    throw new InvalidNumberException("Invalid PNI");
                 }
                 }
+                return new Pni(UUID.fromString(pni));
+            }
 
 
-                if (identifier.startsWith("u:")) {
-                    return new Username(identifier.substring(2));
-                }
+            if (identifier.startsWith("u:")) {
+                return new Username(identifier.substring(2));
+            }
 
 
-                final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber);
-                if (!normalizedNumber.equals(identifier)) {
-                    final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class);
-                    logger.debug("Normalized number {} to {}.", identifier, normalizedNumber);
-                }
-                return new Number(normalizedNumber);
-            } catch (org.whispersystems.signalservice.api.util.InvalidNumberException e) {
-                throw new InvalidNumberException(e.getMessage(), e);
+            final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber);
+            if (!normalizedNumber.equals(identifier)) {
+                final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class);
+                logger.debug("Normalized number {} to {}.", identifier, normalizedNumber);
             }
             }
+            return new Number(normalizedNumber);
         }
 
         static Single fromAddress(RecipientAddress address) {
         }
 
         static Single fromAddress(RecipientAddress address) {
index 0710b50c030a4313f71001d07768ca500a15665b..7d40f99c41114386265e6914052e1cf388f7cee8 100644 (file)
@@ -32,6 +32,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.PNI;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
+import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
 import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
 import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
@@ -50,7 +51,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 import okio.ByteString;
 import java.util.concurrent.TimeUnit;
 
 import okio.ByteString;
@@ -289,12 +290,13 @@ public class AccountHelper {
                 context.getPinHelper(),
                 (sessionId1, verificationCode1, registrationLock) -> {
                     final var registrationApi = dependencies.getRegistrationApi();
                 context.getPinHelper(),
                 (sessionId1, verificationCode1, registrationLock) -> {
                     final var registrationApi = dependencies.getRegistrationApi();
+                    final var accountApi = dependencies.getAccountApi();
                     try {
                         handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1));
                     } catch (AlreadyVerifiedException e) {
                         // Already verified so can continue changing number
                     }
                     try {
                         handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1));
                     } catch (AlreadyVerifiedException e) {
                         // Already verified so can continue changing number
                     }
-                    return handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(sessionId1,
+                    return handleResponseException(accountApi.changeNumber(new ChangePhoneNumberRequest(sessionId1,
                             null,
                             newNumber,
                             registrationLock,
                             null,
                             newNumber,
                             registrationLock,
@@ -378,7 +380,7 @@ public class AccountHelper {
             candidateHashes.add(Base64.encodeUrlSafeWithoutPadding(candidate.getHash()));
         }
 
             candidateHashes.add(Base64.encodeUrlSafeWithoutPadding(candidate.getHash()));
         }
 
-        final var response = dependencies.getAccountManager().reserveUsername(candidateHashes);
+        final var response = handleResponseException(dependencies.getAccountApi().reserveUsername(candidateHashes));
         final var hashIndex = candidateHashes.indexOf(response.getUsernameHash());
         if (hashIndex == -1) {
             logger.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes.");
         final var hashIndex = candidateHashes.indexOf(response.getUsernameHash());
         if (hashIndex == -1) {
             logger.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes.");
@@ -388,7 +390,7 @@ public class AccountHelper {
         logger.debug("[reserveUsername] Successfully reserved username.");
         final var username = candidates.get(hashIndex);
 
         logger.debug("[reserveUsername] Successfully reserved username.");
         final var username = candidates.get(hashIndex);
 
-        final var linkComponents = dependencies.getAccountManager().confirmUsernameAndCreateNewLink(username);
+        final var linkComponents = confirmUsernameAndCreateNewLink(username);
         account.setUsername(username.getUsername());
         account.setUsernameLink(linkComponents);
         account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
         account.setUsername(username.getUsername());
         account.setUsernameLink(linkComponents);
         account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
@@ -396,6 +398,40 @@ public class AccountHelper {
         logger.debug("[confirmUsername] Successfully confirmed username.");
     }
 
         logger.debug("[confirmUsername] Successfully confirmed username.");
     }
 
+    public UsernameLinkComponents createUsernameLink(Username username) throws IOException {
+        try {
+            Username.UsernameLink link = username.generateLink();
+            return handleResponseException(dependencies.getAccountApi().createUsernameLink(link));
+        } catch (BaseUsernameException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    private UsernameLinkComponents confirmUsernameAndCreateNewLink(Username username) throws IOException {
+        try {
+            Username.UsernameLink link = username.generateLink();
+            UUID serverId = handleResponseException(dependencies.getAccountApi().confirmUsername(username, link));
+
+            return new UsernameLinkComponents(link.getEntropy(), serverId);
+        } catch (BaseUsernameException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    private UsernameLinkComponents reclaimUsernameAndLink(
+            Username username,
+            UsernameLinkComponents linkComponents
+    ) throws IOException {
+        try {
+            Username.UsernameLink link = username.generateLink(linkComponents.getEntropy());
+            UUID serverId = handleResponseException(dependencies.getAccountApi().confirmUsername(username, link));
+
+            return new UsernameLinkComponents(link.getEntropy(), serverId);
+        } catch (BaseUsernameException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     public void refreshCurrentUsername() throws IOException, BaseUsernameException {
         final var localUsername = account.getUsername();
         if (localUsername == null) {
     public void refreshCurrentUsername() throws IOException, BaseUsernameException {
         final var localUsername = account.getUsername();
         if (localUsername == null) {
@@ -438,14 +474,14 @@ public class AccountHelper {
         final var usernameLink = account.getUsernameLink();
 
         if (usernameLink == null) {
         final var usernameLink = account.getUsernameLink();
 
         if (usernameLink == null) {
-            dependencies.getAccountManager()
-                    .reserveUsername(List.of(Base64.encodeUrlSafeWithoutPadding(username.getHash())));
+            handleResponseException(dependencies.getAccountApi()
+                    .reserveUsername(List.of(Base64.encodeUrlSafeWithoutPadding(username.getHash()))));
             logger.debug("[reserveUsername] Successfully reserved existing username.");
             logger.debug("[reserveUsername] Successfully reserved existing username.");
-            final var linkComponents = dependencies.getAccountManager().confirmUsernameAndCreateNewLink(username);
+            final var linkComponents = confirmUsernameAndCreateNewLink(username);
             account.setUsernameLink(linkComponents);
             logger.debug("[confirmUsername] Successfully confirmed existing username.");
         } else {
             account.setUsernameLink(linkComponents);
             logger.debug("[confirmUsername] Successfully confirmed existing username.");
         } else {
-            final var linkComponents = dependencies.getAccountManager().reclaimUsernameAndLink(username, usernameLink);
+            final var linkComponents = reclaimUsernameAndLink(username, usernameLink);
             account.setUsernameLink(linkComponents);
             logger.debug("[confirmUsername] Successfully reclaimed existing username and link.");
         }
             account.setUsernameLink(linkComponents);
             logger.debug("[confirmUsername] Successfully reclaimed existing username and link.");
         }
@@ -455,7 +491,7 @@ public class AccountHelper {
     private void tryToSetUsernameLink(Username username) {
         for (var i = 1; i < 4; i++) {
             try {
     private void tryToSetUsernameLink(Username username) {
         for (var i = 1; i < 4; i++) {
             try {
-                final var linkComponents = dependencies.getAccountManager().createUsernameLink(username);
+                final var linkComponents = createUsernameLink(username);
                 account.setUsernameLink(linkComponents);
                 break;
             } catch (IOException e) {
                 account.setUsernameLink(linkComponents);
                 break;
             } catch (IOException e) {
@@ -465,9 +501,8 @@ public class AccountHelper {
     }
 
     public void deleteUsername() throws IOException {
     }
 
     public void deleteUsername() throws IOException {
-        dependencies.getAccountManager().deleteUsernameLink();
+        handleResponseException(dependencies.getAccountApi().deleteUsername());
         account.setUsernameLink(null);
         account.setUsernameLink(null);
-        dependencies.getAccountManager().deleteUsername();
         account.setUsername(null);
         logger.debug("[deleteUsername] Successfully deleted the username.");
     }
         account.setUsername(null);
         logger.debug("[deleteUsername] Successfully deleted the username.");
     }
@@ -479,7 +514,7 @@ public class AccountHelper {
     }
 
     public void updateAccountAttributes() throws IOException {
     }
 
     public void updateAccountAttributes() throws IOException {
-        dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null));
+        handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null)));
     }
 
     public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException {
     }
 
     public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException {
@@ -510,8 +545,8 @@ public class AccountHelper {
     }
 
     public void removeLinkedDevices(int deviceId) throws IOException {
     }
 
     public void removeLinkedDevices(int deviceId) throws IOException {
-        dependencies.getAccountManager().removeDevice(deviceId);
-        var devices = dependencies.getAccountManager().getDevices();
+        handleResponseException(dependencies.getLinkDeviceApi().removeDevice(deviceId));
+        var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
         account.setMultiDevice(devices.size() > 1);
     }
 
         account.setMultiDevice(devices.size() > 1);
     }
 
@@ -519,14 +554,16 @@ public class AccountHelper {
         var masterKey = account.getOrCreatePinMasterKey();
 
         context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
         var masterKey = account.getOrCreatePinMasterKey();
 
         context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
-        dependencies.getAccountManager().enableRegistrationLock(masterKey);
+        handleResponseException(dependencies.getAccountApi()
+                .enableRegistrationLock(masterKey.deriveRegistrationLock()));
     }
 
     public void setRegistrationPin(String pin) throws IOException {
         var masterKey = account.getOrCreatePinMasterKey();
 
         context.getPinHelper().setRegistrationLockPin(pin, masterKey);
     }
 
     public void setRegistrationPin(String pin) throws IOException {
         var masterKey = account.getOrCreatePinMasterKey();
 
         context.getPinHelper().setRegistrationLockPin(pin, masterKey);
-        dependencies.getAccountManager().enableRegistrationLock(masterKey);
+        handleResponseException(dependencies.getAccountApi()
+                .enableRegistrationLock(masterKey.deriveRegistrationLock()));
 
         account.setRegistrationLockPin(pin);
         updateAccountAttributes();
 
         account.setRegistrationLockPin(pin);
         updateAccountAttributes();
@@ -535,7 +572,7 @@ public class AccountHelper {
     public void removeRegistrationPin() throws IOException {
         // Remove KBS Pin
         context.getPinHelper().removeRegistrationLockPin();
     public void removeRegistrationPin() throws IOException {
         // Remove KBS Pin
         context.getPinHelper().removeRegistrationLockPin();
-        dependencies.getAccountManager().disableRegistrationLock();
+        handleResponseException(dependencies.getAccountApi().disableRegistrationLock());
 
         account.setRegistrationLockPin(null);
     }
 
         account.setRegistrationLockPin(null);
     }
@@ -544,7 +581,7 @@ public class AccountHelper {
         // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
         // If this is the primary device, other users can't send messages to this number anymore.
         // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
         // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
         // If this is the primary device, other users can't send messages to this number anymore.
         // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
-        dependencies.getAccountManager().setGcmId(Optional.empty());
+        handleResponseException(dependencies.getAccountApi().clearFcmToken());
 
         account.setRegistered(false);
         unregisteredListener.call();
 
         account.setRegistered(false);
         unregisteredListener.call();
index ab75bd165148c6cfd1dbf0971d3d6862636b2d3c..eb935188501f1a41c47bbd47869add81d9490543 100644 (file)
@@ -11,10 +11,10 @@ import org.asamk.signal.manager.storage.messageCache.CachedMessage;
 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.SignalWebSocket;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
+import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
 import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
 import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException;
 
 import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
 import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException;
 
@@ -28,7 +28,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeoutException;
 
 import java.util.Set;
 import java.util.concurrent.TimeoutException;
 
-import io.reactivex.rxjava3.core.Observable;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 public class ReceiveHelper {
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 public class ReceiveHelper {
@@ -94,9 +93,8 @@ public class ReceiveHelper {
         // Use a Map here because java Set doesn't have a get method ...
         Map<HandleAction, HandleAction> queuedActions = new HashMap<>();
 
         // Use a Map here because java Set doesn't have a get method ...
         Map<HandleAction, HandleAction> queuedActions = new HashMap<>();
 
-        final var signalWebSocket = dependencies.getSignalWebSocket();
-        final var webSocketStateDisposable = Observable.merge(signalWebSocket.getUnidentifiedWebSocketState(),
-                        signalWebSocket.getWebSocketState())
+        final var signalWebSocket = dependencies.getAuthenticatedSignalWebSocket();
+        final var webSocketStateDisposable = signalWebSocket.getState()
                 .subscribeOn(Schedulers.computation())
                 .observeOn(Schedulers.computation())
                 .distinctUntilChanged()
                 .subscribeOn(Schedulers.computation())
                 .observeOn(Schedulers.computation())
                 .distinctUntilChanged()
@@ -116,7 +114,7 @@ public class ReceiveHelper {
     }
 
     private void receiveMessagesInternal(
     }
 
     private void receiveMessagesInternal(
-            final SignalWebSocket signalWebSocket,
+            final SignalWebSocket.AuthenticatedWebSocket signalWebSocket,
             Duration timeout,
             boolean returnOnTimeout,
             Integer maxMessages,
             Duration timeout,
             boolean returnOnTimeout,
             Integer maxMessages,
index 125fa0d83565eafaabc5e2800fc2344a4f0bdb2e..58e7ba59fb85e705254486982b2fe80ff1ea1e75 100644 (file)
@@ -10,13 +10,13 @@ import org.signal.libsignal.usernames.BaseUsernameException;
 import org.signal.libsignal.usernames.Username;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.signal.libsignal.usernames.Username;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.cds.CdsiV2Service;
 import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentException;
 import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException;
 import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentException;
 import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException;
-import org.whispersystems.signalservice.api.services.CdsiV2Service;
 
 import java.io.IOException;
 import java.util.Collection;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -27,6 +27,7 @@ import java.util.Optional;
 import java.util.Set;
 
 import static org.asamk.signal.manager.config.ServiceConfig.MAXIMUM_ONE_OFF_REQUEST_SIZE;
 import java.util.Set;
 
 import static org.asamk.signal.manager.config.ServiceConfig.MAXIMUM_ONE_OFF_REQUEST_SIZE;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
 
 public class RecipientHelper {
 
 
 public class RecipientHelper {
 
@@ -108,7 +109,7 @@ public class RecipientHelper {
         }
         if (forceRefresh) {
             try {
         }
         if (forceRefresh) {
             try {
-                final var aci = dependencies.getAccountManager().getAciByUsername(finalUsername);
+                final var aci = handleResponseException(dependencies.getUsernameApi().getAciByUsername(finalUsername));
                 return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername());
             } catch (IOException e) {
                 throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null,
                 return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername());
             } catch (IOException e) {
                 throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null,
@@ -119,7 +120,7 @@ public class RecipientHelper {
         }
         return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> {
             try {
         }
         return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> {
             try {
-                return dependencies.getAccountManager().getAciByUsername(finalUsername);
+                return handleResponseException(dependencies.getUsernameApi().getAciByUsername(finalUsername));
             } catch (Exception e) {
                 return null;
             }
             } catch (Exception e) {
                 return null;
             }
@@ -130,8 +131,8 @@ public class RecipientHelper {
         try {
             final var usernameLinkUrl = UsernameLinkUrl.fromUri(username);
             final var components = usernameLinkUrl.getComponents();
         try {
             final var usernameLinkUrl = UsernameLinkUrl.fromUri(username);
             final var components = usernameLinkUrl.getComponents();
-            final var encryptedUsername = dependencies.getAccountManager()
-                    .getEncryptedUsernameFromLinkServerId(components.getServerId());
+            final var encryptedUsername = handleResponseException(dependencies.getUsernameApi()
+                    .getEncryptedUsernameFromLinkServerId(components.getServerId()));
             final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername);
 
             return Username.fromLink(link);
             final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername);
 
             return Username.fromLink(link);
@@ -234,13 +235,14 @@ public class RecipientHelper {
 
         final CdsiV2Service.Response response;
         try {
 
         final CdsiV2Service.Response response;
         try {
-            response = dependencies.getAccountManager()
-                    .getRegisteredUsersWithCdsi(token.isEmpty() ? Set.of() : previousNumbers,
+            response = handleResponseException(dependencies.getCdsApi()
+                    .getRegisteredUsers(token.isEmpty() ? Set.of() : previousNumbers,
                             newNumbers,
                             account.getRecipientStore().getServiceIdToProfileKeyMap(),
                             token,
                             null,
                             dependencies.getLibSignalNetwork(),
                             newNumbers,
                             account.getRecipientStore().getServiceIdToProfileKeyMap(),
                             token,
                             null,
                             dependencies.getLibSignalNetwork(),
+                            false,
                             newToken -> {
                                 if (isPartialRefresh) {
                                     account.getCdsiStore().updateAfterPartialCdsQuery(newNumbers);
                             newToken -> {
                                 if (isPartialRefresh) {
                                     account.getCdsiStore().updateAfterPartialCdsQuery(newNumbers);
@@ -256,7 +258,7 @@ public class RecipientHelper {
                                     account.setCdsiToken(newToken);
                                     account.setLastRecipientsRefresh(System.currentTimeMillis());
                                 }
                                     account.setCdsiToken(newToken);
                                     account.setLastRecipientsRefresh(System.currentTimeMillis());
                                 }
-                            });
+                            }));
         } catch (CdsiInvalidTokenException | CdsiInvalidArgumentException e) {
             account.setCdsiToken(null);
             account.getCdsiStore().clearAll();
         } catch (CdsiInvalidTokenException | CdsiInvalidArgumentException e) {
             account.setCdsiToken(null);
             account.getCdsiStore().clearAll();
index 403b9adf7d3ac5ebd9ec7818d2c70eb9ea7187d4..c5a7552f6e85b2269866e8a928bf977c7b4e7d93 100644 (file)
@@ -35,6 +35,7 @@ import org.asamk.signal.manager.api.IdentityVerificationCode;
 import org.asamk.signal.manager.api.InactiveGroupLinkException;
 import org.asamk.signal.manager.api.IncorrectPinException;
 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
 import org.asamk.signal.manager.api.InactiveGroupLinkException;
 import org.asamk.signal.manager.api.IncorrectPinException;
 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
+import org.asamk.signal.manager.api.InvalidNumberException;
 import org.asamk.signal.manager.api.InvalidStickerException;
 import org.asamk.signal.manager.api.InvalidUsernameException;
 import org.asamk.signal.manager.api.LastGroupAdminException;
 import org.asamk.signal.manager.api.InvalidStickerException;
 import org.asamk.signal.manager.api.InvalidUsernameException;
 import org.asamk.signal.manager.api.LastGroupAdminException;
@@ -87,12 +88,12 @@ import org.asamk.signal.manager.storage.stickers.StickerPack;
 import org.asamk.signal.manager.util.AttachmentUtils;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.asamk.signal.manager.util.MimeUtils;
 import org.asamk.signal.manager.util.AttachmentUtils;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.asamk.signal.manager.util.MimeUtils;
+import org.asamk.signal.manager.util.PhoneNumberFormatter;
 import org.asamk.signal.manager.util.StickerUtils;
 import org.signal.libsignal.protocol.InvalidMessageException;
 import org.signal.libsignal.usernames.BaseUsernameException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.asamk.signal.manager.util.StickerUtils;
 import org.signal.libsignal.protocol.InvalidMessageException;
 import org.signal.libsignal.usernames.BaseUsernameException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServicePreview;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServicePreview;
@@ -106,8 +107,6 @@ import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhauste
 import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException;
 import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException;
 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
 import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException;
 import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException;
 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 import org.whispersystems.signalservice.api.util.StreamDetails;
 import org.whispersystems.signalservice.internal.util.Hex;
 import org.whispersystems.signalservice.internal.util.Util;
 import org.whispersystems.signalservice.api.util.StreamDetails;
 import org.whispersystems.signalservice.internal.util.Hex;
 import org.whispersystems.signalservice.internal.util.Util;
@@ -132,7 +131,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -142,6 +140,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
 import okio.Utf8;
 
 import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES;
 import okio.Utf8;
 
 import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
 import static org.signal.core.util.StringExtensionsKt.splitByByteLength;
 
 public class ManagerImpl implements Manager {
 import static org.signal.core.util.StringExtensionsKt.splitByByteLength;
 
 public class ManagerImpl implements Manager {
@@ -171,15 +170,7 @@ public class ManagerImpl implements Manager {
     ) {
         this.account = account;
 
     ) {
         this.account = account;
 
-        final var sessionLock = new SignalSessionLock() {
-            private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
-
-            @Override
-            public Lock acquire() {
-                LEGACY_LOCK.lock();
-                return LEGACY_LOCK::unlock;
-            }
-        };
+        final var sessionLock = new ReentrantSignalSessionLock();
         this.dependencies = new SignalDependencies(serviceEnvironmentConfig,
                 userAgent,
                 account.getCredentialsProvider(),
         this.dependencies = new SignalDependencies(serviceEnvironmentConfig,
                 userAgent,
                 account.getCredentialsProvider(),
@@ -457,10 +448,10 @@ public class ManagerImpl implements Manager {
             String challenge,
             String captcha
     ) throws IOException, CaptchaRejectedException {
             String challenge,
             String captcha
     ) throws IOException, CaptchaRejectedException {
-        captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
+        captcha = captcha == null ? "" : captcha.replace("signalcaptcha://", "");
 
         try {
 
         try {
-            dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha);
+            handleResponseException(dependencies.getRateLimitChallengeApi().submitCaptchaChallenge(challenge, captcha));
         } catch (org.whispersystems.signalservice.internal.push.exceptions.CaptchaRejectedException ignored) {
             throw new CaptchaRejectedException();
         }
         } catch (org.whispersystems.signalservice.internal.push.exceptions.CaptchaRejectedException ignored) {
             throw new CaptchaRejectedException();
         }
@@ -468,7 +459,7 @@ public class ManagerImpl implements Manager {
 
     @Override
     public List<Device> getLinkedDevices() throws IOException {
 
     @Override
     public List<Device> getLinkedDevices() throws IOException {
-        var devices = dependencies.getAccountManager().getDevices();
+        var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
         account.setMultiDevice(devices.size() > 1);
         var identityKey = account.getAciIdentityKeyPair().getPrivateKey();
         return devices.stream().map(d -> {
         account.setMultiDevice(devices.size() > 1);
         var identityKey = account.getAciIdentityKeyPair().getPrivateKey();
         return devices.stream().map(d -> {
@@ -1594,7 +1585,8 @@ public class ManagerImpl implements Manager {
         context.close();
         executor.close();
 
         context.close();
         executor.close();
 
-        dependencies.getSignalWebSocket().disconnect();
+        dependencies.getAuthenticatedSignalWebSocket().disconnect();
+        dependencies.getUnauthenticatedSignalWebSocket().disconnect();
         dependencies.getPushServiceSocket().close();
         disposable.dispose();
 
         dependencies.getPushServiceSocket().close();
         disposable.dispose();
 
index fcef536a6d8bb000b3cc3862b98b60ba97cbf78f..e5a381e102952830b1f257aa9b8121ea7b33110e 100644 (file)
@@ -29,12 +29,11 @@ import org.asamk.signal.manager.util.KeyUtils;
 import org.signal.libsignal.protocol.IdentityKeyPair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.signal.libsignal.protocol.IdentityKeyPair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
-import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
+import org.whispersystems.signalservice.api.registration.ProvisioningApi;
 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
 import org.whispersystems.signalservice.internal.push.ProvisioningSocket;
 import org.whispersystems.signalservice.internal.push.PushServiceSocket;
 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
 import org.whispersystems.signalservice.internal.push.ProvisioningSocket;
 import org.whispersystems.signalservice.internal.push.PushServiceSocket;
@@ -58,7 +57,7 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
     private final Consumer<Manager> newManagerListener;
     private final AccountsStore accountsStore;
 
     private final Consumer<Manager> newManagerListener;
     private final AccountsStore accountsStore;
 
-    private final SignalServiceAccountManager accountManager;
+    private final ProvisioningApi provisioningApi;
     private final IdentityKeyPair tempIdentityKey;
     private final String password;
 
     private final IdentityKeyPair tempIdentityKey;
     private final String password;
 
@@ -78,7 +77,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
         tempIdentityKey = KeyUtils.generateIdentityKeyPair();
         password = KeyUtils.createPassword();
         final var clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration());
         tempIdentityKey = KeyUtils.generateIdentityKeyPair();
         password = KeyUtils.createPassword();
         final var clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration());
-        final var groupsV2Operations = new GroupsV2Operations(clientZkOperations, ServiceConfig.GROUP_MAX_SIZE);
         final var credentialsProvider = new DynamicCredentialsProvider(null,
                 null,
                 null,
         final var credentialsProvider = new DynamicCredentialsProvider(null,
                 null,
                 null,
@@ -89,21 +87,21 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
                 userAgent,
                 clientZkOperations.getProfileOperations(),
                 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
                 userAgent,
                 clientZkOperations.getProfileOperations(),
                 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
-        accountManager = new SignalServiceAccountManager(pushServiceSocket,
-                new ProvisioningSocket(serviceEnvironmentConfig.signalServiceConfiguration(), userAgent),
-                groupsV2Operations);
+        final var provisioningSocket = new ProvisioningSocket(serviceEnvironmentConfig.signalServiceConfiguration(),
+                userAgent);
+        this.provisioningApi = new ProvisioningApi(pushServiceSocket, provisioningSocket, credentialsProvider);
     }
 
     @Override
     public URI getDeviceLinkUri() throws TimeoutException, IOException {
     }
 
     @Override
     public URI getDeviceLinkUri() throws TimeoutException, IOException {
-        var deviceUuid = accountManager.getNewDeviceUuid();
+        var deviceUuid = provisioningApi.getNewDeviceUuid();
 
         return new DeviceLinkUrl(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
     }
 
     @Override
     public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
 
         return new DeviceLinkUrl(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
     }
 
     @Override
     public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
-        var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
+        var ret = provisioningApi.getNewDeviceRegistration(tempIdentityKey);
         var number = ret.getNumber();
         var aci = ret.getAci();
         var pni = ret.getPni();
         var number = ret.getNumber();
         var aci = ret.getAci();
         var pni = ret.getPni();
@@ -160,7 +158,7 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
             final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
 
             logger.debug("Finishing new device registration");
             final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
 
             logger.debug("Finishing new device registration");
-            var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
+            var deviceId = provisioningApi.finishNewDeviceRegistration(ret.getProvisioningCode(),
                     account.getAccountAttributes(null),
                     aciPreKeys,
                     pniPreKeys);
                     account.getAccountAttributes(null),
                     aciPreKeys,
                     pniPreKeys);
diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ReentrantSignalSessionLock.java b/lib/src/main/java/org/asamk/signal/manager/internal/ReentrantSignalSessionLock.java
new file mode 100644 (file)
index 0000000..7830446
--- /dev/null
@@ -0,0 +1,16 @@
+package org.asamk.signal.manager.internal;
+
+import org.whispersystems.signalservice.api.SignalSessionLock;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+class ReentrantSignalSessionLock implements SignalSessionLock {
+
+    private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
+
+    @Override
+    public Lock acquire() {
+        LEGACY_LOCK.lock();
+        return LEGACY_LOCK::unlock;
+    }
+}
index 5faf13710900be823ea1984a115475a6b153395e..e103fec3439c84c4b35bfc82d543af1f6e0bbe2f 100644 (file)
@@ -32,14 +32,11 @@ import org.asamk.signal.manager.helper.PinHelper;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.asamk.signal.manager.util.NumberVerificationUtils;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.asamk.signal.manager.util.NumberVerificationUtils;
-import org.asamk.signal.manager.util.Utils;
 import org.signal.libsignal.usernames.BaseUsernameException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 import org.whispersystems.signalservice.api.account.PreKeyCollection;
 import org.signal.libsignal.usernames.BaseUsernameException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 import org.whispersystems.signalservice.api.account.PreKeyCollection;
-import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
-import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
 import org.whispersystems.signalservice.api.kbs.MasterKey;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
 import org.whispersystems.signalservice.api.kbs.MasterKey;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
@@ -48,13 +45,13 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
 import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
 import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
 import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
-import org.whispersystems.signalservice.internal.push.PushServiceSocket;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
 
 import java.io.IOException;
 import java.util.function.Consumer;
 
 import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
 
 import java.io.IOException;
 import java.util.function.Consumer;
 
 import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
 
 public class RegistrationManagerImpl implements RegistrationManager {
 
 
 public class RegistrationManagerImpl implements RegistrationManager {
 
@@ -199,7 +196,7 @@ public class RegistrationManagerImpl implements RegistrationManager {
             final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
             final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
             final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
             final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
             final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
             final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
-            final var response = Utils.handleResponseException(registrationApi.registerAccount(null,
+            final var response = handleResponseException(registrationApi.registerAccount(null,
                     recoveryPassword,
                     account.getAccountAttributes(null),
                     aciPreKeys,
                     recoveryPassword,
                     account.getAccountAttributes(null),
                     aciPreKeys,
@@ -221,8 +218,14 @@ public class RegistrationManagerImpl implements RegistrationManager {
 
     private boolean attemptReactivateAccount() {
         try {
 
     private boolean attemptReactivateAccount() {
         try {
-            final var accountManager = createAuthenticatedSignalServiceAccountManager();
-            accountManager.setAccountAttributes(account.getAccountAttributes(null));
+            final var dependencies = new SignalDependencies(serviceEnvironmentConfig,
+                    userAgent,
+                    account.getCredentialsProvider(),
+                    account.getSignalServiceDataStore(),
+                    null,
+                    new ReentrantSignalSessionLock());
+            handleResponseException(dependencies.getAccountApi()
+                    .setAccountAttributes(account.getAccountAttributes(null)));
             account.setRegistered(true);
             logger.info("Reactivated existing account, verify is not necessary.");
             if (newManagerListener != null) {
             account.setRegistered(true);
             logger.info("Reactivated existing account, verify is not necessary.");
             if (newManagerListener != null) {
@@ -241,17 +244,6 @@ public class RegistrationManagerImpl implements RegistrationManager {
         return false;
     }
 
         return false;
     }
 
-    private SignalServiceAccountManager createAuthenticatedSignalServiceAccountManager() {
-        final var clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration());
-        final var pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(),
-                account.getCredentialsProvider(),
-                userAgent,
-                clientZkOperations.getProfileOperations(),
-                ServiceConfig.AUTOMATIC_NETWORK_RETRY);
-        final var groupsV2Operations = new GroupsV2Operations(clientZkOperations, ServiceConfig.GROUP_MAX_SIZE);
-        return new SignalServiceAccountManager(pushServiceSocket, null, groupsV2Operations);
-    }
-
     private VerifyAccountResponse verifyAccountWithCode(
             final String sessionId,
             final String verificationCode,
     private VerifyAccountResponse verifyAccountWithCode(
             final String sessionId,
             final String verificationCode,
@@ -261,11 +253,11 @@ public class RegistrationManagerImpl implements RegistrationManager {
     ) throws IOException {
         final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
         try {
     ) throws IOException {
         final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
         try {
-            Utils.handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode));
+            handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode));
         } catch (AlreadyVerifiedException e) {
             // Already verified so can continue registering
         }
         } catch (AlreadyVerifiedException e) {
             // Already verified so can continue registering
         }
-        return Utils.handleResponseException(registrationApi.registerAccount(sessionId,
+        return handleResponseException(registrationApi.registerAccount(sessionId,
                 null,
                 account.getAccountAttributes(registrationLock),
                 aciPreKeys,
                 null,
                 account.getAccountAttributes(registrationLock),
                 aciPreKeys,
index 427c1025806d977f3ca33e5f9caefdd286781111..972b422f1339ec9f5a1995f180dccda8c915f57b 100644 (file)
@@ -13,7 +13,8 @@ import org.whispersystems.signalservice.api.SignalServiceDataStore;
 import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
 import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
 import org.whispersystems.signalservice.api.SignalSessionLock;
-import org.whispersystems.signalservice.api.SignalWebSocket;
+import org.whispersystems.signalservice.api.account.AccountApi;
+import org.whispersystems.signalservice.api.cds.CdsApi;
 import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
 import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
@@ -21,18 +22,18 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
 import org.whispersystems.signalservice.api.link.LinkDeviceApi;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.link.LinkDeviceApi;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi;
 import org.whispersystems.signalservice.api.registration.RegistrationApi;
 import org.whispersystems.signalservice.api.services.ProfileService;
 import org.whispersystems.signalservice.api.storage.StorageServiceApi;
 import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
 import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
 import org.whispersystems.signalservice.api.registration.RegistrationApi;
 import org.whispersystems.signalservice.api.services.ProfileService;
 import org.whispersystems.signalservice.api.storage.StorageServiceApi;
 import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
 import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
+import org.whispersystems.signalservice.api.username.UsernameApi;
 import org.whispersystems.signalservice.api.util.CredentialsProvider;
 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
 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.push.ProvisioningSocket;
+import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
 import org.whispersystems.signalservice.internal.push.PushServiceSocket;
 import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection;
 import org.whispersystems.signalservice.internal.push.PushServiceSocket;
 import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection;
-import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
@@ -58,6 +59,10 @@ public class SignalDependencies {
     private boolean allowStories = true;
 
     private SignalServiceAccountManager accountManager;
     private boolean allowStories = true;
 
     private SignalServiceAccountManager accountManager;
+    private AccountApi accountApi;
+    private RateLimitChallengeApi rateLimitChallengeApi;
+    private CdsApi cdsApi;
+    private UsernameApi usernameApi;
     private GroupsV2Api groupsV2Api;
     private RegistrationApi registrationApi;
     private LinkDeviceApi linkDeviceApi;
     private GroupsV2Api groupsV2Api;
     private RegistrationApi registrationApi;
     private LinkDeviceApi linkDeviceApi;
@@ -66,9 +71,9 @@ public class SignalDependencies {
     private ClientZkOperations clientZkOperations;
 
     private PushServiceSocket pushServiceSocket;
     private ClientZkOperations clientZkOperations;
 
     private PushServiceSocket pushServiceSocket;
-    private ProvisioningSocket provisioningSocket;
     private Network libSignalNetwork;
     private Network libSignalNetwork;
-    private SignalWebSocket signalWebSocket;
+    private SignalWebSocket.AuthenticatedWebSocket authenticatedSignalWebSocket;
+    private SignalWebSocket.UnauthenticatedWebSocket unauthenticatedSignalWebSocket;
     private SignalServiceMessageReceiver messageReceiver;
     private SignalServiceMessageSender messageSender;
 
     private SignalServiceMessageReceiver messageReceiver;
     private SignalServiceMessageSender messageSender;
 
@@ -103,7 +108,12 @@ public class SignalDependencies {
             this.registrationApi = null;
             this.secureValueRecovery = null;
         }
             this.registrationApi = null;
             this.secureValueRecovery = null;
         }
-        getSignalWebSocket().forceNewWebSockets();
+        if (this.authenticatedSignalWebSocket != null) {
+            this.authenticatedSignalWebSocket.forceNewWebSocket();
+        }
+        if (this.unauthenticatedSignalWebSocket != null) {
+            this.unauthenticatedSignalWebSocket.forceNewWebSocket();
+        }
     }
 
     /**
     }
 
     /**
@@ -130,12 +140,6 @@ public class SignalDependencies {
                         ServiceConfig.AUTOMATIC_NETWORK_RETRY));
     }
 
                         ServiceConfig.AUTOMATIC_NETWORK_RETRY));
     }
 
-    public ProvisioningSocket getProvisioningSocket() {
-        return getOrCreate(() -> provisioningSocket,
-                () -> provisioningSocket = new ProvisioningSocket(getServiceEnvironmentConfig().signalServiceConfiguration(),
-                        userAgent));
-    }
-
     public Network getLibSignalNetwork() {
         return getOrCreate(() -> libSignalNetwork, () -> {
             libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent);
     public Network getLibSignalNetwork() {
         return getOrCreate(() -> libSignalNetwork, () -> {
             libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent);
@@ -169,8 +173,8 @@ public class SignalDependencies {
 
     public SignalServiceAccountManager getAccountManager() {
         return getOrCreate(() -> accountManager,
 
     public SignalServiceAccountManager getAccountManager() {
         return getOrCreate(() -> accountManager,
-                () -> accountManager = new SignalServiceAccountManager(getPushServiceSocket(),
-                        getProvisioningSocket(),
+                () -> accountManager = new SignalServiceAccountManager(getAccountApi(),
+                        getPushServiceSocket(),
                         getGroupsV2Operations()));
     }
 
                         getGroupsV2Operations()));
     }
 
@@ -186,6 +190,23 @@ public class SignalDependencies {
                 ServiceConfig.GROUP_MAX_SIZE);
     }
 
                 ServiceConfig.GROUP_MAX_SIZE);
     }
 
+    public AccountApi getAccountApi() {
+        return getOrCreate(() -> accountApi, () -> accountApi = new AccountApi(getAuthenticatedSignalWebSocket()));
+    }
+
+    public RateLimitChallengeApi getRateLimitChallengeApi() {
+        return getOrCreate(() -> rateLimitChallengeApi,
+                () -> rateLimitChallengeApi = new RateLimitChallengeApi(getAuthenticatedSignalWebSocket()));
+    }
+
+    public CdsApi getCdsApi() {
+        return getOrCreate(() -> cdsApi, () -> cdsApi = new CdsApi(getAuthenticatedSignalWebSocket()));
+    }
+
+    public UsernameApi getUsernameApi() {
+        return getOrCreate(() -> usernameApi, () -> usernameApi = new UsernameApi(getUnauthenticatedSignalWebSocket()));
+    }
+
     public GroupsV2Api getGroupsV2Api() {
         return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api());
     }
     public GroupsV2Api getGroupsV2Api() {
         return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api());
     }
@@ -195,12 +216,14 @@ public class SignalDependencies {
     }
 
     public LinkDeviceApi getLinkDeviceApi() {
     }
 
     public LinkDeviceApi getLinkDeviceApi() {
-        return getOrCreate(() -> linkDeviceApi, () -> linkDeviceApi = new LinkDeviceApi(getPushServiceSocket()));
+        return getOrCreate(() -> linkDeviceApi,
+                () -> linkDeviceApi = new LinkDeviceApi(getAuthenticatedSignalWebSocket()));
     }
 
     private StorageServiceApi getStorageServiceApi() {
         return getOrCreate(() -> storageServiceApi,
     }
 
     private StorageServiceApi getStorageServiceApi() {
         return getOrCreate(() -> storageServiceApi,
-                () -> storageServiceApi = new StorageServiceApi(getPushServiceSocket()));
+                () -> storageServiceApi = new StorageServiceApi(getAuthenticatedSignalWebSocket(),
+                        getPushServiceSocket()));
     }
 
     public StorageServiceRepository getStorageServiceRepository() {
     }
 
     public StorageServiceRepository getStorageServiceRepository() {
@@ -223,33 +246,35 @@ public class SignalDependencies {
         return clientZkOperations.getProfileOperations();
     }
 
         return clientZkOperations.getProfileOperations();
     }
 
-    public SignalWebSocket getSignalWebSocket() {
-        return getOrCreate(() -> signalWebSocket, () -> {
+    public SignalWebSocket.AuthenticatedWebSocket getAuthenticatedSignalWebSocket() {
+        return getOrCreate(() -> authenticatedSignalWebSocket, () -> {
             final var timer = new UptimeSleepTimer();
             final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
             final var timer = new UptimeSleepTimer();
             final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
-            final var webSocketFactory = new WebSocketFactory() {
-                @Override
-                public WebSocketConnection createWebSocket() {
-                    return new OkHttpWebSocketConnection("normal",
-                            serviceEnvironmentConfig.signalServiceConfiguration(),
-                            Optional.of(credentialsProvider),
-                            userAgent,
-                            healthMonitor,
-                            allowStories);
-                }
 
 
-                @Override
-                public WebSocketConnection createUnidentifiedWebSocket() {
-                    return new OkHttpWebSocketConnection("unidentified",
-                            serviceEnvironmentConfig.signalServiceConfiguration(),
-                            Optional.empty(),
-                            userAgent,
-                            healthMonitor,
-                            allowStories);
-                }
-            };
-            signalWebSocket = new SignalWebSocket(webSocketFactory);
-            healthMonitor.monitor(signalWebSocket);
+            authenticatedSignalWebSocket = new SignalWebSocket.AuthenticatedWebSocket(() -> new OkHttpWebSocketConnection(
+                    "normal",
+                    serviceEnvironmentConfig.signalServiceConfiguration(),
+                    Optional.of(credentialsProvider),
+                    userAgent,
+                    healthMonitor,
+                    allowStories));
+            healthMonitor.monitor(authenticatedSignalWebSocket);
+        });
+    }
+
+    public SignalWebSocket.UnauthenticatedWebSocket getUnauthenticatedSignalWebSocket() {
+        return getOrCreate(() -> unauthenticatedSignalWebSocket, () -> {
+            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));
+            healthMonitor.monitor(unauthenticatedSignalWebSocket);
         });
     }
 
         });
     }
 
@@ -263,7 +288,8 @@ public class SignalDependencies {
                 () -> messageSender = new SignalServiceMessageSender(getPushServiceSocket(),
                         dataStore,
                         sessionLock,
                 () -> messageSender = new SignalServiceMessageSender(getPushServiceSocket(),
                         dataStore,
                         sessionLock,
-                        getSignalWebSocket(),
+                        getAuthenticatedSignalWebSocket(),
+                        getUnauthenticatedSignalWebSocket(),
                         Optional.empty(),
                         executor,
                         ServiceConfig.MAX_ENVELOPE_SIZE));
                         Optional.empty(),
                         executor,
                         ServiceConfig.MAX_ENVELOPE_SIZE));
@@ -281,7 +307,8 @@ public class SignalDependencies {
         return getOrCreate(() -> profileService,
                 () -> profileService = new ProfileService(getClientZkProfileOperations(),
                         getMessageReceiver(),
         return getOrCreate(() -> profileService,
                 () -> profileService = new ProfileService(getClientZkProfileOperations(),
                         getMessageReceiver(),
-                        getSignalWebSocket()));
+                        getAuthenticatedSignalWebSocket(),
+                        getUnauthenticatedSignalWebSocket()));
     }
 
     public SignalServiceCipher getCipher(ServiceIdType serviceIdType) {
     }
 
     public SignalServiceCipher getCipher(ServiceIdType serviceIdType) {
index 0fb1585a513af7f8e10ad5f08d46f68902250314..b5b855acd83099419711aa27fa361f6730be572a 100644 (file)
@@ -2,195 +2,155 @@ package org.asamk.signal.manager.internal;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.SignalWebSocket;
 import org.whispersystems.signalservice.api.util.Preconditions;
 import org.whispersystems.signalservice.api.util.SleepTimer;
 import org.whispersystems.signalservice.api.websocket.HealthMonitor;
 import org.whispersystems.signalservice.api.util.Preconditions;
 import org.whispersystems.signalservice.api.util.SleepTimer;
 import org.whispersystems.signalservice.api.websocket.HealthMonitor;
+import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
 import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
 import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection;
 
 import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
 import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection;
 
-import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 import io.reactivex.rxjava3.schedulers.Schedulers;
 import java.util.concurrent.TimeUnit;
 
 import io.reactivex.rxjava3.schedulers.Schedulers;
+import kotlin.Unit;
 
 
-/**
- * Monitors the health of the identified and unidentified WebSockets. If either one appears to be
- * unhealthy, will trigger restarting both.
- * <p>
- * The monitor is also responsible for sending heartbeats/keep-alive messages to prevent
- * timeouts.
- */
 final class SignalWebSocketHealthMonitor implements HealthMonitor {
 
     private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHealthMonitor.class);
 
 final class SignalWebSocketHealthMonitor implements HealthMonitor {
 
     private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHealthMonitor.class);
 
+    /**
+     * This is the amount of time in between sent keep alives. Must be greater than [KEEP_ALIVE_TIMEOUT]
+     */
     private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(OkHttpWebSocketConnection.KEEPALIVE_FREQUENCY_SECONDS);
     private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(OkHttpWebSocketConnection.KEEPALIVE_FREQUENCY_SECONDS);
-    private static final long MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE = KEEP_ALIVE_SEND_CADENCE * 3;
-
-    private SignalWebSocket signalWebSocket;
-    private final SleepTimer sleepTimer;
 
 
-    private volatile KeepAliveSender keepAliveSender;
+    /**
+     * This is the amount of time we will wait for a response to the keep alive before we consider the websockets dead.
+     * It is required that this value be less than [KEEP_ALIVE_SEND_CADENCE]
+     */
+    private static final long KEEP_ALIVE_TIMEOUT = TimeUnit.SECONDS.toMillis(20);
 
 
-    private final HealthState identified = new HealthState();
-    private final HealthState unidentified = new HealthState();
+    private final Executor executor = Executors.newSingleThreadExecutor();
+    private final SleepTimer sleepTimer;
+    private SignalWebSocket webSocket = null;
+    private volatile KeepAliveSender keepAliveSender = null;
+    private boolean needsKeepAlive = false;
+    private long lastKeepAliveReceived = 0;
 
     public SignalWebSocketHealthMonitor(SleepTimer sleepTimer) {
         this.sleepTimer = sleepTimer;
     }
 
 
     public SignalWebSocketHealthMonitor(SleepTimer sleepTimer) {
         this.sleepTimer = sleepTimer;
     }
 
-    public void monitor(SignalWebSocket signalWebSocket) {
-        Preconditions.checkNotNull(signalWebSocket);
-        Preconditions.checkArgument(this.signalWebSocket == null, "monitor can only be called once");
-
-        this.signalWebSocket = signalWebSocket;
-
-        //noinspection ResultOfMethodCallIgnored
-        signalWebSocket.getWebSocketState()
-                .subscribeOn(Schedulers.computation())
-                .observeOn(Schedulers.computation())
-                .distinctUntilChanged()
-                .subscribe(s -> onStateChange(s, identified));
-
-        //noinspection ResultOfMethodCallIgnored
-        signalWebSocket.getUnidentifiedWebSocketState()
-                .subscribeOn(Schedulers.computation())
-                .observeOn(Schedulers.computation())
-                .distinctUntilChanged()
-                .subscribe(s -> onStateChange(s, unidentified));
-    }
+    void monitor(SignalWebSocket webSocket) {
+        Preconditions.checkNotNull(webSocket);
+        Preconditions.checkArgument(this.webSocket == null, "monitor can only be called once");
 
 
-    private synchronized void onStateChange(WebSocketConnectionState connectionState, HealthState healthState) {
-        switch (connectionState) {
-            case CONNECTED -> logger.debug("WebSocket is now connected");
-            case AUTHENTICATION_FAILED -> logger.debug("WebSocket authentication failed");
-            case FAILED -> logger.debug("WebSocket connection failed");
-        }
+        executor.execute(() -> {
 
 
-        healthState.needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED;
+            this.webSocket = webSocket;
 
 
-        if (keepAliveSender == null && isKeepAliveNecessary()) {
-            keepAliveSender = new KeepAliveSender();
-            keepAliveSender.start();
-        } else if (keepAliveSender != null && !isKeepAliveNecessary()) {
-            keepAliveSender.shutdown();
-            keepAliveSender = null;
-        }
+            webSocket.getState()
+                    .subscribeOn(Schedulers.computation())
+                    .observeOn(Schedulers.computation())
+                    .distinctUntilChanged()
+                    .subscribe(this::onStateChanged);
+
+            webSocket.setKeepAliveChangedListener(this::updateKeepAliveSenderStatus);
+        });
+    }
+
+    private void onStateChanged(WebSocketConnectionState connectionState) {
+        executor.execute(() -> {
+            needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED;
+
+            updateKeepAliveSenderStatus();
+        });
     }
 
     @Override
     public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) {
     }
 
     @Override
     public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) {
-        if (isIdentifiedWebSocket) {
-            identified.lastKeepAliveReceived = System.currentTimeMillis();
-        } else {
-            unidentified.lastKeepAliveReceived = System.currentTimeMillis();
-        }
+        final var keepAliveTime = System.currentTimeMillis();
+        executor.execute(() -> lastKeepAliveReceived = keepAliveTime);
     }
 
     @Override
     public void onMessageError(int status, boolean isIdentifiedWebSocket) {
     }
 
     @Override
     public void onMessageError(int status, boolean isIdentifiedWebSocket) {
-        if (status == 409) {
-            HealthState healthState = (isIdentifiedWebSocket ? identified : unidentified);
-            if (healthState.mismatchErrorTracker.addSample(System.currentTimeMillis())) {
-                logger.warn("Received too many mismatch device errors, forcing new websockets.");
-                signalWebSocket.forceNewWebSockets();
-                signalWebSocket.connect();
-            }
-        }
     }
 
     }
 
-    private boolean isKeepAliveNecessary() {
-        return identified.needsKeepAlive || unidentified.needsKeepAlive;
+    private Unit updateKeepAliveSenderStatus() {
+        if (keepAliveSender == null && sendKeepAlives()) {
+            keepAliveSender = new KeepAliveSender();
+            keepAliveSender.start();
+        } else if (keepAliveSender != null && !sendKeepAlives()) {
+            keepAliveSender.shutdown();
+            keepAliveSender = null;
+        }
+        return Unit.INSTANCE;
     }
 
     }
 
-    private static class HealthState {
-
-        private final HttpErrorTracker mismatchErrorTracker = new HttpErrorTracker(5, TimeUnit.MINUTES.toMillis(1));
-
-        private volatile boolean needsKeepAlive;
-        private volatile long lastKeepAliveReceived;
+    private boolean sendKeepAlives() {
+        return needsKeepAlive && webSocket != null && webSocket.getShouldSendKeepAlives();
     }
 
     /**
     }
 
     /**
-     * Sends periodic heartbeats/keep-alives over both WebSockets to prevent connection timeouts. If
-     * either WebSocket fails 3 times to get a return heartbeat both are forced to be recreated.
+     * Sends periodic heartbeats/keep-alives over the WebSocket to prevent connection timeouts. If
+     * the WebSocket fails to get a return heartbeat after [KEEP_ALIVE_TIMEOUT] seconds, it is forced to be recreated.
      */
      */
-    private class KeepAliveSender extends Thread {
+    private final class KeepAliveSender extends Thread {
 
         private volatile boolean shouldKeepRunning = true;
 
 
         private volatile boolean shouldKeepRunning = true;
 
+        @Override
         public void run() {
         public void run() {
-            identified.lastKeepAliveReceived = System.currentTimeMillis();
-            unidentified.lastKeepAliveReceived = System.currentTimeMillis();
+            logger.debug("[KeepAliveSender({})] started", this.threadId());
+            lastKeepAliveReceived = System.currentTimeMillis();
 
 
-            while (shouldKeepRunning && isKeepAliveNecessary()) {
+            var keepAliveSendTime = System.currentTimeMillis();
+            while (shouldKeepRunning && sendKeepAlives()) {
                 try {
                 try {
-                    sleepTimer.sleep(KEEP_ALIVE_SEND_CADENCE);
-
-                    if (shouldKeepRunning && isKeepAliveNecessary()) {
-                        long keepAliveRequiredSinceTime = System.currentTimeMillis()
-                                - MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE;
-
-                        if (identified.lastKeepAliveReceived < keepAliveRequiredSinceTime
-                                || unidentified.lastKeepAliveReceived < keepAliveRequiredSinceTime) {
-                            logger.warn("Missed keep alives, identified last: "
-                                    + identified.lastKeepAliveReceived
-                                    + " unidentified last: "
-                                    + unidentified.lastKeepAliveReceived
-                                    + " needed by: "
-                                    + keepAliveRequiredSinceTime);
-                            signalWebSocket.forceNewWebSockets();
-                            signalWebSocket.connect();
-                        } else {
-                            signalWebSocket.sendKeepAlive();
+                    final var nextKeepAliveSendTime = keepAliveSendTime + KEEP_ALIVE_SEND_CADENCE;
+                    sleepUntil(nextKeepAliveSendTime);
+
+                    if (shouldKeepRunning && sendKeepAlives()) {
+                        keepAliveSendTime = System.currentTimeMillis();
+                        webSocket.sendKeepAlive();
+                    }
+
+                    final var responseRequiredTime = keepAliveSendTime + KEEP_ALIVE_TIMEOUT;
+                    sleepUntil(responseRequiredTime);
+
+                    if (shouldKeepRunning && sendKeepAlives()) {
+                        if (lastKeepAliveReceived < keepAliveSendTime) {
+                            logger.debug("Missed keep alive, last: {} needed by: {}",
+                                    lastKeepAliveReceived,
+                                    responseRequiredTime);
+                            webSocket.forceNewWebSocket();
                         }
                     }
                 } catch (Throwable e) {
                         }
                     }
                 } catch (Throwable e) {
-                    logger.warn("Error occurred in KeepAliveSender, ignoring ...", e);
+                    logger.warn("Keep alive sender failed", e);
                 }
             }
                 }
             }
+            logger.debug("[KeepAliveSender({})] ended", threadId());
         }
 
         }
 
-        public void shutdown() {
-            shouldKeepRunning = false;
-        }
-    }
-
-    private static final class HttpErrorTracker {
-
-        private final long[] timestamps;
-        private final long errorTimeRange;
-
-        public HttpErrorTracker(int samples, long errorTimeRange) {
-            this.timestamps = new long[samples];
-            this.errorTimeRange = errorTimeRange;
-        }
-
-        public synchronized boolean addSample(long now) {
-            long errorsMustBeAfter = now - errorTimeRange;
-            int count = 1;
-            int minIndex = 0;
-
-            for (int i = 0; i < timestamps.length; i++) {
-                if (timestamps[i] < errorsMustBeAfter) {
-                    timestamps[i] = 0;
-                } else if (timestamps[i] != 0) {
-                    count++;
-                }
-
-                if (timestamps[i] < timestamps[minIndex]) {
-                    minIndex = i;
+        void sleepUntil(long timeMillis) {
+            while (System.currentTimeMillis() < timeMillis) {
+                final var waitTime = timeMillis - System.currentTimeMillis();
+                if (waitTime > 0) {
+                    try {
+                        sleepTimer.sleep(waitTime);
+                    } catch (InterruptedException e) {
+                        logger.warn("WebSocket health monitor interrupted", e);
+                    }
                 }
             }
                 }
             }
+        }
 
 
-            timestamps[minIndex] = now;
-
-            if (count >= timestamps.length) {
-                Arrays.fill(timestamps, 0);
-                return true;
-            }
-            return false;
+        void shutdown() {
+            shouldKeepRunning = false;
         }
     }
 }
         }
     }
 }
+
index a0fa301224c5b6a4a47597addc9da021a5d5852b..7be4a401c38db593a8527b87261af76e10aeb53e 100644 (file)
@@ -1,6 +1,7 @@
 package org.asamk.signal.manager.storage.accounts;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 package org.asamk.signal.manager.storage.accounts;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
 
 import org.asamk.signal.manager.api.Pair;
 import org.asamk.signal.manager.api.ServiceEnvironment;
 
 import org.asamk.signal.manager.api.Pair;
 import org.asamk.signal.manager.api.ServiceEnvironment;
@@ -10,7 +11,6 @@ import org.asamk.signal.manager.util.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -181,7 +181,7 @@ public class AccountsStore {
         return Arrays.stream(files)
                 .filter(File::isFile)
                 .map(File::getName)
         return Arrays.stream(files)
                 .filter(File::isFile)
                 .map(File::getName)
-                .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
+                .filter(file -> PhoneNumberUtil.getInstance().isPossibleNumber(file, null))
                 .collect(Collectors.toSet());
     }
 
                 .collect(Collectors.toSet());
     }
 
index 2863874b0772b28508c096c854bb745c802d24f3..d4841c7a1a849fe70fda0bc1db15375f9ee3942a 100644 (file)
@@ -195,7 +195,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
                 .hidden(remote.hidden)
                 .pniSignatureVerified(remote.pniSignatureVerified || local.pniSignatureVerified)
                 .nickname(remote.nickname)
                 .hidden(remote.hidden)
                 .pniSignatureVerified(remote.pniSignatureVerified || local.pniSignatureVerified)
                 .nickname(remote.nickname)
-                .note(remote.note);
+                .note(remote.note)
+                .avatarColor(remote.avatarColor);
         final var merged = mergedBuilder.build();
 
         final var matchesRemote = doProtosMatch(merged, remote);
         final var merged = mergedBuilder.build();
 
         final var matchesRemote = doProtosMatch(merged, remote);
index af77e857cf356da1cd05b182ddfcb6f74574b342..3c014463bd1712d821f4681ea6a3058b3c6f8a2b 100644 (file)
@@ -62,7 +62,8 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
                 .mutedUntilTimestamp(remote.mutedUntilTimestamp)
                 .dontNotifyForMentionsIfMuted(remote.dontNotifyForMentionsIfMuted)
                 .hideStory(remote.hideStory)
                 .mutedUntilTimestamp(remote.mutedUntilTimestamp)
                 .dontNotifyForMentionsIfMuted(remote.dontNotifyForMentionsIfMuted)
                 .hideStory(remote.hideStory)
-                .storySendMode(remote.storySendMode);
+                .storySendMode(remote.storySendMode)
+                .avatarColor(remote.avatarColor);
         final var merged = mergedBuilder.build();
 
         final var matchesRemote = doProtosMatch(merged, remote);
         final var merged = mergedBuilder.build();
 
         final var matchesRemote = doProtosMatch(merged, remote);
diff --git a/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java b/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java
new file mode 100644 (file)
index 0000000..cc0c650
--- /dev/null
@@ -0,0 +1,59 @@
+package org.asamk.signal.manager.util;
+
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+
+import org.asamk.signal.manager.api.InvalidNumberException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PhoneNumberFormatter {
+
+    private static final Logger logger = LoggerFactory.getLogger(PhoneNumberFormatter.class);
+
+    private static String impreciseFormatNumber(String number, String localNumber) {
+        number = number.replaceAll("[^0-9+]", "");
+
+        if (number.charAt(0) == '+') return number;
+
+        if (localNumber.charAt(0) == '+') localNumber = localNumber.substring(1);
+
+        if (localNumber.length() == number.length() || number.length() > localNumber.length()) return "+" + number;
+
+        int difference = localNumber.length() - number.length();
+
+        return "+" + localNumber.substring(0, difference) + number;
+    }
+
+    public static String formatNumber(String number, String localNumber) throws InvalidNumberException {
+        if (number == null) {
+            throw new InvalidNumberException("Null String passed as number.");
+        }
+
+        if (number.contains("@")) {
+            throw new InvalidNumberException("Possible attempt to use email address.");
+        }
+
+        number = number.replaceAll("[^0-9+]", "");
+
+        if (number.isEmpty()) {
+            throw new InvalidNumberException("No valid characters found.");
+        }
+
+        try {
+            PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+            PhoneNumber localNumberObject = util.parse(localNumber, null);
+
+            String localCountryCode = util.getRegionCodeForNumber(localNumberObject);
+            logger.trace("Got local CC: {}", localCountryCode);
+
+            PhoneNumber numberObject = util.parse(number, localCountryCode);
+            return util.format(numberObject, PhoneNumberFormat.E164);
+        } catch (NumberParseException e) {
+            logger.debug("{}: {}", e.getClass().getSimpleName(), e.getMessage());
+            return impreciseFormatNumber(number, localNumber);
+        }
+    }
+}
index b90a787340484114742db9ba72cf5f385bcc63ee..9d97ed7a44a1c66fc4cd79fff53cb11e8648ac1c 100644 (file)
@@ -162,7 +162,11 @@ public class Utils {
                 throw new IOException(throwableOptional);
             }
         }
                 throw new IOException(throwableOptional);
             }
         }
-        return response.successOrThrow();
+        try {
+            return response.successOrThrow();
+        } catch (Throwable e) {
+            throw new AssertionError(e);
+        }
     }
 
     public static ByteString firstNonEmpty(ByteString... strings) {
     }
 
     public static ByteString firstNonEmpty(ByteString... strings) {