import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.Group;
+import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
-import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.ContactHelper;
import org.asamk.signal.manager.helper.GroupHelper;
import org.asamk.signal.manager.helper.GroupV2Helper;
+import org.asamk.signal.manager.helper.IdentityHelper;
import org.asamk.signal.manager.helper.IncomingMessageHandler;
import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.helper.PreKeyHelper;
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.Sticker;
import org.asamk.signal.manager.storage.stickers.StickerPackId;
import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.StickerUtils;
-import org.asamk.signal.manager.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
-import org.whispersystems.libsignal.fingerprint.Fingerprint;
-import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
-import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalSessionLock;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Function;
import java.util.stream.Collectors;
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
private final ContactHelper contactHelper;
private final IncomingMessageHandler incomingMessageHandler;
private final PreKeyHelper preKeyHelper;
+ private final IdentityHelper identityHelper;
private final Context context;
private boolean hasCaughtUpWithOldMessages = false;
+ private boolean ignoreAttachments = false;
+
+ private Thread receiveThread;
+ private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
+ private boolean isReceivingSynchronous;
ManagerImpl(
SignalAccount account,
this.attachmentHelper = new AttachmentHelper(dependencies, attachmentStore);
this.pinHelper = new PinHelper(dependencies.getKeyBackupService());
- final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
- account.getProfileStore()::getProfileKey,
- this::getRecipientProfile,
- this::getSenderCertificate);
+ final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account,
+ dependencies,
+ account::getProfileKey,
+ this::getRecipientProfile);
this.profileHelper = new ProfileHelper(account,
dependencies,
avatarStore,
- account.getProfileStore()::getProfileKey,
unidentifiedAccessHelper::getAccessFor,
this::resolveSignalServiceAddress);
final GroupV2Helper groupV2Helper = new GroupV2Helper(profileHelper::getRecipientProfileKeyCredential,
this::resolveSignalServiceAddress,
account.getRecipientStore(),
this::handleIdentityFailure,
- this::getGroup,
+ this::getGroupInfo,
this::refreshRegisteredUser);
this.groupHelper = new GroupHelper(account,
dependencies,
avatarStore,
this::resolveSignalServiceAddress,
account.getRecipientStore());
- this.storageHelper = new StorageHelper(account, dependencies, groupHelper);
+ this.storageHelper = new StorageHelper(account, dependencies, groupHelper, profileHelper);
this.contactHelper = new ContactHelper(account);
this.syncHelper = new SyncHelper(account,
attachmentHelper,
syncHelper,
this::getRecipientProfile,
jobExecutor);
+ this.identityHelper = new IdentityHelper(account,
+ dependencies,
+ this::resolveSignalServiceAddress,
+ syncHelper,
+ profileHelper);
}
@Override
- public String getUsername() {
+ public String getSelfNumber() {
return account.getUsername();
}
- @Override
- public RecipientId getSelfRecipientId() {
- return account.getSelfRecipientId();
- }
-
- @Override
- public int getDeviceId() {
- return account.getDeviceId();
- }
-
@Override
public void checkAccountState() throws IOException {
if (account.getLastReceiveTimestamp() == 0) {
account.isDiscoverableByPhoneNumber());
}
+ @Override
+ public void updateConfiguration(
+ final Boolean readReceipts,
+ final Boolean unidentifiedDeliveryIndicators,
+ final Boolean typingIndicators,
+ final Boolean linkPreviews
+ ) throws IOException, NotMasterDeviceException {
+ if (!account.isMasterDevice()) {
+ throw new NotMasterDeviceException();
+ }
+
+ final var configurationStore = account.getConfigurationStore();
+ if (readReceipts != null) {
+ configurationStore.setReadReceipts(readReceipts);
+ }
+ if (unidentifiedDeliveryIndicators != null) {
+ configurationStore.setUnidentifiedDeliveryIndicators(unidentifiedDeliveryIndicators);
+ }
+ if (typingIndicators != null) {
+ configurationStore.setTypingIndicators(typingIndicators);
+ }
+ if (linkPreviews != null) {
+ configurationStore.setLinkPreviews(linkPreviews);
+ }
+ syncHelper.sendConfigurationMessage();
+ }
+
/**
* @param givenName if null, the previous givenName will be kept
* @param familyName if null, the previous familyName will be kept
logger.debug("Failed to decrypt device name, maybe plain text?", e);
}
}
- return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen());
+ return new Device(d.getId(),
+ deviceName,
+ d.getCreated(),
+ d.getLastSeen(),
+ d.getId() == account.getDeviceId());
}).collect(Collectors.toList());
}
@Override
- public void removeLinkedDevices(int deviceId) throws IOException {
+ public void removeLinkedDevices(long deviceId) throws IOException {
dependencies.getAccountManager().removeDevice(deviceId);
var devices = dependencies.getAccountManager().getDevices();
account.setMultiDevice(devices.size() > 1);
}
@Override
- public Profile getRecipientProfile(RecipientId recipientId) {
+ public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException {
+ return profileHelper.getRecipientProfile(resolveRecipient(recipient));
+ }
+
+ private Profile getRecipientProfile(RecipientId recipientId) {
return profileHelper.getRecipientProfile(recipientId);
}
@Override
- public List<GroupInfo> getGroups() {
- return account.getGroupStore().getGroups();
+ public List<Group> getGroups() {
+ return account.getGroupStore().getGroups().stream().map(this::toGroup).collect(Collectors.toList());
+ }
+
+ private Group toGroup(final GroupInfo groupInfo) {
+ if (groupInfo == null) {
+ return null;
+ }
+
+ return new Group(groupInfo.getGroupId(),
+ groupInfo.getTitle(),
+ groupInfo.getDescription(),
+ groupInfo.getGroupInviteLink(),
+ groupInfo.getMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.getPendingMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.getRequestingMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.getAdminMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.isBlocked(),
+ groupInfo.getMessageExpirationTimer(),
+ groupInfo.getPermissionAddMember(),
+ groupInfo.getPermissionEditDetails(),
+ groupInfo.getPermissionSendMessage(),
+ groupInfo.isMember(account.getSelfRecipientId()),
+ groupInfo.isAdmin(account.getSelfRecipientId()));
}
@Override
@Override
public SendGroupMessageResults updateGroup(
- GroupId groupId,
- String name,
- String description,
- Set<RecipientIdentifier.Single> members,
- Set<RecipientIdentifier.Single> removeMembers,
- Set<RecipientIdentifier.Single> admins,
- Set<RecipientIdentifier.Single> removeAdmins,
- boolean resetGroupLink,
- GroupLinkState groupLinkState,
- GroupPermission addMemberPermission,
- GroupPermission editDetailsPermission,
- File avatarFile,
- Integer expirationTimer,
- Boolean isAnnouncementGroup
+ final GroupId groupId, final UpdateGroup updateGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
return groupHelper.updateGroup(groupId,
- name,
- description,
- members == null ? null : resolveRecipients(members),
- removeMembers == null ? null : resolveRecipients(removeMembers),
- admins == null ? null : resolveRecipients(admins),
- removeAdmins == null ? null : resolveRecipients(removeAdmins),
- resetGroupLink,
- groupLinkState,
- addMemberPermission,
- editDetailsPermission,
- avatarFile,
- expirationTimer,
- isAnnouncementGroup);
+ updateGroup.getName(),
+ updateGroup.getDescription(),
+ updateGroup.getMembers() == null ? null : resolveRecipients(updateGroup.getMembers()),
+ updateGroup.getRemoveMembers() == null ? null : resolveRecipients(updateGroup.getRemoveMembers()),
+ updateGroup.getAdmins() == null ? null : resolveRecipients(updateGroup.getAdmins()),
+ updateGroup.getRemoveAdmins() == null ? null : resolveRecipients(updateGroup.getRemoveAdmins()),
+ updateGroup.isResetGroupLink(),
+ updateGroup.getGroupLinkState(),
+ updateGroup.getAddMemberPermission(),
+ updateGroup.getEditDetailsPermission(),
+ updateGroup.getAvatarFile(),
+ updateGroup.getExpirationTimer(),
+ updateGroup.getIsAnnouncementGroup());
}
@Override
long timestamp = System.currentTimeMillis();
messageBuilder.withTimestamp(timestamp);
for (final var recipient : recipients) {
- if (recipient instanceof RecipientIdentifier.Single) {
- final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
+ if (recipient instanceof RecipientIdentifier.Single single) {
+ final var recipientId = resolveRecipient(single);
final var result = sendHelper.sendMessage(messageBuilder, recipientId);
results.put(recipient, List.of(result));
} else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
final var result = sendHelper.sendSelfMessage(messageBuilder);
results.put(recipient, List.of(result));
- } else if (recipient instanceof RecipientIdentifier.Group) {
- final var groupId = ((RecipientIdentifier.Group) recipient).groupId;
- final var result = sendHelper.sendAsGroupMessage(messageBuilder, groupId);
+ } else if (recipient instanceof RecipientIdentifier.Group group) {
+ final var result = sendHelper.sendAsGroupMessage(messageBuilder, group.groupId);
results.put(recipient, result);
}
}
@Override
public void setGroupBlocked(
final GroupId groupId, final boolean blocked
- ) throws GroupNotFoundException, IOException {
+ ) throws GroupNotFoundException, IOException, NotMasterDeviceException {
+ if (!account.isMasterDevice()) {
+ throw new NotMasterDeviceException();
+ }
groupHelper.setGroupBlocked(groupId, blocked);
// TODO cycle our profile key
syncHelper.sendBlockedList();
}
}
- private byte[] getSenderCertificate() {
- byte[] certificate;
- try {
- if (account.isPhoneNumberShared()) {
- certificate = dependencies.getAccountManager().getSenderCertificate();
- } else {
- certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
- }
- } catch (IOException e) {
- logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
- return null;
- }
- // TODO cache for a day
- return certificate;
- }
-
private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException {
final var address = resolveSignalServiceAddress(recipientId);
if (!address.getNumber().isPresent()) {
return registeredUsers;
}
- private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
+ private void retryFailedReceivedMessages(ReceiveMessageHandler handler) {
Set<HandleAction> queuedActions = new HashSet<>();
for (var cachedMessage : account.getMessageCache().getCachedMessages()) {
- var actions = retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
+ var actions = retryFailedReceivedMessage(handler, cachedMessage);
if (actions != null) {
queuedActions.addAll(actions);
}
}
private List<HandleAction> retryFailedReceivedMessage(
- final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage
+ final ReceiveMessageHandler handler, final CachedMessage cachedMessage
) {
var envelope = cachedMessage.loadEnvelope();
if (envelope == null) {
}
@Override
- public void receiveMessages(
- long timeout,
- TimeUnit unit,
- boolean returnOnTimeout,
- boolean ignoreAttachments,
- ReceiveMessageHandler handler
+ public void addReceiveHandler(final ReceiveMessageHandler handler) {
+ if (isReceivingSynchronous) {
+ throw new IllegalStateException("Already receiving message synchronously.");
+ }
+ synchronized (messageHandlers) {
+ messageHandlers.add(handler);
+
+ startReceiveThreadIfRequired();
+ }
+ }
+
+ private void startReceiveThreadIfRequired() {
+ if (receiveThread != null) {
+ return;
+ }
+ receiveThread = new Thread(() -> {
+ while (!Thread.interrupted()) {
+ try {
+ receiveMessagesInternal(1L, TimeUnit.HOURS, false, (envelope, decryptedContent, e) -> {
+ synchronized (messageHandlers) {
+ for (ReceiveMessageHandler h : messageHandlers) {
+ try {
+ h.handleMessage(envelope, decryptedContent, e);
+ } catch (Exception ex) {
+ logger.warn("Message handler failed, ignoring", ex);
+ }
+ }
+ }
+ });
+ break;
+ } catch (IOException e) {
+ logger.warn("Receiving messages failed, retrying", e);
+ }
+ }
+ hasCaughtUpWithOldMessages = false;
+ synchronized (messageHandlers) {
+ receiveThread = null;
+
+ // Check if in the meantime another handler has been registered
+ if (!messageHandlers.isEmpty()) {
+ startReceiveThreadIfRequired();
+ }
+ }
+ });
+
+ receiveThread.start();
+ }
+
+ @Override
+ public void removeReceiveHandler(final ReceiveMessageHandler handler) {
+ final Thread thread;
+ synchronized (messageHandlers) {
+ thread = receiveThread;
+ receiveThread = null;
+ messageHandlers.remove(handler);
+ if (!messageHandlers.isEmpty() || isReceivingSynchronous) {
+ return;
+ }
+ }
+
+ stopReceiveThread(thread);
+ }
+
+ private void stopReceiveThread(final Thread thread) {
+ thread.interrupt();
+ try {
+ thread.join();
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ @Override
+ public boolean isReceiving() {
+ if (isReceivingSynchronous) {
+ return true;
+ }
+ synchronized (messageHandlers) {
+ return messageHandlers.size() > 0;
+ }
+ }
+
+ @Override
+ public void receiveMessages(long timeout, TimeUnit unit, ReceiveMessageHandler handler) throws IOException {
+ receiveMessages(timeout, unit, true, handler);
+ }
+
+ @Override
+ public void receiveMessages(ReceiveMessageHandler handler) throws IOException {
+ receiveMessages(1L, TimeUnit.HOURS, false, handler);
+ }
+
+ private void receiveMessages(
+ long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler
+ ) throws IOException {
+ if (isReceiving()) {
+ throw new IllegalStateException("Already receiving message.");
+ }
+ isReceivingSynchronous = true;
+ receiveThread = Thread.currentThread();
+ try {
+ receiveMessagesInternal(timeout, unit, returnOnTimeout, handler);
+ } finally {
+ receiveThread = null;
+ hasCaughtUpWithOldMessages = false;
+ isReceivingSynchronous = false;
+ }
+ }
+
+ private void receiveMessagesInternal(
+ long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler
) throws IOException {
- retryFailedReceivedMessages(handler, ignoreAttachments);
+ retryFailedReceivedMessages(handler);
Set<HandleAction> queuedActions = new HashSet<>();
signalWebSocket.connect();
hasCaughtUpWithOldMessages = false;
+ var backOffCounter = 0;
+ final var MAX_BACKOFF_COUNTER = 9;
while (!Thread.interrupted()) {
SignalServiceEnvelope envelope;
// store message on disk, before acknowledging receipt to the server
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
});
+ backOffCounter = 0;
+
if (result.isPresent()) {
envelope = result.get();
logger.debug("New message received from server");
} else {
throw e;
}
- } catch (WebSocketUnavailableException e) {
- logger.debug("Pipe unexpectedly unavailable, connecting");
- signalWebSocket.connect();
- continue;
+ } catch (IOException e) {
+ logger.debug("Pipe unexpectedly unavailable: {}", e.getMessage());
+ if (e instanceof WebSocketUnavailableException || "Connection closed!".equals(e.getMessage())) {
+ final var sleepMilliseconds = 100 * (long) Math.pow(2, backOffCounter);
+ backOffCounter = Math.min(backOffCounter + 1, MAX_BACKOFF_COUNTER);
+ logger.warn("Connection closed unexpectedly, reconnecting in {} ms", sleepMilliseconds);
+ try {
+ Thread.sleep(sleepMilliseconds);
+ } catch (InterruptedException interruptedException) {
+ return;
+ }
+ hasCaughtUpWithOldMessages = false;
+ signalWebSocket.connect();
+ continue;
+ }
+ throw e;
} catch (TimeoutException e) {
+ backOffCounter = 0;
if (returnOnTimeout) return;
continue;
}
if (hasCaughtUpWithOldMessages) {
handleQueuedActions(queuedActions);
+ queuedActions.clear();
}
if (cachedMessage[0] != null) {
if (exception instanceof UntrustedIdentityException) {
+ logger.debug("Keeping message with untrusted identity in message cache");
final var address = ((UntrustedIdentityException) exception).getSender();
final var recipientId = resolveRecipient(address);
if (!envelope.hasSourceUuid()) {
}
}
handleQueuedActions(queuedActions);
+ queuedActions.clear();
+ }
+
+ @Override
+ public void setIgnoreAttachments(final boolean ignoreAttachments) {
+ this.ignoreAttachments = ignoreAttachments;
}
@Override
}
private void handleQueuedActions(final Collection<HandleAction> queuedActions) {
+ logger.debug("Handling message actions");
var interrupted = false;
for (var action : queuedActions) {
try {
}
@Override
- public List<Pair<RecipientId, Contact>> getContacts() {
- return account.getContactStore().getContacts();
+ public List<Pair<RecipientAddress, Contact>> getContacts() {
+ return account.getContactStore()
+ .getContacts()
+ .stream()
+ .map(p -> new Pair<>(account.getRecipientStore().resolveRecipientAddress(p.first()), p.second()))
+ .collect(Collectors.toList());
}
@Override
- public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) {
+ public String getContactOrProfileName(RecipientIdentifier.Single recipient) {
final RecipientId recipientId;
try {
- recipientId = resolveRecipient(recipientIdentifier);
+ recipientId = resolveRecipient(recipient);
} catch (UnregisteredUserException e) {
return null;
}
}
@Override
- public GroupInfo getGroup(GroupId groupId) {
+ public Group getGroup(GroupId groupId) {
+ return toGroup(groupHelper.getGroup(groupId));
+ }
+
+ private GroupInfo getGroupInfo(GroupId groupId) {
return groupHelper.getGroup(groupId);
}
@Override
- public List<IdentityInfo> getIdentities() {
- return account.getIdentityKeyStore().getIdentities();
+ public List<Identity> getIdentities() {
+ return account.getIdentityKeyStore()
+ .getIdentities()
+ .stream()
+ .map(this::toIdentity)
+ .collect(Collectors.toList());
+ }
+
+ private Identity toIdentity(final IdentityInfo identityInfo) {
+ if (identityInfo == null) {
+ return null;
+ }
+
+ final var address = account.getRecipientStore().resolveRecipientAddress(identityInfo.getRecipientId());
+ final var scannableFingerprint = identityHelper.computeSafetyNumberForScanning(identityInfo.getRecipientId(),
+ identityInfo.getIdentityKey());
+ return new Identity(address,
+ identityInfo.getIdentityKey(),
+ identityHelper.computeSafetyNumber(identityInfo.getRecipientId(), identityInfo.getIdentityKey()),
+ scannableFingerprint == null ? null : scannableFingerprint.getSerialized(),
+ identityInfo.getTrustLevel(),
+ identityInfo.getDateAdded());
}
@Override
- public List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient) {
+ public List<Identity> getIdentities(RecipientIdentifier.Single recipient) {
IdentityInfo identity;
try {
identity = account.getIdentityKeyStore().getIdentity(resolveRecipient(recipient));
} catch (UnregisteredUserException e) {
identity = null;
}
- return identity == null ? List.of() : List.of(identity);
+ return identity == null ? List.of() : List.of(toIdentity(identity));
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- return trustIdentity(recipientId,
- identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
- TrustLevel.TRUSTED_VERIFIED);
+ return identityHelper.trustIdentityVerified(recipientId, fingerprint);
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- var address = resolveSignalServiceAddress(recipientId);
- return trustIdentity(recipientId,
- identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
- TrustLevel.TRUSTED_VERIFIED);
+ return identityHelper.trustIdentityVerifiedSafetyNumber(recipientId, safetyNumber);
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- var address = resolveSignalServiceAddress(recipientId);
- return trustIdentity(recipientId, identityKey -> {
- final var fingerprint = computeSafetyNumberFingerprint(address, identityKey);
- try {
- return fingerprint != null && fingerprint.getScannableFingerprint().compareTo(safetyNumber);
- } catch (FingerprintVersionMismatchException | FingerprintParsingException e) {
- return false;
- }
- }, TrustLevel.TRUSTED_VERIFIED);
+ return identityHelper.trustIdentityVerifiedSafetyNumber(recipientId, safetyNumber);
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
- }
-
- private boolean trustIdentity(
- RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
- ) {
- var identity = account.getIdentityKeyStore().getIdentity(recipientId);
- if (identity == null) {
- return false;
- }
-
- if (!verifier.apply(identity.getIdentityKey())) {
- return false;
- }
-
- account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
- try {
- var address = resolveSignalServiceAddress(recipientId);
- syncHelper.sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
- } catch (IOException e) {
- logger.warn("Failed to send verification sync message: {}", e.getMessage());
- }
-
- return true;
+ return identityHelper.trustIdentityAllKeys(recipientId);
}
private void handleIdentityFailure(
final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
) {
- final var identityKey = identityFailure.getIdentityKey();
- if (identityKey != null) {
- final var newIdentity = account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date());
- if (newIdentity) {
- account.getSessionStore().archiveSessions(recipientId);
- }
- } else {
- // Retrieve profile to get the current identity key from the server
- profileHelper.refreshRecipientProfile(recipientId);
- }
- }
-
- @Override
- public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
- final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey);
- return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
- }
-
- @Override
- public byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
- final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey);
- return fingerprint == null ? null : fingerprint.getScannableFingerprint().getSerialized();
- }
-
- private Fingerprint computeSafetyNumberFingerprint(
- final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
- ) {
- return Utils.computeSafetyNumber(capabilities.isUuid(),
- account.getSelfAddress(),
- account.getIdentityKeyPair().getPublicKey(),
- theirAddress,
- theirIdentityKey);
+ this.identityHelper.handleIdentityFailure(recipientId, identityFailure);
}
@Override
return resolveSignalServiceAddress(resolveRecipient(address));
}
- @Override
- public SignalServiceAddress resolveSignalServiceAddress(UUID uuid) {
- return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid));
- }
-
- @Override
- public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
+ private SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
final var address = account.getRecipientStore().resolveRecipientAddress(recipientId);
if (address.getUuid().isPresent()) {
return address.toSignalServiceAddress();
// Address in recipient store doesn't have a uuid, this shouldn't happen
// Try to retrieve the uuid from the server
final var number = address.getNumber().get();
+ final UUID uuid;
try {
- return resolveSignalServiceAddress(getRegisteredUser(number));
+ uuid = getRegisteredUser(number);
} catch (IOException e) {
logger.warn("Failed to get uuid for e164 number: {}", number, e);
// Return SignalServiceAddress with unknown UUID
return address.toSignalServiceAddress();
}
+ return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid));
}
private Set<RecipientId> resolveRecipients(Collection<RecipientIdentifier.Single> recipients) throws UnregisteredUserException {
}
private void close(boolean closeAccount) throws IOException {
+ Thread thread;
+ synchronized (messageHandlers) {
+ messageHandlers.clear();
+ thread = receiveThread;
+ receiveThread = null;
+ }
+ if (thread != null) {
+ stopReceiveThread(thread);
+ }
executor.shutdown();
dependencies.getSignalWebSocket().disconnect();
}
account = null;
}
-
}