mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Convert SignalService, Database, Group, Payment, and other remaining protos to wire.
This commit is contained in:
committed by
Alex Hart
parent
a6b7d0bcc5
commit
efbd5cab85
@@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.groups;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.core.util.DatabaseId;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.libsignal.protocol.kdf.HKDFv3;
|
||||
@@ -18,6 +16,8 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public abstract class GroupId implements DatabaseId {
|
||||
|
||||
private static final String ENCODED_SIGNAL_GROUP_V1_PREFIX = "__textsecure_group__!";
|
||||
|
||||
@@ -7,8 +7,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
@@ -26,7 +24,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||
import org.whispersystems.signalservice.internal.push.GroupContext;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -38,6 +36,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
final class GroupManagerV1 {
|
||||
|
||||
private static final String TAG = Log.tag(GroupManagerV1.class);
|
||||
@@ -159,13 +159,13 @@ final class GroupManagerV1 {
|
||||
}
|
||||
}
|
||||
|
||||
GroupContext.Builder groupContextBuilder = GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(groupId.getDecodedId()))
|
||||
.setType(GroupContext.Type.UPDATE)
|
||||
.addAllMembersE164(e164Members)
|
||||
.addAllMembers(uuidMembers);
|
||||
GroupContext.Builder groupContextBuilder = new GroupContext.Builder()
|
||||
.id(ByteString.of(groupId.getDecodedId()))
|
||||
.type(GroupContext.Type.UPDATE)
|
||||
.membersE164(e164Members)
|
||||
.members(uuidMembers);
|
||||
|
||||
if (groupName != null) groupContextBuilder.setName(groupName);
|
||||
if (groupName != null) groupContextBuilder.name(groupName);
|
||||
|
||||
GroupContext groupContext = groupContextBuilder.build();
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
@@ -52,7 +50,6 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
@@ -64,9 +61,9 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
|
||||
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.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
|
||||
@@ -91,6 +88,8 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
final class GroupManagerV2 {
|
||||
|
||||
private static final String TAG = Log.tag(GroupManagerV2.class);
|
||||
@@ -227,17 +226,17 @@ final class GroupManagerV2 {
|
||||
GroupsV2StateProcessor.StateProcessorForGroup stateProcessorForGroup = new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey);
|
||||
DecryptedGroup latest = stateProcessorForGroup.getCurrentGroupStateFromServer();
|
||||
|
||||
if (latest.getRevision() == 0) {
|
||||
if (latest.revision == 0) {
|
||||
return latest;
|
||||
}
|
||||
|
||||
Optional<DecryptedMember> selfInFullMemberList = DecryptedGroupUtil.findMemberByAci(latest.getMembersList(), selfAci);
|
||||
Optional<DecryptedMember> selfInFullMemberList = DecryptedGroupUtil.findMemberByAci(latest.members, selfAci);
|
||||
|
||||
if (!selfInFullMemberList.isPresent()) {
|
||||
return latest;
|
||||
}
|
||||
|
||||
DecryptedGroup joinedVersion = stateProcessorForGroup.getSpecificVersionFromServer(selfInFullMemberList.get().getJoinedAtRevision());
|
||||
DecryptedGroup joinedVersion = stateProcessorForGroup.getSpecificVersionFromServer(selfInFullMemberList.get().joinedAtRevision);
|
||||
|
||||
if (joinedVersion != null) {
|
||||
return joinedVersion;
|
||||
@@ -267,7 +266,7 @@ final class GroupManagerV2 {
|
||||
|
||||
@WorkerThread
|
||||
void sendNoopGroupUpdate(@NonNull GroupMasterKey masterKey, @NonNull DecryptedGroup currentState) {
|
||||
sendGroupUpdateHelper.sendGroupUpdate(masterKey, new GroupMutation(currentState, DecryptedGroupChange.newBuilder().build(), currentState), null);
|
||||
sendGroupUpdateHelper.sendGroupUpdate(masterKey, new GroupMutation(currentState, new DecryptedGroupChange(), currentState), null);
|
||||
}
|
||||
|
||||
|
||||
@@ -308,16 +307,17 @@ final class GroupManagerV2 {
|
||||
groupDatabase.onAvatarUpdated(groupId, avatar != null);
|
||||
SignalDatabase.recipients().setProfileSharing(groupRecipient.getId(), true);
|
||||
|
||||
DecryptedGroupChange groupChange = DecryptedGroupChange.newBuilder(GroupChangeReconstruct.reconstructGroupChange(DecryptedGroup.newBuilder().build(), decryptedGroup))
|
||||
.setEditorServiceIdBytes(selfAci.toByteString())
|
||||
.build();
|
||||
DecryptedGroupChange groupChange = GroupChangeReconstruct.reconstructGroupChange(new DecryptedGroup(), decryptedGroup)
|
||||
.newBuilder()
|
||||
.editorServiceIdBytes(selfAci.toByteString())
|
||||
.build();
|
||||
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(masterKey, new GroupMutation(null, groupChange, decryptedGroup), null);
|
||||
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient,
|
||||
recipientAndThread.threadId,
|
||||
decryptedGroup.getMembersCount() - 1,
|
||||
getPendingMemberRecipientIds(decryptedGroup.getPendingMembersList()));
|
||||
decryptedGroup.members.size() - 1,
|
||||
getPendingMemberRecipientIds(decryptedGroup.pendingMembers));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,17 +393,16 @@ final class GroupManagerV2 {
|
||||
{
|
||||
try {
|
||||
GroupChange.Actions.Builder change = title != null ? groupOperations.createModifyGroupTitle(title)
|
||||
: GroupChange.Actions.newBuilder();
|
||||
: new GroupChange.Actions.Builder();
|
||||
|
||||
if (description != null) {
|
||||
change.setModifyDescription(groupOperations.createModifyGroupDescriptionAction(description));
|
||||
change.modifyDescription(groupOperations.createModifyGroupDescriptionAction(description).build());
|
||||
}
|
||||
|
||||
if (avatarChanged) {
|
||||
String cdnKey = avatarBytes != null ? groupsV2Api.uploadAvatar(avatarBytes, groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
: "";
|
||||
change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder()
|
||||
.setAvatar(cdnKey));
|
||||
change.modifyAvatar(new GroupChange.Actions.ModifyAvatarAction.Builder().avatar(cdnKey).build());
|
||||
}
|
||||
|
||||
GroupManager.GroupActionResult groupActionResult = commitChangeWithConflictResolution(selfAci, change);
|
||||
@@ -445,7 +444,7 @@ final class GroupManagerV2 {
|
||||
.map(r -> Recipient.resolved(r).requireAci())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return commitChangeWithConflictResolution(selfAci, groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
|
||||
return commitChangeWithConflictResolution(selfAci, groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().bannedMembers));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -463,9 +462,9 @@ final class GroupManagerV2 {
|
||||
{
|
||||
GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
|
||||
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
|
||||
Optional<DecryptedMember> selfMember = DecryptedGroupUtil.findMemberByAci(decryptedGroup.getMembersList(), selfAci);
|
||||
Optional<DecryptedPendingMember> aciPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.getPendingMembersList(), selfAci);
|
||||
Optional<DecryptedPendingMember> pniPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.getPendingMembersList(), selfPni);
|
||||
Optional<DecryptedMember> selfMember = DecryptedGroupUtil.findMemberByAci(decryptedGroup.members, selfAci);
|
||||
Optional<DecryptedPendingMember> aciPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembers, selfAci);
|
||||
Optional<DecryptedPendingMember> pniPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembers, selfPni);
|
||||
Optional<DecryptedPendingMember> selfPendingMember = Optional.empty();
|
||||
ServiceId serviceId = selfAci;
|
||||
|
||||
@@ -478,7 +477,7 @@ final class GroupManagerV2 {
|
||||
|
||||
if (selfPendingMember.isPresent()) {
|
||||
try {
|
||||
revokeInvites(serviceId, Collections.singleton(new UuidCiphertext(selfPendingMember.get().getServiceIdCipherText().toByteArray())), false);
|
||||
revokeInvites(serviceId, Collections.singleton(new UuidCiphertext(selfPendingMember.get().serviceIdCipherText.toByteArray())), false);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
@@ -496,7 +495,7 @@ final class GroupManagerV2 {
|
||||
return commitChangeWithConflictResolution(selfAci,
|
||||
groupOperations.createRemoveMembersChange(Collections.singleton(aci),
|
||||
ban,
|
||||
ban ? v2GroupProperties.getDecryptedGroup().getBannedMembersList()
|
||||
ban ? v2GroupProperties.getDecryptedGroup().bannedMembers
|
||||
: Collections.emptyList()),
|
||||
allowWhenBlocked,
|
||||
sendToMembers);
|
||||
@@ -518,14 +517,14 @@ final class GroupManagerV2 {
|
||||
{
|
||||
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
|
||||
DecryptedGroup group = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup();
|
||||
Optional<DecryptedMember> selfInGroup = DecryptedGroupUtil.findMemberByAci(group.getMembersList(), selfAci);
|
||||
Optional<DecryptedMember> selfInGroup = DecryptedGroupUtil.findMemberByAci(group.members, selfAci);
|
||||
|
||||
if (!selfInGroup.isPresent()) {
|
||||
Log.w(TAG, "Self not in group " + groupId);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Arrays.equals(profileKey.serialize(), selfInGroup.get().getProfileKey().toByteArray())) {
|
||||
if (Arrays.equals(profileKey.serialize(), selfInGroup.get().profileKey.toByteArray())) {
|
||||
Log.i(TAG, "Own Profile Key is already up to date in group " + groupId);
|
||||
return null;
|
||||
} else {
|
||||
@@ -548,15 +547,15 @@ final class GroupManagerV2 {
|
||||
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
|
||||
{
|
||||
DecryptedGroup group = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup();
|
||||
Optional<DecryptedMember> selfInGroup = DecryptedGroupUtil.findMemberByAci(group.getMembersList(), selfAci);
|
||||
Optional<DecryptedMember> selfInGroup = DecryptedGroupUtil.findMemberByAci(group.members, selfAci);
|
||||
|
||||
if (selfInGroup.isPresent()) {
|
||||
Log.w(TAG, "Self already in group");
|
||||
return null;
|
||||
}
|
||||
|
||||
Optional<DecryptedPendingMember> aciInPending = DecryptedGroupUtil.findPendingByServiceId(group.getPendingMembersList(), selfAci);
|
||||
Optional<DecryptedPendingMember> pniInPending = DecryptedGroupUtil.findPendingByServiceId(group.getPendingMembersList(), selfPni);
|
||||
Optional<DecryptedPendingMember> aciInPending = DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfAci);
|
||||
Optional<DecryptedPendingMember> pniInPending = DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfPni);
|
||||
|
||||
GroupCandidate groupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
|
||||
|
||||
@@ -579,9 +578,9 @@ final class GroupManagerV2 {
|
||||
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
|
||||
{
|
||||
ByteString serviceIdByteString = serviceId.toByteString();
|
||||
boolean rejectJoinRequest = v2GroupProperties.getDecryptedGroup().getRequestingMembersList().stream().anyMatch(m -> m.getAciBytes().equals(serviceIdByteString));
|
||||
boolean rejectJoinRequest = v2GroupProperties.getDecryptedGroup().requestingMembers.stream().anyMatch(m -> m.aciBytes.equals(serviceIdByteString));
|
||||
|
||||
return commitChangeWithConflictResolution(selfAci, groupOperations.createBanServiceIdsChange(Collections.singleton(serviceId), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
|
||||
return commitChangeWithConflictResolution(selfAci, groupOperations.createBanServiceIdsChange(Collections.singleton(serviceId), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().bannedMembers));
|
||||
}
|
||||
|
||||
public GroupManager.GroupActionResult unban(Set<ServiceId> serviceIds)
|
||||
@@ -615,7 +614,7 @@ final class GroupManagerV2 {
|
||||
if (state != GroupManager.GroupLinkState.DISABLED) {
|
||||
DecryptedGroup group = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getDecryptedGroup();
|
||||
|
||||
if (group.getInviteLinkPassword().isEmpty()) {
|
||||
if (group.inviteLinkPassword.size() == 0) {
|
||||
Log.d(TAG, "First time enabling group links for group and password empty, generating");
|
||||
change = groupOperations.createModifyGroupLinkPasswordAndRightsChange(GroupLinkPassword.createNew().serialize(), access);
|
||||
}
|
||||
@@ -650,13 +649,13 @@ final class GroupManagerV2 {
|
||||
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
|
||||
{
|
||||
boolean refetchedAddMemberCredentials = false;
|
||||
change.setSourceServiceId(UuidUtil.toByteString(authServiceId.getRawUuid()));
|
||||
change.sourceServiceId(UuidUtil.toByteString(authServiceId.getRawUuid()));
|
||||
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
return commitChange(change, allowWhenBlocked, sendToMembers);
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
if (change.getAddMembersCount() > 0 && !refetchedAddMemberCredentials) {
|
||||
if (change.addMembers.size() > 0 && !refetchedAddMemberCredentials) {
|
||||
refetchedAddMemberCredentials = true;
|
||||
change = refetchAddMemberCredentials(change);
|
||||
} else {
|
||||
@@ -692,7 +691,7 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
if (groupUpdateResult.getGroupState() != GroupsV2StateProcessor.GroupState.GROUP_UPDATED) {
|
||||
int serverRevision = groupUpdateResult.getLatestServer().getRevision();
|
||||
int serverRevision = groupUpdateResult.getLatestServer().revision;
|
||||
int localRevision = groupDatabase.requireGroup(groupId).requireV2GroupProperties().getGroupRevision();
|
||||
int revisionDelta = serverRevision - localRevision;
|
||||
Log.w(TAG, String.format(Locale.US, "Server is ahead by %d revisions", revisionDelta));
|
||||
@@ -713,7 +712,7 @@ final class GroupManagerV2 {
|
||||
|
||||
private GroupChange.Actions.Builder refetchAddMemberCredentials(@NonNull GroupChange.Actions.Builder change) {
|
||||
try {
|
||||
List<RecipientId> ids = groupOperations.decryptAddMembers(change.getAddMembersList())
|
||||
List<RecipientId> ids = groupOperations.decryptAddMembers(change.addMembers)
|
||||
.stream()
|
||||
.map(RecipientId::from)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
@@ -738,10 +737,10 @@ final class GroupManagerV2 {
|
||||
final GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
|
||||
final GroupTable.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
|
||||
final int nextRevision = v2GroupProperties.getGroupRevision() + 1;
|
||||
final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
|
||||
final DecryptedGroupChange decryptedChange;
|
||||
final DecryptedGroup decryptedGroupState;
|
||||
final DecryptedGroup previousGroupState;
|
||||
final GroupChange.Actions changeActions = change.revision(nextRevision).build();
|
||||
final DecryptedGroupChange decryptedChange;
|
||||
final DecryptedGroup decryptedGroupState;
|
||||
final DecryptedGroup previousGroupState;
|
||||
|
||||
if (!allowWhenBlocked && Recipient.externalGroupExact(groupId).isBlocked()) {
|
||||
throw new GroupChangeFailedException("Group is blocked.");
|
||||
@@ -763,8 +762,8 @@ final class GroupManagerV2 {
|
||||
|
||||
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
|
||||
int newMembersCount = decryptedChange.getNewMembersCount();
|
||||
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList());
|
||||
int newMembersCount = decryptedChange.newMembers.size();
|
||||
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.newPendingMembers);
|
||||
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
|
||||
}
|
||||
@@ -826,9 +825,9 @@ final class GroupManagerV2 {
|
||||
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
|
||||
|
||||
try {
|
||||
return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true)
|
||||
return groupOperations.decryptChange(GroupChange.ADAPTER.decode(signedGroupChange), true)
|
||||
.orElse(null);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | IOException e) {
|
||||
Log.w(TAG, "Unable to verify supplied group change", e);
|
||||
}
|
||||
}
|
||||
@@ -912,7 +911,7 @@ final class GroupManagerV2 {
|
||||
@Nullable byte[] avatar)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException
|
||||
{
|
||||
boolean requestToJoin = joinInfo.getAddFromInviteLink() == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
|
||||
if (requestToJoin) {
|
||||
@@ -924,7 +923,7 @@ final class GroupManagerV2 {
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
try {
|
||||
signedGroupChange = joinGroupOnServer(requestToJoin, joinInfo.getRevision());
|
||||
signedGroupChange = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, String.format("Successfully requested to join %s on server", groupId));
|
||||
@@ -954,7 +953,7 @@ final class GroupManagerV2 {
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
groupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.getRevision(), System.currentTimeMillis(), decryptedChange);
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
@@ -968,7 +967,7 @@ final class GroupManagerV2 {
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
groupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.getRevision(), System.currentTimeMillis(), decryptedChange);
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
@@ -1017,7 +1016,7 @@ final class GroupManagerV2 {
|
||||
{
|
||||
try {
|
||||
new GroupsV2StateProcessor(context).forGroup(serviceIds, groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.getRevision(),
|
||||
.updateLocalGroupToRevision(decryptedChange.revision,
|
||||
System.currentTimeMillis(),
|
||||
decryptedChange);
|
||||
|
||||
@@ -1050,7 +1049,7 @@ final class GroupManagerV2 {
|
||||
try {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
return groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
@@ -1064,23 +1063,25 @@ final class GroupManagerV2 {
|
||||
* update message.
|
||||
*/
|
||||
private DecryptedGroup createPlaceholderGroup(@NonNull DecryptedGroupJoinInfo joinInfo, boolean requestToJoin) {
|
||||
DecryptedGroup.Builder group = DecryptedGroup.newBuilder()
|
||||
.setTitle(joinInfo.getTitle())
|
||||
.setAvatar(joinInfo.getAvatar())
|
||||
.setRevision(GroupsV2StateProcessor.PLACEHOLDER_REVISION);
|
||||
DecryptedGroup.Builder group = new DecryptedGroup.Builder()
|
||||
.title(joinInfo.title)
|
||||
.avatar(joinInfo.avatar)
|
||||
.revision(GroupsV2StateProcessor.PLACEHOLDER_REVISION);
|
||||
|
||||
Recipient self = Recipient.self();
|
||||
ByteString selfAciBytes = selfAci.toByteString();
|
||||
ByteString profileKey = ByteString.copyFrom(Objects.requireNonNull(self.getProfileKey()));
|
||||
ByteString profileKey = ByteString.of(Objects.requireNonNull(self.getProfileKey()));
|
||||
|
||||
if (requestToJoin) {
|
||||
group.addRequestingMembers(DecryptedRequestingMember.newBuilder()
|
||||
.setAciBytes(selfAciBytes)
|
||||
.setProfileKey(profileKey));
|
||||
group.requestingMembers(Collections.singletonList(new DecryptedRequestingMember.Builder()
|
||||
.aciBytes(selfAciBytes)
|
||||
.profileKey(profileKey)
|
||||
.build()));
|
||||
} else {
|
||||
group.addMembers(DecryptedMember.newBuilder()
|
||||
.setAciBytes(selfAciBytes)
|
||||
.setProfileKey(profileKey));
|
||||
group.members(Collections.singletonList(new DecryptedMember.Builder()
|
||||
.aciBytes(selfAciBytes)
|
||||
.profileKey(profileKey)
|
||||
.build()));
|
||||
}
|
||||
|
||||
return group.build();
|
||||
@@ -1104,7 +1105,7 @@ final class GroupManagerV2 {
|
||||
GroupChange.Actions.Builder change = requestToJoin ? groupOperations.createGroupJoinRequest(expiringProfileKeyCredential)
|
||||
: groupOperations.createGroupJoinDirect(expiringProfileKeyCredential);
|
||||
|
||||
change.setSourceServiceId(selfAci.toByteString());
|
||||
change.sourceServiceId(selfAci.toByteString());
|
||||
|
||||
return commitJoinChangeWithConflictResolution(currentRevision, change);
|
||||
}
|
||||
@@ -1114,13 +1115,13 @@ final class GroupManagerV2 {
|
||||
{
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
GroupChange.Actions changeActions = change.setRevision(currentRevision + 1)
|
||||
GroupChange.Actions changeActions = change.revision(currentRevision + 1)
|
||||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to join group at V" + changeActions.getRevision());
|
||||
Log.i(TAG, "Trying to join group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
|
||||
Log.i(TAG, "Successfully joined group at V" + changeActions.getRevision());
|
||||
Log.i(TAG, "Successfully joined group at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
Log.w(TAG, "Patch not accepted", e);
|
||||
@@ -1162,7 +1163,7 @@ final class GroupManagerV2 {
|
||||
throws IOException, GroupLinkNotActiveException, GroupChangeFailedException
|
||||
{
|
||||
try {
|
||||
int currentRevision = getGroupJoinInfoFromServer(groupMasterKey, password).getRevision();
|
||||
int currentRevision = getGroupJoinInfoFromServer(groupMasterKey, password).revision;
|
||||
|
||||
Log.i(TAG, "Server now on V" + currentRevision);
|
||||
|
||||
@@ -1176,7 +1177,7 @@ final class GroupManagerV2 {
|
||||
throws IOException, GroupLinkNotActiveException, GroupChangeFailedException
|
||||
{
|
||||
try {
|
||||
boolean pendingAdminApproval = getGroupJoinInfoFromServer(groupMasterKey, password).getPendingAdminApproval();
|
||||
boolean pendingAdminApproval = getGroupJoinInfoFromServer(groupMasterKey, password).pendingAdminApproval;
|
||||
|
||||
if (pendingAdminApproval) {
|
||||
Log.i(TAG, "User is already pending admin approval");
|
||||
@@ -1220,7 +1221,7 @@ final class GroupManagerV2 {
|
||||
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
|
||||
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.getRevision()));
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision));
|
||||
|
||||
sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange, false);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||
@@ -1229,9 +1230,9 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
private DecryptedGroup resetRevision(DecryptedGroup newGroup, int revision) {
|
||||
return DecryptedGroup.newBuilder(newGroup)
|
||||
.setRevision(revision)
|
||||
.build();
|
||||
return newGroup.newBuilder()
|
||||
.revision(revision)
|
||||
.build();
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitCancelChangeWithConflictResolution(@NonNull GroupChange.Actions.Builder change)
|
||||
@@ -1241,13 +1242,13 @@ final class GroupManagerV2 {
|
||||
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
GroupChange.Actions changeActions = change.setRevision(currentRevision + 1)
|
||||
GroupChange.Actions changeActions = change.revision(currentRevision + 1)
|
||||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to cancel request group at V" + changeActions.getRevision());
|
||||
Log.i(TAG, "Trying to cancel request group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
|
||||
Log.i(TAG, "Successfully cancelled group join at V" + changeActions.getRevision());
|
||||
Log.i(TAG, "Successfully cancelled group join at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package org.thoughtcrime.securesms.groups;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
@@ -20,11 +16,11 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.GroupContextV2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class GroupProtoUtil {
|
||||
|
||||
@@ -36,12 +32,12 @@ public final class GroupProtoUtil {
|
||||
{
|
||||
ByteString bytes = self.toByteString();
|
||||
for (DecryptedMember decryptedMember : partialDecryptedGroup.getMembersList()) {
|
||||
if (decryptedMember.getAciBytes().equals(bytes)) {
|
||||
return decryptedMember.getJoinedAtRevision();
|
||||
if (decryptedMember.aciBytes.equals(bytes)) {
|
||||
return decryptedMember.joinedAtRevision;
|
||||
}
|
||||
}
|
||||
for (DecryptedPendingMember decryptedMember : partialDecryptedGroup.getPendingMembersList()) {
|
||||
if (decryptedMember.getServiceIdBytes().equals(bytes)) {
|
||||
if (decryptedMember.serviceIdBytes.equals(bytes)) {
|
||||
// Assume latest, we don't have any information about when pending members were invited
|
||||
return partialDecryptedGroup.getRevision();
|
||||
}
|
||||
@@ -53,27 +49,27 @@ public final class GroupProtoUtil {
|
||||
@NonNull GroupMutation groupMutation,
|
||||
@Nullable GroupChange signedServerChange)
|
||||
{
|
||||
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
||||
DecryptedGroup decryptedGroup = groupMutation.getNewGroupState();
|
||||
int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision();
|
||||
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
|
||||
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
|
||||
.setRevision(revision);
|
||||
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
||||
DecryptedGroup decryptedGroup = groupMutation.getNewGroupState();
|
||||
int revision = plainGroupChange != null ? plainGroupChange.revision : decryptedGroup.revision;
|
||||
GroupContextV2.Builder contextBuilder = new GroupContextV2.Builder()
|
||||
.masterKey(ByteString.of(masterKey.serialize()))
|
||||
.revision(revision);
|
||||
|
||||
if (signedServerChange != null) {
|
||||
contextBuilder.setGroupChange(signedServerChange.toByteString());
|
||||
contextBuilder.groupChange(signedServerChange.encodeByteString());
|
||||
}
|
||||
|
||||
DecryptedGroupV2Context.Builder builder = DecryptedGroupV2Context.newBuilder()
|
||||
.setContext(contextBuilder.build())
|
||||
.setGroupState(decryptedGroup);
|
||||
DecryptedGroupV2Context.Builder builder = new DecryptedGroupV2Context.Builder()
|
||||
.context(contextBuilder.build())
|
||||
.groupState(decryptedGroup);
|
||||
|
||||
if (groupMutation.getPreviousGroupState() != null) {
|
||||
builder.setPreviousGroupState(groupMutation.getPreviousGroupState());
|
||||
builder.previousGroupState(groupMutation.getPreviousGroupState());
|
||||
}
|
||||
|
||||
if (plainGroupChange != null) {
|
||||
builder.setChange(plainGroupChange);
|
||||
builder.change(plainGroupChange);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
@@ -81,7 +77,7 @@ public final class GroupProtoUtil {
|
||||
|
||||
@WorkerThread
|
||||
public static Recipient pendingMemberToRecipient(@NonNull DecryptedPendingMember pendingMember) {
|
||||
return pendingMemberServiceIdToRecipient(pendingMember.getServiceIdBytes());
|
||||
return pendingMemberServiceIdToRecipient(pendingMember.serviceIdBytes);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -110,7 +106,7 @@ public final class GroupProtoUtil {
|
||||
ByteString aciBytes = aci.toByteString();
|
||||
|
||||
for (DecryptedMember member : membersList) {
|
||||
if (aciBytes.equals(member.getAciBytes())) {
|
||||
if (aciBytes.equals(member.aciBytes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,10 +173,10 @@ public final class GroupsV1MigrationUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.getRevision());
|
||||
Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.revision);
|
||||
SignalDatabase.groups().migrateToV2(threadId, gv1Id, decryptedGroup);
|
||||
|
||||
Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision());
|
||||
Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.revision);
|
||||
try {
|
||||
GroupManager.updateGroupFromServer(context, gv1Id.deriveV2MigrationMasterKey(), LATEST, System.currentTimeMillis(), null);
|
||||
} catch (GroupChangeBusyException | GroupNotAMemberException e) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class LiveGroup {
|
||||
|
||||
@@ -68,9 +67,9 @@ public final class LiveGroup {
|
||||
LiveData<GroupTable.V2GroupProperties> v2Properties = Transformations.map(this.groupRecord, GroupRecord::requireV2GroupProperties);
|
||||
this.groupLink = Transformations.map(v2Properties, g -> {
|
||||
DecryptedGroup group = g.getDecryptedGroup();
|
||||
AccessControl.AccessRequired addFromInviteLink = group.getAccessControl().getAddFromInviteLink();
|
||||
AccessControl.AccessRequired addFromInviteLink = group.accessControl.addFromInviteLink;
|
||||
|
||||
if (group.getInviteLinkPassword().isEmpty()) {
|
||||
if (group.inviteLinkPassword.size() == 0) {
|
||||
return GroupLinkUrlAndStatus.NONE;
|
||||
}
|
||||
|
||||
@@ -107,11 +106,11 @@ public final class LiveGroup {
|
||||
}
|
||||
|
||||
boolean selfAdmin = g.isAdmin(Recipient.self());
|
||||
List<DecryptedRequestingMember> requestingMembersList = g.requireV2GroupProperties().getDecryptedGroup().getRequestingMembersList();
|
||||
List<DecryptedRequestingMember> requestingMembersList = g.requireV2GroupProperties().getDecryptedGroup().requestingMembers;
|
||||
|
||||
return Stream.of(requestingMembersList)
|
||||
.map(requestingMember -> {
|
||||
Recipient recipient = Recipient.externalPush(ServiceId.parseOrThrow(requestingMember.getAciBytes()));
|
||||
Recipient recipient = Recipient.externalPush(ServiceId.parseOrThrow(requestingMember.aciBytes));
|
||||
return new GroupMemberEntry.RequestingMember(recipient, selfAdmin);
|
||||
})
|
||||
.toList();
|
||||
@@ -157,7 +156,7 @@ public final class LiveGroup {
|
||||
}
|
||||
|
||||
public LiveData<Integer> getPendingMemberCount() {
|
||||
return Transformations.map(groupRecord, g -> g.isV2Group() ? g.requireV2GroupProperties().getDecryptedGroup().getPendingMembersCount() : 0);
|
||||
return Transformations.map(groupRecord, g -> g.isV2Group() ? g.requireV2GroupProperties().getDecryptedGroup().pendingMembers.size() : 0);
|
||||
}
|
||||
|
||||
public LiveData<Integer> getPendingAndRequestingMemberCount() {
|
||||
@@ -165,7 +164,7 @@ public final class LiveGroup {
|
||||
if (g.isV2Group()) {
|
||||
DecryptedGroup decryptedGroup = g.requireV2GroupProperties().getDecryptedGroup();
|
||||
|
||||
return decryptedGroup.getPendingMembersCount() + decryptedGroup.getRequestingMembersCount();
|
||||
return decryptedGroup.pendingMembers.size() + decryptedGroup.requestingMembers.size();
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.annotation.WorkerThread;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -30,6 +29,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Repository for modifying the pending members on a single group.
|
||||
*/
|
||||
@@ -49,17 +50,17 @@ final class PendingMemberInvitesRepository {
|
||||
|
||||
public void getInvitees(@NonNull Consumer<InviteeResult> onInviteesLoaded) {
|
||||
executor.execute(() -> {
|
||||
GroupTable groupDatabase = SignalDatabase.groups();
|
||||
GroupTable.V2GroupProperties v2GroupProperties = groupDatabase.getGroup(groupId).get().requireV2GroupProperties();
|
||||
DecryptedGroup decryptedGroup = v2GroupProperties.getDecryptedGroup();
|
||||
List<DecryptedPendingMember> pendingMembersList = decryptedGroup.getPendingMembersList();
|
||||
GroupTable groupDatabase = SignalDatabase.groups();
|
||||
GroupTable.V2GroupProperties v2GroupProperties = groupDatabase.getGroup(groupId).get().requireV2GroupProperties();
|
||||
DecryptedGroup decryptedGroup = v2GroupProperties.getDecryptedGroup();
|
||||
List<DecryptedPendingMember> pendingMembersList = decryptedGroup.pendingMembers;
|
||||
List<SinglePendingMemberInvitedByYou> byMe = new ArrayList<>(pendingMembersList.size());
|
||||
List<MultiplePendingMembersInvitedByAnother> byOthers = new ArrayList<>(pendingMembersList.size());
|
||||
ByteString self = SignalStore.account().requireAci().toByteString();
|
||||
boolean selfIsAdmin = v2GroupProperties.isAdmin(Recipient.self());
|
||||
|
||||
Stream.of(pendingMembersList)
|
||||
.groupBy(DecryptedPendingMember::getAddedByAci)
|
||||
.groupBy(m -> m.addedByAci)
|
||||
.forEach(g ->
|
||||
{
|
||||
ByteString inviterAci = g.getKey();
|
||||
@@ -69,7 +70,7 @@ final class PendingMemberInvitesRepository {
|
||||
for (DecryptedPendingMember pendingMember : invitedMembers) {
|
||||
try {
|
||||
Recipient invitee = GroupProtoUtil.pendingMemberToRecipient(pendingMember);
|
||||
UuidCiphertext uuidCipherText = new UuidCiphertext(pendingMember.getServiceIdCipherText().toByteArray());
|
||||
UuidCiphertext uuidCipherText = new UuidCiphertext(pendingMember.serviceIdCipherText.toByteArray());
|
||||
|
||||
byMe.add(new SinglePendingMemberInvitedByYou(invitee, uuidCipherText));
|
||||
} catch (InvalidInputException e) {
|
||||
@@ -82,7 +83,7 @@ final class PendingMemberInvitesRepository {
|
||||
|
||||
for (DecryptedPendingMember pendingMember : invitedMembers) {
|
||||
try {
|
||||
uuidCipherTexts.add(new UuidCiphertext(pendingMember.getServiceIdCipherText().toByteArray()));
|
||||
uuidCipherTexts.add(new UuidCiphertext(pendingMember.serviceIdCipherText.toByteArray()));
|
||||
} catch (InvalidInputException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ public final class GroupDetails {
|
||||
}
|
||||
|
||||
public @NonNull String getGroupName() {
|
||||
return joinInfo.getTitle();
|
||||
return joinInfo.title;
|
||||
}
|
||||
|
||||
public @NonNull String getGroupDescription() {
|
||||
return joinInfo.getDescription();
|
||||
return joinInfo.description;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getAvatarBytes() {
|
||||
@@ -34,10 +34,10 @@ public final class GroupDetails {
|
||||
}
|
||||
|
||||
public int getGroupMembershipCount() {
|
||||
return joinInfo.getMemberCount();
|
||||
return joinInfo.memberCount;
|
||||
}
|
||||
|
||||
public boolean joinRequiresAdminApproval() {
|
||||
return joinInfo.getAddFromInviteLink() == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
return joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ final class GroupJoinRepository {
|
||||
|
||||
private @Nullable byte[] tryGetAvatarBytes(@NonNull DecryptedGroupJoinInfo joinInfo) {
|
||||
try {
|
||||
return AvatarGroupsV2DownloadJob.downloadGroupAvatarBytes(context, groupInviteLinkUrl.getGroupMasterKey(), joinInfo.getAvatar());
|
||||
return AvatarGroupsV2DownloadJob.downloadGroupAvatarBytes(context, groupInviteLinkUrl.getGroupMasterKey(), joinInfo.avatar);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to get group avatar", e);
|
||||
return null;
|
||||
|
||||
@@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.groups.v2;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.storageservice.protos.groups.GroupInviteLink;
|
||||
@@ -15,6 +13,8 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class GroupInviteLinkUrl {
|
||||
|
||||
private static final String GROUP_URL_HOST = "signal.group";
|
||||
@@ -27,7 +27,7 @@ public final class GroupInviteLinkUrl {
|
||||
public static GroupInviteLinkUrl forGroup(@NonNull GroupMasterKey groupMasterKey,
|
||||
@NonNull DecryptedGroup group)
|
||||
{
|
||||
return new GroupInviteLinkUrl(groupMasterKey, GroupLinkPassword.fromBytes(group.getInviteLinkPassword().toByteArray()));
|
||||
return new GroupInviteLinkUrl(groupMasterKey, GroupLinkPassword.fromBytes(group.inviteLinkPassword.toByteArray()));
|
||||
}
|
||||
|
||||
public static boolean isGroupLink(@NonNull String urlString) {
|
||||
@@ -59,20 +59,19 @@ public final class GroupInviteLinkUrl {
|
||||
}
|
||||
|
||||
byte[] bytes = Base64UrlSafe.decodePaddingAgnostic(encoding);
|
||||
GroupInviteLink groupInviteLink = GroupInviteLink.parseFrom(bytes);
|
||||
GroupInviteLink groupInviteLink = GroupInviteLink.ADAPTER.decode(bytes);
|
||||
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (groupInviteLink.getContentsCase()) {
|
||||
case V1CONTENTS: {
|
||||
GroupInviteLink.GroupInviteLinkContentsV1 groupInviteLinkContentsV1 = groupInviteLink.getV1Contents();
|
||||
GroupMasterKey groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.getGroupMasterKey().toByteArray());
|
||||
GroupLinkPassword password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.getInviteLinkPassword().toByteArray());
|
||||
if (groupInviteLink.v1Contents != null) {
|
||||
GroupInviteLink.GroupInviteLinkContentsV1 groupInviteLinkContentsV1 = groupInviteLink.v1Contents;
|
||||
GroupMasterKey groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.groupMasterKey.toByteArray());
|
||||
GroupLinkPassword password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.inviteLinkPassword.toByteArray());
|
||||
|
||||
return new GroupInviteLinkUrl(groupMasterKey, password);
|
||||
}
|
||||
default: throw new UnknownGroupLinkVersionException("Url contains no known group link content");
|
||||
return new GroupInviteLinkUrl(groupMasterKey, password);
|
||||
} else {
|
||||
throw new UnknownGroupLinkVersionException("Url contains no known group link content");
|
||||
}
|
||||
} catch (InvalidInputException | IOException e) {
|
||||
} catch (InvalidInputException | IllegalStateException | IOException e) {
|
||||
throw new InvalidGroupLinkException(e);
|
||||
}
|
||||
}
|
||||
@@ -106,13 +105,14 @@ public final class GroupInviteLinkUrl {
|
||||
}
|
||||
|
||||
protected static @NonNull String createUrl(@NonNull GroupMasterKey groupMasterKey, @NonNull GroupLinkPassword password) {
|
||||
GroupInviteLink groupInviteLink = GroupInviteLink.newBuilder()
|
||||
.setV1Contents(GroupInviteLink.GroupInviteLinkContentsV1.newBuilder()
|
||||
.setGroupMasterKey(ByteString.copyFrom(groupMasterKey.serialize()))
|
||||
.setInviteLinkPassword(ByteString.copyFrom(password.serialize())))
|
||||
.build();
|
||||
GroupInviteLink groupInviteLink = new GroupInviteLink.Builder()
|
||||
.v1Contents(new GroupInviteLink.GroupInviteLinkContentsV1.Builder()
|
||||
.groupMasterKey(ByteString.of(groupMasterKey.serialize()))
|
||||
.inviteLinkPassword(ByteString.of(password.serialize()))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
String encoding = Base64UrlSafe.encodeBytesWithoutPadding(groupInviteLink.toByteArray());
|
||||
String encoding = Base64UrlSafe.encodeBytesWithoutPadding(groupInviteLink.encode());
|
||||
|
||||
return GROUP_URL_PREFIX + encoding;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.groups.v2;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
@@ -12,13 +10,13 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Collects profile keys from group states.
|
||||
@@ -42,25 +40,29 @@ public final class ProfileKeySet {
|
||||
* authoritative.
|
||||
*/
|
||||
public void addKeysFromGroupChange(@NonNull DecryptedGroupChange change) {
|
||||
ServiceId editor = ServiceId.parseOrNull(change.getEditorServiceIdBytes());
|
||||
ServiceId editor = ServiceId.parseOrNull(change.editorServiceIdBytes);
|
||||
|
||||
for (DecryptedMember member : change.getNewMembersList()) {
|
||||
for (DecryptedMember member : change.newMembers) {
|
||||
addMemberKey(member, editor);
|
||||
}
|
||||
|
||||
for (DecryptedMember member : change.getPromotePendingMembersList()) {
|
||||
for (DecryptedMember member : change.promotePendingMembers) {
|
||||
addMemberKey(member, editor);
|
||||
}
|
||||
|
||||
for (DecryptedMember member : change.getModifiedProfileKeysList()) {
|
||||
for (DecryptedMember member : change.modifiedProfileKeys) {
|
||||
addMemberKey(member, editor);
|
||||
}
|
||||
|
||||
for (DecryptedRequestingMember member : change.getNewRequestingMembersList()) {
|
||||
addMemberKey(editor, member.getAciBytes(), member.getProfileKey());
|
||||
for (DecryptedRequestingMember member : change.newRequestingMembers) {
|
||||
addMemberKey(editor, member.aciBytes, member.profileKey);
|
||||
}
|
||||
|
||||
for (DecryptedMember member : change.getPromotePendingPniAciMembersList()) {
|
||||
for (DecryptedMember member : change.promotePendingPniAciMembers) {
|
||||
addMemberKey(member, editor);
|
||||
}
|
||||
|
||||
for (DecryptedMember member : change.promotePendingPniAciMembers) {
|
||||
addMemberKey(member, editor);
|
||||
}
|
||||
}
|
||||
@@ -73,13 +75,13 @@ public final class ProfileKeySet {
|
||||
* gathered from a group state can only be used to fill in gaps in knowledge.
|
||||
*/
|
||||
public void addKeysFromGroupState(@NonNull DecryptedGroup group) {
|
||||
for (DecryptedMember member : group.getMembersList()) {
|
||||
for (DecryptedMember member : group.members) {
|
||||
addMemberKey(member, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMemberKey(@NonNull DecryptedMember member, @Nullable ServiceId changeSource) {
|
||||
addMemberKey(changeSource, member.getAciBytes(), member.getProfileKey());
|
||||
addMemberKey(changeSource, member.aciBytes, member.profileKey);
|
||||
}
|
||||
|
||||
private void addMemberKey(@Nullable ServiceId changeSource,
|
||||
|
||||
@@ -43,7 +43,7 @@ final class GlobalGroupState {
|
||||
|
||||
int getEarliestRevisionNumber() {
|
||||
if (localState != null) {
|
||||
return localState.getRevision();
|
||||
return localState.revision;
|
||||
} else {
|
||||
if (serverHistory.isEmpty()) {
|
||||
throw new AssertionError();
|
||||
@@ -57,7 +57,7 @@ final class GlobalGroupState {
|
||||
if (localState == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return localState.getRevision();
|
||||
return localState.revision;
|
||||
}
|
||||
return serverHistory.get(serverHistory.size() - 1).getRevision();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ final class GroupStateMapper {
|
||||
final int from = Math.max(0, inputState.getEarliestRevisionNumber());
|
||||
final int to = Math.min(inputState.getLatestRevisionNumber(), maximumRevisionToApply);
|
||||
|
||||
if (current != null && current.getRevision() == PLACEHOLDER_REVISION) {
|
||||
if (current != null && current.revision == PLACEHOLDER_REVISION) {
|
||||
Log.i(TAG, "Ignoring place holder group state");
|
||||
} else {
|
||||
stateChain.push(current, null);
|
||||
@@ -83,11 +83,11 @@ final class GroupStateMapper {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stateChain.getLatestState() == null && entry.getGroup() != null && current != null && current.getRevision() == PLACEHOLDER_REVISION) {
|
||||
DecryptedGroup previousState = DecryptedGroup.newBuilder(entry.getGroup())
|
||||
.setTitle(current.getTitle())
|
||||
.setAvatar(current.getAvatar())
|
||||
.build();
|
||||
if (stateChain.getLatestState() == null && entry.getGroup() != null && current != null && current.revision == PLACEHOLDER_REVISION) {
|
||||
DecryptedGroup previousState = entry.getGroup().newBuilder()
|
||||
.title(current.title)
|
||||
.avatar(current.avatar)
|
||||
.build();
|
||||
|
||||
stateChain.push(previousState, null);
|
||||
}
|
||||
@@ -135,12 +135,12 @@ final class GroupStateMapper {
|
||||
try {
|
||||
return DecryptedGroupUtil.applyWithoutRevisionCheck(group, change);
|
||||
} catch (NotAbleToApplyGroupV2ChangeException e) {
|
||||
Log.w(TAG, "Unable to apply V" + change.getRevision(), e);
|
||||
Log.w(TAG, "Unable to apply V" + change.revision, e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
(groupB, groupA) -> GroupChangeReconstruct.reconstructGroupChange(groupA, groupB),
|
||||
(groupA, groupB) -> groupA.getRevision() == groupB.getRevision() && DecryptedGroupUtil.changeIsEmpty(GroupChangeReconstruct.reconstructGroupChange(groupA, groupB))
|
||||
(groupA, groupB) -> groupA.revision == groupB.revision && DecryptedGroupUtil.changeIsEmpty(GroupChangeReconstruct.reconstructGroupChange(groupA, groupB))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
@@ -101,9 +99,9 @@ public class GroupsV2StateProcessor {
|
||||
public GroupsV2StateProcessor(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.groupsV2Authorization = ApplicationDependencies.getGroupsV2Authorization();
|
||||
this.groupsV2Api = ApplicationDependencies.getSignalServiceAccountManager().getGroupsV2Api();
|
||||
this.recipientTable = SignalDatabase.recipients();
|
||||
this.groupDatabase = SignalDatabase.groups();
|
||||
this.groupsV2Api = ApplicationDependencies.getSignalServiceAccountManager().getGroupsV2Api();
|
||||
this.recipientTable = SignalDatabase.recipients();
|
||||
this.groupDatabase = SignalDatabase.groups();
|
||||
}
|
||||
|
||||
public StateProcessorForGroup forGroup(@NonNull ServiceIds serviceIds, @NonNull GroupMasterKey groupMasterKey) {
|
||||
@@ -150,9 +148,8 @@ public class GroupsV2StateProcessor {
|
||||
|
||||
public static final class StateProcessorForGroup {
|
||||
private final ServiceIds serviceIds;
|
||||
private final Context context;
|
||||
private final GroupTable groupDatabase;
|
||||
private final GroupsV2Api groupsV2Api;
|
||||
private final GroupTable groupDatabase;
|
||||
private final GroupsV2Api groupsV2Api;
|
||||
private final GroupsV2Authorization groupsV2Authorization;
|
||||
private final GroupMasterKey masterKey;
|
||||
private final GroupId.V2 groupId;
|
||||
@@ -160,12 +157,12 @@ public class GroupsV2StateProcessor {
|
||||
private final ProfileAndMessageHelper profileAndMessageHelper;
|
||||
|
||||
private StateProcessorForGroup(@NonNull ServiceIds serviceIds,
|
||||
@NonNull Context context,
|
||||
@NonNull GroupTable groupDatabase,
|
||||
@NonNull GroupsV2Api groupsV2Api,
|
||||
@NonNull GroupsV2Authorization groupsV2Authorization,
|
||||
@NonNull GroupMasterKey groupMasterKey,
|
||||
@NonNull RecipientTable recipientTable)
|
||||
@NonNull Context context,
|
||||
@NonNull GroupTable groupDatabase,
|
||||
@NonNull GroupsV2Api groupsV2Api,
|
||||
@NonNull GroupsV2Authorization groupsV2Authorization,
|
||||
@NonNull GroupMasterKey groupMasterKey,
|
||||
@NonNull RecipientTable recipientTable)
|
||||
{
|
||||
this(serviceIds, context, groupDatabase, groupsV2Api, groupsV2Authorization, groupMasterKey, GroupSecretParams.deriveFromMasterKey(groupMasterKey), recipientTable);
|
||||
}
|
||||
@@ -180,7 +177,6 @@ public class GroupsV2StateProcessor {
|
||||
@NonNull RecipientTable recipientTable)
|
||||
{
|
||||
this.serviceIds = serviceIds;
|
||||
this.context = context;
|
||||
this.groupDatabase = groupDatabase;
|
||||
this.groupsV2Api = groupsV2Api;
|
||||
this.groupsV2Authorization = groupsV2Authorization;
|
||||
@@ -191,7 +187,6 @@ public class GroupsV2StateProcessor {
|
||||
}
|
||||
|
||||
@VisibleForTesting StateProcessorForGroup(@NonNull ServiceIds serviceIds,
|
||||
@NonNull Context context,
|
||||
@NonNull GroupTable groupDatabase,
|
||||
@NonNull GroupsV2Api groupsV2Api,
|
||||
@NonNull GroupsV2Authorization groupsV2Authorization,
|
||||
@@ -199,7 +194,6 @@ public class GroupsV2StateProcessor {
|
||||
@NonNull ProfileAndMessageHelper profileAndMessageHelper)
|
||||
{
|
||||
this.serviceIds = serviceIds;
|
||||
this.context = context;
|
||||
this.groupDatabase = groupDatabase;
|
||||
this.groupsV2Api = groupsV2Api;
|
||||
this.groupsV2Authorization = groupsV2Authorization;
|
||||
@@ -232,18 +226,18 @@ public class GroupsV2StateProcessor {
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(localState, serverState);
|
||||
GlobalGroupState inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(serverState, decryptedGroupChange)));
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, serverState.getRevision());
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, serverState.revision);
|
||||
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
|
||||
|
||||
if (newLocalState == null || newLocalState == inputGroupState.getLocalState()) {
|
||||
info("Local state and server state are equal");
|
||||
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
|
||||
} else {
|
||||
info("Local state (revision: " + localState.getRevision() + ") does not match server state (revision: " + serverState.getRevision() + "), updating");
|
||||
info("Local state (revision: " + localState.revision + ") does not match server state (revision: " + serverState.revision + "), updating");
|
||||
}
|
||||
|
||||
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
|
||||
if (localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
if (localState.revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
info("Inserting single update message for restore placeholder");
|
||||
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
|
||||
} else {
|
||||
@@ -291,8 +285,8 @@ public class GroupsV2StateProcessor {
|
||||
|
||||
if (signedGroupChange != null &&
|
||||
localState != null &&
|
||||
localState.getRevision() + 1 == signedGroupChange.getRevision() &&
|
||||
revision == signedGroupChange.getRevision())
|
||||
localState.revision + 1 == signedGroupChange.revision &&
|
||||
revision == signedGroupChange.revision)
|
||||
{
|
||||
|
||||
if (notInGroupAndNotBeingAdded(localRecord, signedGroupChange) && notHavingInviteRevoked(signedGroupChange)) {
|
||||
@@ -351,7 +345,7 @@ public class GroupsV2StateProcessor {
|
||||
}
|
||||
|
||||
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
|
||||
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
if (localState != null && localState.revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
info("Inserting single update message for restore placeholder");
|
||||
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
|
||||
} else {
|
||||
@@ -361,7 +355,7 @@ public class GroupsV2StateProcessor {
|
||||
|
||||
GlobalGroupState remainingWork = advanceGroupStateResult.getNewGlobalGroupState();
|
||||
if (remainingWork.getServerHistory().size() > 0) {
|
||||
info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", newLocalState.getRevision() + 1, remainingWork.getLatestRevisionNumber()));
|
||||
info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", newLocalState.revision + 1, remainingWork.getLatestRevisionNumber()));
|
||||
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, remainingWork.getLatestRevisionNumber()));
|
||||
}
|
||||
|
||||
@@ -371,21 +365,21 @@ public class GroupsV2StateProcessor {
|
||||
private boolean notInGroupAndNotBeingAdded(@NonNull Optional<GroupRecord> localRecord, @NonNull DecryptedGroupChange signedGroupChange) {
|
||||
boolean currentlyInGroup = localRecord.isPresent() && localRecord.get().isActive();
|
||||
|
||||
boolean addedAsMember = signedGroupChange.getNewMembersList()
|
||||
boolean addedAsMember = signedGroupChange.newMembers
|
||||
.stream()
|
||||
.map(DecryptedMember::getAciBytes)
|
||||
.map(m -> m.aciBytes)
|
||||
.map(ACI::parseOrNull)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(serviceIds::matches);
|
||||
|
||||
boolean addedAsPendingMember = signedGroupChange.getNewPendingMembersList()
|
||||
boolean addedAsPendingMember = signedGroupChange.newPendingMembers
|
||||
.stream()
|
||||
.map(DecryptedPendingMember::getServiceIdBytes)
|
||||
.map(m -> m.serviceIdBytes)
|
||||
.anyMatch(serviceIds::matches);
|
||||
|
||||
boolean addedAsRequestingMember = signedGroupChange.getNewRequestingMembersList()
|
||||
boolean addedAsRequestingMember = signedGroupChange.newRequestingMembers
|
||||
.stream()
|
||||
.map(DecryptedRequestingMember::getAciBytes)
|
||||
.map(m -> m.aciBytes)
|
||||
.map(ACI::parseOrNull)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(serviceIds::matches);
|
||||
@@ -394,9 +388,9 @@ public class GroupsV2StateProcessor {
|
||||
}
|
||||
|
||||
private boolean notHavingInviteRevoked(@NonNull DecryptedGroupChange signedGroupChange) {
|
||||
boolean havingInviteRevoked = signedGroupChange.getDeletePendingMembersList()
|
||||
boolean havingInviteRevoked = signedGroupChange.deletePendingMembers
|
||||
.stream()
|
||||
.map(DecryptedPendingMemberRemoval::getServiceIdBytes)
|
||||
.map(m -> m.serviceIdBytes)
|
||||
.anyMatch(serviceIds::matches);
|
||||
|
||||
return !havingInviteRevoked;
|
||||
@@ -406,7 +400,7 @@ public class GroupsV2StateProcessor {
|
||||
* Using network, attempt to bring the local copy of the group up to the revision specified via paging.
|
||||
*/
|
||||
private GroupUpdateResult updateLocalGroupFromServerPaged(int revision, DecryptedGroup localState, long timestamp, boolean forceIncludeFirst) throws IOException, GroupNotAMemberException {
|
||||
boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION);
|
||||
boolean latestRevisionOnly = revision == LATEST && (localState == null || localState.revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION);
|
||||
|
||||
info("Paging from server revision: " + (revision == LATEST ? "latest" : revision) + ", latestOnly: " + latestRevisionOnly);
|
||||
|
||||
@@ -421,7 +415,7 @@ public class GroupsV2StateProcessor {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
if (localState != null && localState.getRevision() >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceIds.getAci(), localState.getMembersList())) {
|
||||
if (localState != null && localState.revision >= latestServerGroup.getRevision() && GroupProtoUtil.isMember(serviceIds.getAci(), localState.members)) {
|
||||
info("Local state is at or later than server");
|
||||
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
|
||||
}
|
||||
@@ -431,16 +425,16 @@ public class GroupsV2StateProcessor {
|
||||
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new ServerGroupLogEntry(latestServerGroup.getFullyDecryptedGroup(), null)));
|
||||
} else {
|
||||
int revisionWeWereAdded = GroupProtoUtil.findRevisionWeWereAdded(latestServerGroup, serviceIds.getAci());
|
||||
int logsNeededFrom = localState != null ? Math.max(localState.getRevision(), revisionWeWereAdded) : revisionWeWereAdded;
|
||||
int logsNeededFrom = localState != null ? Math.max(localState.revision, revisionWeWereAdded) : revisionWeWereAdded;
|
||||
|
||||
boolean includeFirstState = forceIncludeFirst ||
|
||||
localState == null ||
|
||||
localState.getRevision() < 0 ||
|
||||
localState.getRevision() == revisionWeWereAdded ||
|
||||
!GroupProtoUtil.isMember(serviceIds.getAci(), localState.getMembersList()) ||
|
||||
(revision == LATEST && localState.getRevision() + 1 < latestServerGroup.getRevision());
|
||||
localState.revision < 0 ||
|
||||
localState.revision == revisionWeWereAdded ||
|
||||
!GroupProtoUtil.isMember(serviceIds.getAci(), localState.members) ||
|
||||
(revision == LATEST && localState.revision + 1 < latestServerGroup.getRevision());
|
||||
|
||||
info("Requesting from server currentRevision: " + (localState != null ? localState.getRevision() : "null") +
|
||||
info("Requesting from server currentRevision: " + (localState != null ? localState.revision : "null") +
|
||||
" logsNeededFrom: " + logsNeededFrom +
|
||||
" includeFirstState: " + includeFirstState +
|
||||
" forceIncludeFirst: " + forceIncludeFirst);
|
||||
@@ -456,10 +450,10 @@ public class GroupsV2StateProcessor {
|
||||
while (hasMore) {
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
|
||||
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
|
||||
info("Advanced group to revision: " + (newLocalState != null ? newLocalState.getRevision() : "null"));
|
||||
info("Advanced group to revision: " + (newLocalState != null ? newLocalState.revision : "null"));
|
||||
|
||||
if (newLocalState != null && !inputGroupState.hasMore() && !forceIncludeFirst) {
|
||||
int newLocalRevision = newLocalState.getRevision();
|
||||
int newLocalRevision = newLocalState.revision;
|
||||
int requestRevision = (revision == LATEST) ? latestServerGroup.getRevision() : revision;
|
||||
if (newLocalRevision < requestRevision) {
|
||||
warn( "Paging again with force first snapshot enabled due to error processing changes. New local revision [" + newLocalRevision + "] hasn't reached our desired level [" + requestRevision + "]");
|
||||
@@ -473,7 +467,7 @@ public class GroupsV2StateProcessor {
|
||||
|
||||
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
|
||||
|
||||
if (localState == null || localState.getRevision() != GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
if (localState == null || localState.revision != GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
timestamp = profileAndMessageHelper.insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
|
||||
}
|
||||
|
||||
@@ -491,12 +485,12 @@ public class GroupsV2StateProcessor {
|
||||
hasMore = inputGroupState.hasMore();
|
||||
|
||||
if (hasMore) {
|
||||
info("Request next page from server revision: " + finalState.getRevision() + " nextPageRevision: " + inputGroupState.getNextPageRevision());
|
||||
info("Request next page from server revision: " + finalState.revision + " nextPageRevision: " + inputGroupState.getNextPageRevision());
|
||||
inputGroupState = getFullMemberHistoryPage(finalState, inputGroupState.getNextPageRevision(), false);
|
||||
}
|
||||
}
|
||||
|
||||
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
if (localState != null && localState.revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||
info("Inserting single update message for restore placeholder");
|
||||
profileAndMessageHelper.insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(finalState, null)));
|
||||
}
|
||||
@@ -504,7 +498,7 @@ public class GroupsV2StateProcessor {
|
||||
profileAndMessageHelper.persistLearnedProfileKeys(profileKeys);
|
||||
|
||||
if (finalGlobalGroupState.getServerHistory().size() > 0) {
|
||||
info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.getRevision() + 1, finalGlobalGroupState.getLatestRevisionNumber()));
|
||||
info(String.format(Locale.US, "There are more revisions on the server for this group, scheduling for later, V[%d..%d]", finalState.revision + 1, finalGlobalGroupState.getLatestRevisionNumber()));
|
||||
ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId, finalGlobalGroupState.getLatestRevisionNumber()));
|
||||
}
|
||||
|
||||
@@ -557,13 +551,13 @@ public class GroupsV2StateProcessor {
|
||||
.requireV2GroupProperties()
|
||||
.getDecryptedGroup();
|
||||
|
||||
DecryptedGroup simulatedGroupState = DecryptedGroupUtil.removeMember(decryptedGroup, serviceIds.getAci(), decryptedGroup.getRevision() + 1);
|
||||
DecryptedGroup simulatedGroupState = DecryptedGroupUtil.removeMember(decryptedGroup, serviceIds.getAci(), decryptedGroup.revision + 1);
|
||||
|
||||
DecryptedGroupChange simulatedGroupChange = DecryptedGroupChange.newBuilder()
|
||||
.setEditorServiceIdBytes(ACI.UNKNOWN.toByteString())
|
||||
.setRevision(simulatedGroupState.getRevision())
|
||||
.addDeleteMembers(serviceIds.getAci().toByteString())
|
||||
.build();
|
||||
DecryptedGroupChange simulatedGroupChange = new DecryptedGroupChange.Builder()
|
||||
.editorServiceIdBytes(ACI.UNKNOWN.toByteString())
|
||||
.revision(simulatedGroupState.revision)
|
||||
.deleteMembers(Collections.singletonList(serviceIds.getAci().toByteString()))
|
||||
.build();
|
||||
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
|
||||
OutgoingMessage leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, decryptedGroupV2Context, System.currentTimeMillis());
|
||||
@@ -605,14 +599,14 @@ public class GroupsV2StateProcessor {
|
||||
Log.w(TAG, "Group create failed, trying to update");
|
||||
groupDatabase.update(masterKey, newLocalState);
|
||||
}
|
||||
needsAvatarFetch = !TextUtils.isEmpty(newLocalState.getAvatar());
|
||||
needsAvatarFetch = !TextUtils.isEmpty(newLocalState.avatar);
|
||||
} else {
|
||||
groupDatabase.update(masterKey, newLocalState);
|
||||
needsAvatarFetch = !newLocalState.getAvatar().equals(inputGroupState.getLocalState().getAvatar());
|
||||
needsAvatarFetch = !newLocalState.avatar.equals(inputGroupState.getLocalState().avatar);
|
||||
}
|
||||
|
||||
if (needsAvatarFetch) {
|
||||
ApplicationDependencies.getJobManager().add(new AvatarGroupsV2DownloadJob(groupId, newLocalState.getAvatar()));
|
||||
ApplicationDependencies.getJobManager().add(new AvatarGroupsV2DownloadJob(groupId, newLocalState.avatar));
|
||||
}
|
||||
|
||||
profileAndMessageHelper.determineProfileSharing(inputGroupState, newLocalState);
|
||||
@@ -681,25 +675,25 @@ public class GroupsV2StateProcessor {
|
||||
|
||||
void determineProfileSharing(@NonNull GlobalGroupState inputGroupState, @NonNull DecryptedGroup newLocalState) {
|
||||
if (inputGroupState.getLocalState() != null) {
|
||||
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByAci(inputGroupState.getLocalState().getMembersList(), aci).isPresent();
|
||||
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByAci(inputGroupState.getLocalState().members, aci).isPresent();
|
||||
|
||||
if (wasAMemberAlready) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByAci(newLocalState.getMembersList(), aci);
|
||||
Optional<DecryptedPendingMember> selfAsPendingOptional = DecryptedGroupUtil.findPendingByServiceId(newLocalState.getPendingMembersList(), aci);
|
||||
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByAci(newLocalState.members, aci);
|
||||
Optional<DecryptedPendingMember> selfAsPendingOptional = DecryptedGroupUtil.findPendingByServiceId(newLocalState.pendingMembers, aci);
|
||||
|
||||
if (selfAsMemberOptional.isPresent()) {
|
||||
DecryptedMember selfAsMember = selfAsMemberOptional.get();
|
||||
int revisionJoinedAt = selfAsMember.getJoinedAtRevision();
|
||||
int revisionJoinedAt = selfAsMember.joinedAtRevision;
|
||||
|
||||
Optional<Recipient> addedByOptional = Stream.of(inputGroupState.getServerHistory())
|
||||
.map(ServerGroupLogEntry::getChange)
|
||||
.filter(c -> c != null && c.getRevision() == revisionJoinedAt)
|
||||
.filter(c -> c != null && c.revision == revisionJoinedAt)
|
||||
.findFirst()
|
||||
.map(c -> Optional.ofNullable(ServiceId.parseOrNull(c.getEditorServiceIdBytes()))
|
||||
.map(c -> Optional.ofNullable(ServiceId.parseOrNull(c.editorServiceIdBytes))
|
||||
.map(Recipient::externalPush))
|
||||
.orElse(Optional.empty());
|
||||
|
||||
@@ -724,7 +718,7 @@ public class GroupsV2StateProcessor {
|
||||
Log.w(TAG, "Could not find founding member during gv2 create. Not enabling profile sharing.");
|
||||
}
|
||||
} else if (selfAsPendingOptional.isPresent()) {
|
||||
Optional<Recipient> addedBy = selfAsPendingOptional.flatMap(adder -> Optional.ofNullable(UuidUtil.fromByteStringOrNull(adder.getAddedByAci()))
|
||||
Optional<Recipient> addedBy = selfAsPendingOptional.flatMap(adder -> Optional.ofNullable(UuidUtil.fromByteStringOrNull(adder.addedByAci))
|
||||
.map(uuid -> Recipient.externalPush(ACI.from(uuid))));
|
||||
|
||||
if (addedBy.isPresent() && addedBy.get().isBlocked()) {
|
||||
@@ -825,14 +819,14 @@ public class GroupsV2StateProcessor {
|
||||
}
|
||||
|
||||
private Optional<ServiceId> getEditor(@NonNull DecryptedGroupV2Context decryptedGroupV2Context) {
|
||||
DecryptedGroupChange change = decryptedGroupV2Context.getChange();
|
||||
DecryptedGroupChange change = decryptedGroupV2Context.change;
|
||||
Optional<ServiceId> changeEditor = DecryptedGroupUtil.editorServiceId(change);
|
||||
if (changeEditor.isPresent()) {
|
||||
return changeEditor;
|
||||
} else {
|
||||
Optional<DecryptedPendingMember> pending = DecryptedGroupUtil.findPendingByServiceId(decryptedGroupV2Context.getGroupState().getPendingMembersList(), aci);
|
||||
Optional<DecryptedPendingMember> pending = DecryptedGroupUtil.findPendingByServiceId(decryptedGroupV2Context.groupState.pendingMembers, aci);
|
||||
if (pending.isPresent()) {
|
||||
return Optional.ofNullable(ACI.parseOrNull(pending.get().getAddedByAci()));
|
||||
return Optional.ofNullable(ACI.parseOrNull(pending.get().addedByAci));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
@@ -21,7 +21,7 @@ final class LocalGroupLogEntry {
|
||||
@Nullable private final DecryptedGroupChange change;
|
||||
|
||||
LocalGroupLogEntry(@NonNull DecryptedGroup group, @Nullable DecryptedGroupChange change) {
|
||||
if (change != null && group.getRevision() != change.getRevision()) {
|
||||
if (change != null && group.revision != change.revision) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ final class ServerGroupLogEntry {
|
||||
@Nullable private final DecryptedGroupChange change;
|
||||
|
||||
ServerGroupLogEntry(@Nullable DecryptedGroup group, @Nullable DecryptedGroupChange change) {
|
||||
if (change != null && group != null && group.getRevision() != change.getRevision()) {
|
||||
if (change != null && group != null && group.revision != change.revision) {
|
||||
Log.w(TAG, "Ignoring change with revision number not matching group");
|
||||
change = null;
|
||||
}
|
||||
@@ -43,8 +43,8 @@ final class ServerGroupLogEntry {
|
||||
}
|
||||
|
||||
int getRevision() {
|
||||
if (group != null) return group.getRevision();
|
||||
else if (change != null) return change.getRevision();
|
||||
if (group != null) return group.revision;
|
||||
else if (change != null) return change.revision;
|
||||
else throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user