package org.thoughtcrime.securesms.groups.v2; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.protobuf.ByteString; 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.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.logging.Log; import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; /** * Collects profile keys from group states. *
* Separates out "authoritative" profile keys that came from a group update created by their owner. *
* Authoritative profile keys can be used to overwrite local profile keys.
* Non-authoritative profile keys can be used to fill in missing knowledge.
*/
public final class ProfileKeySet {
private static final String TAG = Log.tag(ProfileKeySet.class);
private final Map
* If the change came from the member whose profile key is changing then it is regarded as
* authoritative.
*/
public void addKeysFromGroupChange(@NonNull DecryptedGroupChange change) {
UUID editor = UuidUtil.fromByteStringOrNull(change.getEditor());
for (DecryptedMember member : change.getNewMembersList()) {
addMemberKey(member, editor);
}
for (DecryptedMember member : change.getPromotePendingMembersList()) {
addMemberKey(member, editor);
}
for (DecryptedMember member : change.getModifiedProfileKeysList()) {
addMemberKey(member, editor);
}
for (DecryptedRequestingMember member : change.getNewRequestingMembersList()) {
addMemberKey(editor, member.getUuid(), member.getProfileKey());
}
}
/**
* Add new profile keys from the group state.
*
* Profile keys found in group state are never authoritative as the change cannot be easily
* attributed to a member and it's possible that the group is out of date. So profile keys
* 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()) {
addMemberKey(member, null);
}
}
private void addMemberKey(@NonNull DecryptedMember member, @Nullable UUID changeSource) {
addMemberKey(changeSource, member.getUuid(), member.getProfileKey());
}
private void addMemberKey(@Nullable UUID changeSource,
@NonNull ByteString memberUuidBytes,
@NonNull ByteString profileKeyBytes)
{
UUID memberUuid = UuidUtil.fromByteString(memberUuidBytes);
if (UuidUtil.UNKNOWN_UUID.equals(memberUuid)) {
Log.w(TAG, "Seen unknown member UUID");
return;
}
ProfileKey profileKey;
try {
profileKey = new ProfileKey(profileKeyBytes.toByteArray());
} catch (InvalidInputException e) {
Log.w(TAG, "Bad profile key in group");
return;
}
if (memberUuid.equals(changeSource)) {
authoritativeProfileKeys.put(memberUuid, profileKey);
profileKeys.remove(memberUuid);
} else {
if (!authoritativeProfileKeys.containsKey(memberUuid)) {
profileKeys.put(memberUuid, profileKey);
}
}
}
public Map