package org.asamk.signal.manager.helper;
-import org.asamk.signal.manager.DeviceLinkInfo;
-import org.asamk.signal.manager.SignalDependencies;
import org.asamk.signal.manager.api.CaptchaRequiredException;
+import org.asamk.signal.manager.api.DeviceLinkUrl;
import org.asamk.signal.manager.api.IncorrectPinException;
-import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.PinLockedException;
-import org.asamk.signal.manager.config.ServiceConfig;
+import org.asamk.signal.manager.api.RateLimitException;
+import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
+import org.asamk.signal.manager.internal.SignalDependencies;
+import org.asamk.signal.manager.jobs.SyncStorageJob;
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.core.util.Base64;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
+import org.signal.libsignal.protocol.SignalProtocolAddress;
+import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
+import org.signal.libsignal.protocol.util.KeyHelper;
+import org.signal.libsignal.usernames.BaseUsernameException;
+import org.signal.libsignal.usernames.Username;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
-import org.whispersystems.signalservice.api.push.ACI;
-import org.whispersystems.signalservice.api.push.PNI;
+import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
+import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse;
+import org.whispersystems.signalservice.api.push.ServiceId.ACI;
+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.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.UsernameIsNotReservedException;
+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.internal.push.DeviceLimitExceededException;
+import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
+import org.whispersystems.signalservice.internal.push.SyncMessage;
+import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.Objects;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
+import okio.ByteString;
+
+import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
+import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
+
public class AccountHelper {
- private final static Logger logger = LoggerFactory.getLogger(AccountHelper.class);
+ private static final Logger logger = LoggerFactory.getLogger(AccountHelper.class);
private final Context context;
private final SignalAccount account;
}
try {
updateAccountAttributes();
- context.getPreKeyHelper().refreshPreKeysIfNecessary();
+ if (account.getPreviousStorageVersion() < 9) {
+ context.getPreKeyHelper().forceRefreshPreKeys();
+ } else {
+ context.getPreKeyHelper().refreshPreKeysIfNecessary();
+ }
if (account.getAci() == null || account.getPni() == null) {
checkWhoAmiI();
}
if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
- context.getSyncHelper().requestSyncPniIdentity();
+ throw new IOException("Missing PNI identity key, relinking required");
}
- if (account.getPreviousStorageVersion() < 4
+ if (account.getPreviousStorageVersion() < 10
&& account.isPrimaryDevice()
&& account.getRegistrationLockPin() != null) {
migrateRegistrationPin();
}
+ if (account.getUsername() != null && account.getUsernameLink() == null) {
+ try {
+ tryToSetUsernameLink(new Username(account.getUsername()));
+ } catch (BaseUsernameException e) {
+ logger.debug("Invalid local username");
+ }
+ }
} catch (DeprecatedVersionException e) {
logger.debug("Signal-Server returned deprecated version exception", e);
throw e;
public void checkWhoAmiI() throws IOException {
final var whoAmI = dependencies.getAccountManager().getWhoAmI();
final var number = whoAmI.getNumber();
- final var aci = ACI.parseOrNull(whoAmI.getAci());
- final var pni = PNI.parseOrNull(whoAmI.getPni());
+ final var aci = ACI.parseOrThrow(whoAmI.getAci());
+ final var pni = PNI.parseOrThrow(whoAmI.getPni());
if (number.equals(account.getNumber()) && aci.equals(account.getAci()) && pni.equals(account.getPni())) {
return;
}
account.setNumber(number);
account.setAci(aci);
account.setPni(pni);
- if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null && account.getPni() != null) {
+ if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
}
account.getRecipientTrustedResolver().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
- // TODO check and update remote storage
context.getUnidentifiedAccessHelper().rotateSenderCertificates();
dependencies.resetAfterAddressChange();
- dependencies.getSignalWebSocket().forceNewWebSockets();
+ context.getGroupV2Helper().clearAuthCredentialCache();
context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), account.getAci());
+ context.getJobExecutor().enqueueJob(new SyncStorageJob());
}
public void setPni(
final PNI updatedPni,
final IdentityKeyPair pniIdentityKeyPair,
+ final String number,
+ final int localPniRegistrationId,
final SignedPreKeyRecord pniSignedPreKey,
- final int localPniRegistrationId
+ final KyberPreKeyRecord lastResortKyberPreKey
) throws IOException {
- account.setPni(updatedPni, pniIdentityKeyPair, pniSignedPreKey, localPniRegistrationId);
+ updateSelfIdentifiers(number != null ? number : account.getNumber(), account.getAci(), updatedPni);
+ account.setNewPniIdentity(pniIdentityKeyPair, pniSignedPreKey, lastResortKyberPreKey, localPniRegistrationId);
context.getPreKeyHelper().refreshPreKeysIfNecessary(ServiceIdType.PNI);
- if (account.getPni() == null || !account.getPni().equals(updatedPni)) {
- context.getGroupV2Helper().clearAuthCredentialCache();
- }
}
public void startChangeNumber(
- String newNumber, String captcha, boolean voiceVerification
- ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
+ String newNumber,
+ boolean voiceVerification,
+ String captcha
+ ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException {
final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
- NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
+ final var registrationApi = accountManager.getRegistrationApi();
+ String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi,
+ account.getSessionId(newNumber),
+ id -> account.setSessionId(newNumber, id),
+ voiceVerification,
+ captcha);
+ NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
}
public void finishChangeNumber(
- String newNumber, String verificationCode, String pin
+ String newNumber,
+ String verificationCode,
+ String pin
) throws IncorrectPinException, PinLockedException, IOException {
- // TODO create new PNI identity key
- final List<OutgoingPushMessage> deviceMessages = null;
- final Map<String, SignedPreKeyEntity> devicePniSignedPreKeys = null;
- final Map<String, Integer> pniRegistrationIds = null;
- final var result = NumberVerificationUtils.verifyNumber(verificationCode,
+ for (var attempts = 0; attempts < 5; attempts++) {
+ try {
+ finishChangeNumberInternal(newNumber, verificationCode, pin);
+ break;
+ } catch (MismatchedDevicesException e) {
+ logger.debug("Change number failed with mismatched devices, retrying.");
+ try {
+ dependencies.getMessageSender().handleChangeNumberMismatchDevices(e.getMismatchedDevices());
+ } catch (UntrustedIdentityException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+ }
+ }
+
+ private void finishChangeNumberInternal(
+ String newNumber,
+ String verificationCode,
+ String pin
+ ) throws IncorrectPinException, PinLockedException, IOException {
+ final var pniIdentity = KeyUtils.generateIdentityKeyPair();
+ final var encryptedDeviceMessages = new ArrayList<OutgoingPushMessage>();
+ final var devicePniSignedPreKeys = new HashMap<Integer, SignedPreKeyEntity>();
+ final var devicePniLastResortKyberPreKeys = new HashMap<Integer, KyberPreKeyEntity>();
+ final var pniRegistrationIds = new HashMap<Integer, Integer>();
+
+ final var selfDeviceId = account.getDeviceId();
+ SyncMessage.PniChangeNumber selfChangeNumber = null;
+
+ final var deviceIds = new ArrayList<Integer>();
+ deviceIds.add(SignalServiceAddress.DEFAULT_DEVICE_ID);
+ final var aci = account.getAci();
+ final var accountDataStore = account.getSignalServiceDataStore().aci();
+ final var subDeviceSessions = accountDataStore.getSubDeviceSessions(aci.toString())
+ .stream()
+ .filter(deviceId -> accountDataStore.containsSession(new SignalProtocolAddress(aci.toString(),
+ deviceId)))
+ .toList();
+ deviceIds.addAll(subDeviceSessions);
+
+ final var messageSender = dependencies.getMessageSender();
+ for (final var deviceId : deviceIds) {
+ // Signed Prekey
+ final SignedPreKeyRecord signedPreKeyRecord;
+ try {
+ signedPreKeyRecord = KeyUtils.generateSignedPreKeyRecord(KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID),
+ pniIdentity.getPrivateKey());
+ final var signedPreKeyEntity = new SignedPreKeyEntity(signedPreKeyRecord.getId(),
+ signedPreKeyRecord.getKeyPair().getPublicKey(),
+ signedPreKeyRecord.getSignature());
+ devicePniSignedPreKeys.put(deviceId, signedPreKeyEntity);
+ } catch (InvalidKeyException e) {
+ throw new AssertionError("unexpected invalid key", e);
+ }
+
+ // Last-resort kyber prekey
+ final KyberPreKeyRecord lastResortKyberPreKeyRecord;
+ try {
+ lastResortKyberPreKeyRecord = KeyUtils.generateKyberPreKeyRecord(KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID),
+ pniIdentity.getPrivateKey());
+ final var kyberPreKeyEntity = new KyberPreKeyEntity(lastResortKyberPreKeyRecord.getId(),
+ lastResortKyberPreKeyRecord.getKeyPair().getPublicKey(),
+ lastResortKyberPreKeyRecord.getSignature());
+ devicePniLastResortKyberPreKeys.put(deviceId, kyberPreKeyEntity);
+ } catch (InvalidKeyException e) {
+ throw new AssertionError("unexpected invalid key", e);
+ }
+
+ // Registration Id
+ var pniRegistrationId = -1;
+ while (pniRegistrationId < 0 || pniRegistrationIds.containsValue(pniRegistrationId)) {
+ pniRegistrationId = KeyHelper.generateRegistrationId(false);
+ }
+ pniRegistrationIds.put(deviceId, pniRegistrationId);
+
+ // Device Message
+ final var pniChangeNumber = new SyncMessage.PniChangeNumber.Builder().identityKeyPair(ByteString.of(
+ pniIdentity.serialize()))
+ .signedPreKey(ByteString.of(signedPreKeyRecord.serialize()))
+ .lastResortKyberPreKey(ByteString.of(lastResortKyberPreKeyRecord.serialize()))
+ .registrationId(pniRegistrationId)
+ .newE164(newNumber)
+ .build();
+
+ if (deviceId == selfDeviceId) {
+ selfChangeNumber = pniChangeNumber;
+ } else {
+ try {
+ final var message = messageSender.getEncryptedSyncPniInitializeDeviceMessage(deviceId,
+ pniChangeNumber);
+ encryptedDeviceMessages.add(message);
+ } catch (UntrustedIdentityException | IOException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ final var sessionId = account.getSessionId(newNumber);
+ final var result = NumberVerificationUtils.verifyNumber(sessionId,
+ verificationCode,
pin,
context.getPinHelper(),
- (verificationCode1, registrationLock) -> dependencies.getAccountManager()
- .changeNumber(new ChangePhoneNumberRequest(newNumber,
- verificationCode1,
- registrationLock,
- account.getPniIdentityKeyPair().getPublicKey(),
- deviceMessages,
- devicePniSignedPreKeys,
- pniRegistrationIds)));
- // TODO handle response
- updateSelfIdentifiers(newNumber, account.getAci(), PNI.parseOrThrow(result.first().getPni()));
+ (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
+ }
+ return handleResponseException(accountApi.changeNumber(new ChangePhoneNumberRequest(sessionId1,
+ null,
+ newNumber,
+ registrationLock,
+ pniIdentity.getPublicKey(),
+ encryptedDeviceMessages,
+ Utils.mapKeys(devicePniSignedPreKeys, Object::toString),
+ Utils.mapKeys(devicePniLastResortKyberPreKeys, Object::toString),
+ Utils.mapKeys(pniRegistrationIds, Object::toString))));
+ });
+
+ final var updatePni = PNI.parseOrThrow(result.first().getPni());
+ if (updatePni.equals(account.getPni())) {
+ logger.debug("PNI is unchanged after change number");
+ return;
+ }
+
+ handlePniChangeNumberMessage(selfChangeNumber, updatePni);
+ }
+
+ public void handlePniChangeNumberMessage(final SyncMessage.PniChangeNumber pniChangeNumber, final PNI updatedPni) {
+ if (pniChangeNumber.identityKeyPair != null
+ && pniChangeNumber.registrationId != null
+ && pniChangeNumber.signedPreKey != null) {
+ logger.debug("New PNI: {}", updatedPni);
+ try {
+ setPni(updatedPni,
+ new IdentityKeyPair(pniChangeNumber.identityKeyPair.toByteArray()),
+ pniChangeNumber.newE164,
+ pniChangeNumber.registrationId,
+ new SignedPreKeyRecord(pniChangeNumber.signedPreKey.toByteArray()),
+ pniChangeNumber.lastResortKyberPreKey != null
+ ? new KyberPreKeyRecord(pniChangeNumber.lastResortKyberPreKey.toByteArray())
+ : null);
+ } catch (Exception e) {
+ logger.warn("Failed to handle change number message", e);
+ }
+ }
+ }
+
+ public static final int USERNAME_MIN_LENGTH = 3;
+ public static final int USERNAME_MAX_LENGTH = 32;
+
+ public void reserveUsernameFromNickname(String nickname) throws IOException, BaseUsernameException {
+ final var currentUsername = account.getUsername();
+ if (currentUsername != null) {
+ final var currentNickname = currentUsername.substring(0, currentUsername.indexOf('.'));
+ if (currentNickname.equals(nickname)) {
+ try {
+ refreshCurrentUsername();
+ return;
+ } catch (IOException | BaseUsernameException e) {
+ logger.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
+ }
+ }
+ }
+
+ final var candidates = Username.candidatesFrom(nickname, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH);
+ reserveUsername(candidates);
+ }
+
+ public void reserveExactUsername(String username) throws IOException, BaseUsernameException {
+ final var currentUsername = account.getUsername();
+ if (currentUsername != null) {
+ if (currentUsername.equals(username)) {
+ try {
+ refreshCurrentUsername();
+ return;
+ } catch (IOException | BaseUsernameException e) {
+ logger.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
+ }
+ }
+ }
+
+ final var candidates = List.of(new Username(username));
+ reserveUsername(candidates);
+ }
+
+ private void reserveUsername(final List<Username> candidates) throws IOException {
+ final var candidateHashes = new ArrayList<String>();
+ for (final var candidate : candidates) {
+ candidateHashes.add(Base64.encodeUrlSafeWithoutPadding(candidate.getHash()));
+ }
+
+ 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.");
+ throw new IOException("Unexpected username response");
+ }
+
+ logger.debug("[reserveUsername] Successfully reserved username.");
+ final var username = candidates.get(hashIndex);
+
+ final var linkComponents = confirmUsernameAndCreateNewLink(username);
+ account.setUsername(username.getUsername());
+ account.setUsernameLink(linkComponents);
+ account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
+ account.getRecipientStore().rotateSelfStorageId();
+ 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) {
+ return;
+ }
+
+ final var whoAmIResponse = dependencies.getAccountManager().getWhoAmI();
+ final var serverUsernameHash = whoAmIResponse.getUsernameHash();
+ final var hasServerUsername = !isEmpty(serverUsernameHash);
+ final var username = new Username(localUsername);
+ final var localUsernameHash = Base64.encodeUrlSafeWithoutPadding(username.getHash());
+
+ if (!hasServerUsername) {
+ logger.debug("No remote username is set.");
+ }
+
+ if (!Objects.equals(localUsernameHash, serverUsernameHash)) {
+ logger.debug("Local username hash does not match server username hash.");
+ }
+
+ if (!hasServerUsername || !Objects.equals(localUsernameHash, serverUsernameHash)) {
+ logger.debug("Attempting to resynchronize username.");
+ try {
+ tryReserveConfirmUsername(username);
+ } catch (UsernameMalformedException | UsernameTakenException | UsernameIsNotReservedException e) {
+ logger.debug("[confirmUsername] Failed to reserve confirm username: {} ({})",
+ e.getMessage(),
+ e.getClass().getSimpleName());
+ account.setUsername(null);
+ account.setUsernameLink(null);
+ account.getRecipientStore().rotateSelfStorageId();
+ throw e;
+ }
+ } else {
+ logger.debug("Username already set, not refreshing.");
+ }
+ }
+
+ private void tryReserveConfirmUsername(final Username username) throws IOException {
+ final var usernameLink = account.getUsernameLink();
+
+ if (usernameLink == null) {
+ handleResponseException(dependencies.getAccountApi()
+ .reserveUsername(List.of(Base64.encodeUrlSafeWithoutPadding(username.getHash()))));
+ logger.debug("[reserveUsername] Successfully reserved existing username.");
+ final var linkComponents = confirmUsernameAndCreateNewLink(username);
+ account.setUsernameLink(linkComponents);
+ logger.debug("[confirmUsername] Successfully confirmed existing username.");
+ } else {
+ final var linkComponents = reclaimUsernameAndLink(username, usernameLink);
+ account.setUsernameLink(linkComponents);
+ logger.debug("[confirmUsername] Successfully reclaimed existing username and link.");
+ }
+ account.getRecipientStore().rotateSelfStorageId();
+ }
+
+ private void tryToSetUsernameLink(Username username) {
+ for (var i = 1; i < 4; i++) {
+ try {
+ final var linkComponents = createUsernameLink(username);
+ account.setUsernameLink(linkComponents);
+ break;
+ } catch (IOException e) {
+ logger.debug("[tryToSetUsernameLink] Failed with IOException on attempt {}/3", i, e);
+ }
+ }
+ }
+
+ public void deleteUsername() throws IOException {
+ handleResponseException(dependencies.getAccountApi().deleteUsername());
+ account.setUsernameLink(null);
+ account.setUsername(null);
+ logger.debug("[deleteUsername] Successfully deleted the username.");
}
public void setDeviceName(String deviceName) {
}
public void updateAccountAttributes() throws IOException {
- dependencies.getAccountManager()
- .setAccountAttributes(null,
- account.getLocalRegistrationId(),
- true,
- null,
- account.getRegistrationLock(),
- account.getSelfUnidentifiedAccessKey(),
- account.isUnrestrictedUnidentifiedAccess(),
- ServiceConfig.capabilities,
- account.isDiscoverableByPhoneNumber(),
- account.getEncryptedDeviceName(),
- account.getLocalPniRegistrationId());
- }
-
- public void addDevice(DeviceLinkInfo deviceLinkInfo) throws IOException, InvalidDeviceLinkException {
- var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
+ handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null)));
+ }
+ public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException {
+ final var linkDeviceApi = dependencies.getLinkDeviceApi();
+ final LinkedDeviceVerificationCodeResponse verificationCode;
try {
- dependencies.getAccountManager()
- .addDevice(deviceLinkInfo.deviceIdentifier(),
- deviceLinkInfo.deviceKey(),
- account.getAciIdentityKeyPair(),
- account.getPniIdentityKeyPair(),
- account.getProfileKey(),
- verificationCode);
- } catch (InvalidKeyException e) {
- throw new InvalidDeviceLinkException("Invalid device link", e);
+ verificationCode = handleResponseException(linkDeviceApi.getDeviceVerificationCode());
+ } catch (DeviceLimitExceededException e) {
+ throw new org.asamk.signal.manager.api.DeviceLimitExceededException("Too many linked devices", e);
}
+
+ handleResponseException(dependencies.getLinkDeviceApi()
+ .linkDevice(account.getNumber(),
+ account.getAci(),
+ account.getPni(),
+ deviceLinkInfo.deviceIdentifier(),
+ deviceLinkInfo.deviceKey(),
+ account.getAciIdentityKeyPair(),
+ account.getPniIdentityKeyPair(),
+ account.getProfileKey(),
+ account.getOrCreateAccountEntropyPool(),
+ account.getOrCreatePinMasterKey(),
+ account.getOrCreateMediaRootBackupKey(),
+ verificationCode.getVerificationCode(),
+ null));
account.setMultiDevice(true);
+ context.getJobExecutor().enqueueJob(new SyncStorageJob());
}
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);
}
var masterKey = account.getOrCreatePinMasterKey();
context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
+ handleResponseException(dependencies.getAccountApi()
+ .enableRegistrationLock(masterKey.deriveRegistrationLock()));
}
public void setRegistrationPin(String pin) throws IOException {
var masterKey = account.getOrCreatePinMasterKey();
context.getPinHelper().setRegistrationLockPin(pin, masterKey);
+ handleResponseException(dependencies.getAccountApi()
+ .enableRegistrationLock(masterKey.deriveRegistrationLock()));
account.setRegistrationLockPin(pin);
+ updateAccountAttributes();
}
public void removeRegistrationPin() throws IOException {
// Remove KBS Pin
context.getPinHelper().removeRegistrationLockPin();
+ handleResponseException(dependencies.getAccountApi().disableRegistrationLock());
account.setRegistrationLockPin(null);
}
// 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.setRegistrationLockPin(null);
- dependencies.getAccountManager().deleteAccount();
+ handleResponseException(dependencies.getAccountApi().deleteAccount());
account.setRegistered(false);
unregisteredListener.call();