Convert SignalService, Database, Group, Payment, and other remaining protos to wire.

This commit is contained in:
Cody Henthorne
2023-09-18 15:32:43 -04:00
committed by Alex Hart
parent a6b7d0bcc5
commit efbd5cab85
267 changed files with 7100 additions and 7214 deletions

View File

@@ -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__!";

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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;
});

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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();
}

View File

@@ -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))
);
}
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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();
}
}