import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdV1;
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.GroupUtils;
+import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.manager.helper.GroupHelper;
+import org.asamk.signal.manager.helper.GroupV2Helper;
import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.helper.ProfileHelper;
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
+import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import java.util.concurrent.Executors;
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;
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper;
- private final GroupHelper groupHelper;
+ private final GroupV2Helper groupV2Helper;
private final PinHelper pinHelper;
private final AvatarStore avatarStore;
private final AttachmentStore attachmentStore;
+ private final SignalSessionLock sessionLock = new SignalSessionLock() {
+ private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
+
+ @Override
+ public Lock acquire() {
+ LEGACY_LOCK.lock();
+ return LEGACY_LOCK::unlock;
+ }
+ };
Manager(
SignalAccount account,
unidentified -> unidentified ? getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
() -> messageReceiver,
this::resolveSignalServiceAddress);
- this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential,
+ this.groupV2Helper = new GroupV2Helper(this::getRecipientProfileKeyCredential,
this::getRecipientProfile,
account::getSelfRecipientId,
groupsV2Operations,
}
public void checkAccountState() throws IOException {
+ if (account.getLastReceiveTimestamp() == 0) {
+ logger.warn("The Signal protocol expects that incoming messages are regularly received.");
+ } else {
+ var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
+ long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
+ if (days > 7) {
+ logger.warn(
+ "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
+ days);
+ }
+ }
if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
refreshPreKeys();
}
newProfile.getInternalServiceName(),
newProfile.getAbout() == null ? "" : newProfile.getAbout(),
newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
+ Optional.absent(),
streamDetails);
}
account.getPassword(),
account.getDeviceId(),
account.getSignalProtocolStore(),
+ sessionLock,
userAgent,
account.isMultiDevice(),
Optional.fromNullable(messagePipe),
Profile getRecipientProfile(
RecipientId recipientId, boolean force
) {
- var profileKey = account.getProfileStore().getProfileKey(recipientId);
- if (profileKey == null) {
- if (force) {
- // retrieve profile to get identity key
- retrieveEncryptedProfile(recipientId);
- }
- return null;
- }
var profile = account.getProfileStore().getProfile(recipientId);
- var now = new Date().getTime();
+ var now = System.currentTimeMillis();
// Profiles are cached for 24h before retrieving them again, unless forced
if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
return profile;
return null;
}
- profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
+ var profileKey = account.getProfileStore().getProfileKey(recipientId);
+ if (profileKey == null) {
+ profile = new Profile(System.currentTimeMillis(),
+ null,
+ null,
+ null,
+ null,
+ ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null),
+ ProfileUtils.getCapabilities(encryptedProfile));
+ } else {
+ profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
+ }
account.getProfileStore().storeProfile(recipientId, profile);
return profile;
}
} catch (InvalidKeyException ignored) {
logger.warn("Got invalid identity key in profile for {}",
- resolveSignalServiceAddress(recipientId).getLegacyIdentifier());
+ resolveSignalServiceAddress(recipientId).getIdentifier());
}
return profileAndCredential;
}
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
}
- public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
+ public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
+ GroupId groupId, Set<String> groupAdmins
+ ) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
SignalServiceDataMessage.Builder messageBuilder;
final var g = getGroupForUpdating(groupId);
account.getGroupStore().updateGroup(groupInfoV1);
} else {
final var groupInfoV2 = (GroupInfoV2) g;
- final var groupGroupChangePair = groupHelper.leaveGroup(groupInfoV2);
+ final var currentAdmins = g.getAdminMembers();
+ final var newAdmins = getSignalServiceAddresses(groupAdmins);
+ newAdmins.removeAll(currentAdmins);
+ newAdmins.retainAll(g.getMembers());
+ if (currentAdmins.contains(getSelfRecipientId())
+ && currentAdmins.size() == 1
+ && g.getMembers().size() > 1
+ && newAdmins.size() == 0) {
+ // Last admin can't leave the group, unless she's also the last member
+ throw new LastGroupAdminException(g.getGroupId(), g.getTitle());
+ }
+ final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
account.getGroupStore().updateGroup(groupInfoV2);
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
}
- public Pair<GroupId, List<SendMessageResult>> updateGroup(
- GroupId groupId, String name, List<String> members, File avatarFile
- ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
- return sendUpdateGroupMessage(groupId,
- name,
- members == null ? null : getSignalServiceAddresses(members),
- avatarFile);
+ public Pair<GroupId, List<SendMessageResult>> createGroup(
+ String name, List<String> members, File avatarFile
+ ) throws IOException, AttachmentInvalidException, InvalidNumberException {
+ return createGroup(name, members == null ? null : getSignalServiceAddresses(members), avatarFile);
}
- private Pair<GroupId, List<SendMessageResult>> sendUpdateGroupMessage(
- GroupId groupId, String name, Set<RecipientId> members, File avatarFile
- ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
- GroupInfo g;
+ private Pair<GroupId, List<SendMessageResult>> createGroup(
+ String name, Set<RecipientId> members, File avatarFile
+ ) throws IOException, AttachmentInvalidException {
+ final var selfRecipientId = account.getSelfRecipientId();
+ if (members != null && members.contains(selfRecipientId)) {
+ members = new HashSet<>(members);
+ members.remove(selfRecipientId);
+ }
+
+ var gv2Pair = groupV2Helper.createGroup(name == null ? "" : name,
+ members == null ? Set.of() : members,
+ avatarFile);
+
SignalServiceDataMessage.Builder messageBuilder;
- if (groupId == null) {
- // Create new group
- var gv2Pair = groupHelper.createGroupV2(name == null ? "" : name,
- members == null ? Set.of() : members,
- avatarFile);
- if (gv2Pair == null) {
- var gv1 = new GroupInfoV1(GroupIdV1.createRandom());
- gv1.addMembers(List.of(account.getSelfRecipientId()));
- updateGroupV1(gv1, name, members, avatarFile);
- messageBuilder = getGroupUpdateMessageBuilder(gv1);
- g = gv1;
- } else {
- final var gv2 = gv2Pair.first();
- final var decryptedGroup = gv2Pair.second();
+ if (gv2Pair == null) {
+ // Failed to create v2 group, creating v1 group instead
+ var gv1 = new GroupInfoV1(GroupIdV1.createRandom());
+ gv1.addMembers(List.of(selfRecipientId));
+ final var result = updateGroupV1(gv1, name, members, avatarFile);
+ return new Pair<>(gv1.getGroupId(), result.second());
+ }
- gv2.setGroup(decryptedGroup, this::resolveRecipient);
- if (avatarFile != null) {
- avatarStore.storeGroupAvatar(gv2.getGroupId(),
- outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
- }
- messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
- g = gv2;
- }
- } else {
- var group = getGroupForUpdating(groupId);
- if (group instanceof GroupInfoV2) {
- final var groupInfoV2 = (GroupInfoV2) group;
-
- Pair<Long, List<SendMessageResult>> result = null;
- if (groupInfoV2.isPendingMember(account.getSelfRecipientId())) {
- var groupGroupChangePair = groupHelper.acceptInvite(groupInfoV2);
- result = sendUpdateGroupMessage(groupInfoV2,
- groupGroupChangePair.first(),
- groupGroupChangePair.second());
- }
+ final var gv2 = gv2Pair.first();
+ final var decryptedGroup = gv2Pair.second();
- if (members != null) {
- final var newMembers = new HashSet<>(members);
- newMembers.removeAll(group.getMembers());
- if (newMembers.size() > 0) {
- var groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, newMembers);
- result = sendUpdateGroupMessage(groupInfoV2,
- groupGroupChangePair.first(),
- groupGroupChangePair.second());
- }
- }
- if (result == null || name != null || avatarFile != null) {
- var groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, name, avatarFile);
- if (avatarFile != null) {
- avatarStore.storeGroupAvatar(groupInfoV2.getGroupId(),
- outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
- }
- result = sendUpdateGroupMessage(groupInfoV2,
- groupGroupChangePair.first(),
- groupGroupChangePair.second());
- }
+ gv2.setGroup(decryptedGroup, this::resolveRecipient);
+ if (avatarFile != null) {
+ avatarStore.storeGroupAvatar(gv2.getGroupId(),
+ outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
+ }
+ messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
+ account.getGroupStore().updateGroup(gv2);
+
+ final var result = sendMessage(messageBuilder, gv2.getMembersIncludingPendingWithout(selfRecipientId));
+ return new Pair<>(gv2.getGroupId(), result.second());
+ }
+
+ public Pair<Long, List<SendMessageResult>> updateGroup(
+ GroupId groupId,
+ String name,
+ String description,
+ List<String> members,
+ List<String> removeMembers,
+ List<String> admins,
+ List<String> removeAdmins,
+ boolean resetGroupLink,
+ GroupLinkState groupLinkState,
+ GroupPermission addMemberPermission,
+ GroupPermission editDetailsPermission,
+ File avatarFile,
+ Integer expirationTimer
+ ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
+ return updateGroup(groupId,
+ name,
+ description,
+ members == null ? null : getSignalServiceAddresses(members),
+ removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
+ admins == null ? null : getSignalServiceAddresses(admins),
+ removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
+ resetGroupLink,
+ groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
+ avatarFile,
+ expirationTimer);
+ }
+
+ private Pair<Long, List<SendMessageResult>> updateGroup(
+ final GroupId groupId,
+ final String name,
+ final String description,
+ final Set<RecipientId> members,
+ final Set<RecipientId> removeMembers,
+ final Set<RecipientId> admins,
+ final Set<RecipientId> removeAdmins,
+ final boolean resetGroupLink,
+ final GroupLinkState groupLinkState,
+ final GroupPermission addMemberPermission,
+ final GroupPermission editDetailsPermission,
+ final File avatarFile,
+ final Integer expirationTimer
+ ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
+ var group = getGroupForUpdating(groupId);
- return new Pair<>(group.getGroupId(), result.second());
- } else {
- var gv1 = (GroupInfoV1) group;
- updateGroupV1(gv1, name, members, avatarFile);
- messageBuilder = getGroupUpdateMessageBuilder(gv1);
- g = gv1;
+ if (group instanceof GroupInfoV2) {
+ try {
+ return updateGroupV2((GroupInfoV2) group,
+ name,
+ description,
+ members,
+ removeMembers,
+ admins,
+ removeAdmins,
+ resetGroupLink,
+ groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
+ avatarFile,
+ expirationTimer);
+ } catch (ConflictException e) {
+ // Detected conflicting update, refreshing group and trying again
+ group = getGroup(groupId, true);
+ return updateGroupV2((GroupInfoV2) group,
+ name,
+ description,
+ members,
+ removeMembers,
+ admins,
+ removeAdmins,
+ resetGroupLink,
+ groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
+ avatarFile,
+ expirationTimer);
}
}
- account.getGroupStore().updateGroup(g);
+ final var gv1 = (GroupInfoV1) group;
+ final var result = updateGroupV1(gv1, name, members, avatarFile);
+ if (expirationTimer != null) {
+ setExpirationTimer(gv1, expirationTimer);
+ }
+ return result;
+ }
+
+ private Pair<Long, List<SendMessageResult>> updateGroupV1(
+ final GroupInfoV1 gv1, final String name, final Set<RecipientId> members, final File avatarFile
+ ) throws IOException, AttachmentInvalidException {
+ updateGroupV1Details(gv1, name, members, avatarFile);
+ var messageBuilder = getGroupUpdateMessageBuilder(gv1);
+
+ account.getGroupStore().updateGroup(gv1);
- final var result = sendMessage(messageBuilder,
- g.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
- return new Pair<>(g.getGroupId(), result.second());
+ return sendMessage(messageBuilder, gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
}
- private void updateGroupV1(
+ private void updateGroupV1Details(
final GroupInfoV1 g, final String name, final Collection<RecipientId> members, final File avatarFile
) throws IOException {
if (name != null) {
}
}
- public Pair<GroupId, List<SendMessageResult>> joinGroup(
- GroupInviteLinkUrl inviteLinkUrl
- ) throws IOException, GroupLinkNotActiveException {
- return sendJoinGroupMessage(inviteLinkUrl);
+ private Pair<Long, List<SendMessageResult>> updateGroupV2(
+ final GroupInfoV2 group,
+ final String name,
+ final String description,
+ final Set<RecipientId> members,
+ final Set<RecipientId> removeMembers,
+ final Set<RecipientId> admins,
+ final Set<RecipientId> removeAdmins,
+ final boolean resetGroupLink,
+ final GroupLinkState groupLinkState,
+ final GroupPermission addMemberPermission,
+ final GroupPermission editDetailsPermission,
+ final File avatarFile,
+ Integer expirationTimer
+ ) throws IOException {
+ Pair<Long, List<SendMessageResult>> result = null;
+ if (group.isPendingMember(account.getSelfRecipientId())) {
+ var groupGroupChangePair = groupV2Helper.acceptInvite(group);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (members != null) {
+ final var newMembers = new HashSet<>(members);
+ newMembers.removeAll(group.getMembers());
+ if (newMembers.size() > 0) {
+ var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+ }
+
+ if (removeMembers != null) {
+ var existingRemoveMembers = new HashSet<>(removeMembers);
+ existingRemoveMembers.retainAll(group.getMembers());
+ existingRemoveMembers.remove(getSelfRecipientId());// self can be removed with sendQuitGroupMessage
+ if (existingRemoveMembers.size() > 0) {
+ var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ var pendingRemoveMembers = new HashSet<>(removeMembers);
+ pendingRemoveMembers.retainAll(group.getPendingMembers());
+ if (pendingRemoveMembers.size() > 0) {
+ var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+ }
+
+ if (admins != null) {
+ final var newAdmins = new HashSet<>(admins);
+ newAdmins.retainAll(group.getMembers());
+ newAdmins.removeAll(group.getAdminMembers());
+ if (newAdmins.size() > 0) {
+ for (var admin : newAdmins) {
+ var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ groupGroupChangePair.second());
+ }
+ }
+ }
+
+ if (removeAdmins != null) {
+ final var existingRemoveAdmins = new HashSet<>(removeAdmins);
+ existingRemoveAdmins.retainAll(group.getAdminMembers());
+ if (existingRemoveAdmins.size() > 0) {
+ for (var admin : existingRemoveAdmins) {
+ var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ groupGroupChangePair.second());
+ }
+ }
+ }
+
+ if (resetGroupLink) {
+ var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (groupLinkState != null) {
+ var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (addMemberPermission != null) {
+ var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (editDetailsPermission != null) {
+ var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (expirationTimer != null) {
+ var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (name != null || description != null || avatarFile != null) {
+ var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
+ if (avatarFile != null) {
+ avatarStore.storeGroupAvatar(group.getGroupId(),
+ outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
+ }
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ return result;
}
- private Pair<GroupId, List<SendMessageResult>> sendJoinGroupMessage(
+ public Pair<GroupId, List<SendMessageResult>> joinGroup(
GroupInviteLinkUrl inviteLinkUrl
) throws IOException, GroupLinkNotActiveException {
- final var groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
+ final var groupJoinInfo = groupV2Helper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
inviteLinkUrl.getPassword());
- final var groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
+ final var groupChange = groupV2Helper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
inviteLinkUrl.getPassword(),
groupJoinInfo);
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
return new Pair<>(group.getGroupId(), List.of());
}
- final var result = sendUpdateGroupMessage(group, group.getGroup(), groupChange);
+ final var result = sendUpdateGroupV2Message(group, group.getGroup(), groupChange);
return new Pair<>(group.getGroupId(), result.second());
}
+ private Pair<Long, List<SendMessageResult>> sendUpdateGroupV2Message(
+ GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
+ ) throws IOException {
+ final var selfRecipientId = account.getSelfRecipientId();
+ final var members = group.getMembersIncludingPendingWithout(selfRecipientId);
+ group.setGroup(newDecryptedGroup, this::resolveRecipient);
+ members.addAll(group.getMembersIncludingPendingWithout(selfRecipientId));
+
+ final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray());
+ account.getGroupStore().updateGroup(group);
+ return sendMessage(messageBuilder, members);
+ }
+
private static int currentTimeDays() {
return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
}
}
}
- private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
- GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
- ) throws IOException {
- group.setGroup(newDecryptedGroup, this::resolveRecipient);
- final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray());
- account.getGroupStore().updateGroup(group);
- return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
- }
-
Pair<Long, List<SendMessageResult>> sendGroupInfoMessage(
GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
}
}
- SendMessageResult renewSession(RecipientId recipientId) throws IOException {
+ void renewSession(RecipientId recipientId) throws IOException {
account.getSessionStore().archiveSessions(recipientId);
- return sendNullMessage(recipientId);
+ if (!recipientId.equals(getSelfRecipientId())) {
+ sendNullMessage(recipientId);
+ }
}
public String getContactName(String number) throws InvalidNumberException {
/**
* Change the expiration timer for a group
*/
- public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) {
- var g = getGroup(groupId);
- if (g instanceof GroupInfoV1) {
- var groupInfoV1 = (GroupInfoV1) g;
- groupInfoV1.messageExpirationTime = messageExpirationTimer;
- account.getGroupStore().updateGroup(groupInfoV1);
- } else {
- throw new RuntimeException("TODO Not implemented!");
- }
+ private void setExpirationTimer(
+ GroupInfoV1 groupInfoV1, int messageExpirationTimer
+ ) throws NotAGroupMemberException, GroupNotFoundException, IOException {
+ groupInfoV1.messageExpirationTime = messageExpirationTimer;
+ account.getGroupStore().updateGroup(groupInfoV1);
+ sendExpirationTimerUpdate(groupInfoV1.getGroupId());
+ }
+
+ private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException {
+ final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
+ sendGroupMessage(messageBuilder, groupId);
}
/**
}
}
- private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
+ private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException {
var cipher = new SignalServiceCipher(account.getSelfAddress(),
account.getSignalProtocolStore(),
+ sessionLock,
certificateValidator);
- try {
- return cipher.decrypt(envelope);
- } catch (ProtocolUntrustedIdentityException e) {
- if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
- throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
- }
- throw new AssertionError(e);
- }
+ return cipher.decrypt(envelope);
}
private void handleEndSession(RecipientId recipientId) {
if (signedGroupChange != null
&& groupInfoV2.getGroup() != null
&& groupInfoV2.getGroup().getRevision() + 1 == revision) {
- group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(), signedGroupChange, groupMasterKey);
+ group = groupV2Helper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
+ signedGroupChange,
+ groupMasterKey);
}
if (group == null) {
- group = groupHelper.getDecryptedGroup(groupSecretParams);
+ group = groupV2Helper.getDecryptedGroup(groupSecretParams);
}
if (group != null) {
storeProfileKeysFromMembers(group);
if (!envelope.isReceipt()) {
try {
content = decryptMessage(envelope);
- } catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
+ } catch (ProtocolUntrustedIdentityException e) {
if (!envelope.hasSource()) {
- final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) e).getName();
+ final var identifier = e.getSender();
final var recipientId = resolveRecipient(identifier);
try {
account.getMessageCache().replaceSender(cachedMessage, recipientId);
SignalServiceContent content = null;
Exception exception = null;
final CachedMessage[] cachedMessage = {null};
+ account.setLastReceiveTimestamp(System.currentTimeMillis());
try {
var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
final var recipientId = envelope1.hasSource()
if (envelope.hasSource()) {
// Store uuid if we don't have it already
+ // address/uuid in envelope is sent by server
resolveRecipientTrusted(envelope.getSourceAddress());
}
final var notAGroupMember = isNotAGroupMember(envelope, content);
} catch (Exception e) {
exception = e;
}
+ if (!envelope.hasSource() && content != null) {
+ // Store uuid if we don't have it already
+ // address/uuid is validated by unidentified sender certificate
+ resolveRecipientTrusted(content.getSender());
+ }
var actions = handleMessage(envelope, content, ignoreAttachments);
if (exception instanceof ProtocolInvalidMessageException) {
final var sender = resolveRecipient(((ProtocolInvalidMessageException) exception).getSender());
handler.handleMessage(envelope, content, exception);
}
if (cachedMessage[0] != null) {
- if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
- final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) exception).getName();
+ if (exception instanceof ProtocolUntrustedIdentityException) {
+ final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender();
final var recipientId = resolveRecipient(identifier);
queuedActions.add(new RetrieveProfileAction(recipientId));
if (!envelope.hasSource()) {
try (var attachmentAsStream = retrieveAttachmentAsStream(groupsMessage.asPointer(), tmpFile)) {
var s = new DeviceGroupsInputStream(attachmentAsStream);
DeviceGroup g;
- while ((g = s.read()) != null) {
+ while (true) {
+ try {
+ g = s.read();
+ } catch (IOException e) {
+ logger.warn("Sync groups contained invalid group, ignoring: {}", e.getMessage());
+ continue;
+ }
+ if (g == null) {
+ break;
+ }
var syncGroup = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(g.getId()));
if (syncGroup != null) {
if (g.getName().isPresent()) {
.asPointer(), tmpFile)) {
var s = new DeviceContactsInputStream(attachmentAsStream);
DeviceContact c;
- while ((c = s.read()) != null) {
+ while (true) {
+ try {
+ c = s.read();
+ } catch (IOException e) {
+ logger.warn("Sync contacts contained invalid contact, ignoring: {}",
+ e.getMessage());
+ continue;
+ }
+ if (c == null) {
+ break;
+ }
if (c.getAddress().matches(account.getSelfAddress()) && c.getProfileKey().isPresent()) {
account.setProfileKey(c.getProfileKey().get());
}
}
public GroupInfo getGroup(GroupId groupId) {
+ return getGroup(groupId, false);
+ }
+
+ public GroupInfo getGroup(GroupId groupId, boolean forceUpdate) {
final var group = account.getGroupStore().getGroup(groupId);
- if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) {
+ if (group instanceof GroupInfoV2 && (forceUpdate || ((GroupInfoV2) group).getGroup() == null)) {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(((GroupInfoV2) group).getMasterKey());
- ((GroupInfoV2) group).setGroup(groupHelper.getDecryptedGroup(groupSecretParams), this::resolveRecipient);
+ ((GroupInfoV2) group).setGroup(groupV2Helper.getDecryptedGroup(groupSecretParams), this::resolveRecipient);
account.getGroupStore().updateGroup(group);
}
return group;