116 lines
3.8 KiB
Java
116 lines
3.8 KiB
Java
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.
|
|
* <p>
|
|
* Separates out "authoritative" profile keys that came from a group update created by their owner.
|
|
* <p>
|
|
* 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<UUID, ProfileKey> profileKeys = new LinkedHashMap<>();
|
|
private final Map<UUID, ProfileKey> authoritativeProfileKeys = new LinkedHashMap<>();
|
|
|
|
/**
|
|
* Add new profile keys from a group change.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* 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<UUID, ProfileKey> getProfileKeys() {
|
|
return profileKeys;
|
|
}
|
|
|
|
public Map<UUID, ProfileKey> getAuthoritativeProfileKeys() {
|
|
return authoritativeProfileKeys;
|
|
}
|
|
}
|