mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Paralellize profile fetching
This commit is contained in:
parent
3b81ba3596
commit
fba7a6a75c
8 changed files with 181 additions and 140 deletions
|
@ -130,6 +130,9 @@
|
||||||
{
|
{
|
||||||
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PE\\E"
|
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PE\\E"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PH\\E"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E"
|
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E"
|
||||||
},
|
},
|
||||||
|
|
|
@ -197,8 +197,7 @@ public class ManagerImpl implements Manager {
|
||||||
avatarStore,
|
avatarStore,
|
||||||
unidentifiedAccessHelper::getAccessFor,
|
unidentifiedAccessHelper::getAccessFor,
|
||||||
this::resolveSignalServiceAddress);
|
this::resolveSignalServiceAddress);
|
||||||
final GroupV2Helper groupV2Helper = new GroupV2Helper(profileHelper::getRecipientProfileKeyCredential,
|
final GroupV2Helper groupV2Helper = new GroupV2Helper(profileHelper,
|
||||||
profileHelper::getRecipientProfile,
|
|
||||||
account::getSelfRecipientId,
|
account::getSelfRecipientId,
|
||||||
dependencies.getGroupsV2Operations(),
|
dependencies.getGroupsV2Operations(),
|
||||||
dependencies.getGroupsV2Api(),
|
dependencies.getGroupsV2Api(),
|
||||||
|
@ -210,7 +209,7 @@ public class ManagerImpl implements Manager {
|
||||||
account.getRecipientStore(),
|
account.getRecipientStore(),
|
||||||
this::handleIdentityFailure,
|
this::handleIdentityFailure,
|
||||||
this::getGroupInfo,
|
this::getGroupInfo,
|
||||||
profileHelper::getRecipientProfile,
|
profileHelper,
|
||||||
this::refreshRegisteredUser);
|
this::refreshRegisteredUser);
|
||||||
this.groupHelper = new GroupHelper(account,
|
this.groupHelper = new GroupHelper(account,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV2;
|
||||||
import org.asamk.signal.manager.storage.recipients.Profile;
|
import org.asamk.signal.manager.storage.recipients.Profile;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.util.IOUtils;
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
|
import org.asamk.signal.manager.util.Utils;
|
||||||
import org.signal.storageservice.protos.groups.AccessControl;
|
import org.signal.storageservice.protos.groups.AccessControl;
|
||||||
import org.signal.storageservice.protos.groups.GroupChange;
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
|
@ -44,6 +45,7 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -54,8 +56,7 @@ public class GroupV2Helper {
|
||||||
|
|
||||||
private final static Logger logger = LoggerFactory.getLogger(GroupV2Helper.class);
|
private final static Logger logger = LoggerFactory.getLogger(GroupV2Helper.class);
|
||||||
|
|
||||||
private final ProfileKeyCredentialProvider profileKeyCredentialProvider;
|
private final ProfileHelper profileHelper;
|
||||||
private final ProfileProvider profileProvider;
|
|
||||||
private final SelfRecipientIdProvider selfRecipientIdProvider;
|
private final SelfRecipientIdProvider selfRecipientIdProvider;
|
||||||
private final GroupsV2Operations groupsV2Operations;
|
private final GroupsV2Operations groupsV2Operations;
|
||||||
private final GroupsV2Api groupsV2Api;
|
private final GroupsV2Api groupsV2Api;
|
||||||
|
@ -64,15 +65,13 @@ public class GroupV2Helper {
|
||||||
private HashMap<Integer, AuthCredentialResponse> groupApiCredentials;
|
private HashMap<Integer, AuthCredentialResponse> groupApiCredentials;
|
||||||
|
|
||||||
public GroupV2Helper(
|
public GroupV2Helper(
|
||||||
final ProfileKeyCredentialProvider profileKeyCredentialProvider,
|
final ProfileHelper profileHelper,
|
||||||
final ProfileProvider profileProvider,
|
|
||||||
final SelfRecipientIdProvider selfRecipientIdProvider,
|
final SelfRecipientIdProvider selfRecipientIdProvider,
|
||||||
final GroupsV2Operations groupsV2Operations,
|
final GroupsV2Operations groupsV2Operations,
|
||||||
final GroupsV2Api groupsV2Api,
|
final GroupsV2Api groupsV2Api,
|
||||||
final SignalServiceAddressResolver addressResolver
|
final SignalServiceAddressResolver addressResolver
|
||||||
) {
|
) {
|
||||||
this.profileKeyCredentialProvider = profileKeyCredentialProvider;
|
this.profileHelper = profileHelper;
|
||||||
this.profileProvider = profileProvider;
|
|
||||||
this.selfRecipientIdProvider = selfRecipientIdProvider;
|
this.selfRecipientIdProvider = selfRecipientIdProvider;
|
||||||
this.groupsV2Operations = groupsV2Operations;
|
this.groupsV2Operations = groupsV2Operations;
|
||||||
this.groupsV2Api = groupsV2Api;
|
this.groupsV2Api = groupsV2Api;
|
||||||
|
@ -149,7 +148,7 @@ public class GroupV2Helper {
|
||||||
private GroupsV2Operations.NewGroup buildNewGroup(
|
private GroupsV2Operations.NewGroup buildNewGroup(
|
||||||
String name, Set<RecipientId> members, byte[] avatar
|
String name, Set<RecipientId> members, byte[] avatar
|
||||||
) {
|
) {
|
||||||
final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientIdProvider.getSelfRecipientId());
|
final var profileKeyCredential = profileHelper.getRecipientProfileKeyCredential(selfRecipientIdProvider.getSelfRecipientId());
|
||||||
if (profileKeyCredential == null) {
|
if (profileKeyCredential == null) {
|
||||||
logger.warn("Cannot create a V2 group as self does not have a versioned profile");
|
logger.warn("Cannot create a V2 group as self does not have a versioned profile");
|
||||||
return null;
|
return null;
|
||||||
|
@ -157,10 +156,14 @@ public class GroupV2Helper {
|
||||||
|
|
||||||
if (!areMembersValid(members)) return null;
|
if (!areMembersValid(members)) return null;
|
||||||
|
|
||||||
var self = new GroupCandidate(getSelfAci().uuid(), Optional.fromNullable(profileKeyCredential));
|
final var self = new GroupCandidate(getSelfAci().uuid(), Optional.fromNullable(profileKeyCredential));
|
||||||
var candidates = members.stream()
|
final var memberList = new ArrayList<>(members);
|
||||||
.map(member -> new GroupCandidate(addressResolver.resolveSignalServiceAddress(member).getAci().uuid(),
|
final var credentials = profileHelper.getRecipientProfileKeyCredential(memberList).stream();
|
||||||
Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
|
final var uuids = memberList.stream()
|
||||||
|
.map(member -> addressResolver.resolveSignalServiceAddress(member).getAci().uuid());
|
||||||
|
var candidates = Utils.zip(uuids,
|
||||||
|
credentials,
|
||||||
|
(uuid, credential) -> new GroupCandidate(uuid, Optional.fromNullable(credential)))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
final var groupSecretParams = GroupSecretParams.generate();
|
final var groupSecretParams = GroupSecretParams.generate();
|
||||||
|
@ -174,8 +177,8 @@ public class GroupV2Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean areMembersValid(final Set<RecipientId> members) {
|
private boolean areMembersValid(final Set<RecipientId> members) {
|
||||||
final var noGv2Capability = members.stream()
|
final var noGv2Capability = profileHelper.getRecipientProfile(new ArrayList<>(members))
|
||||||
.map(profileProvider::getProfile)
|
.stream()
|
||||||
.filter(profile -> profile != null && !profile.getCapabilities().contains(Profile.Capability.gv2))
|
.filter(profile -> profile != null && !profile.getCapabilities().contains(Profile.Capability.gv2))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if (noGv2Capability.size() > 0) {
|
if (noGv2Capability.size() > 0) {
|
||||||
|
@ -221,9 +224,13 @@ public class GroupV2Helper {
|
||||||
throw new IOException("Failed to update group");
|
throw new IOException("Failed to update group");
|
||||||
}
|
}
|
||||||
|
|
||||||
var candidates = newMembers.stream()
|
final var memberList = new ArrayList<>(newMembers);
|
||||||
.map(member -> new GroupCandidate(addressResolver.resolveSignalServiceAddress(member).getAci().uuid(),
|
final var credentials = profileHelper.getRecipientProfileKeyCredential(memberList).stream();
|
||||||
Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
|
final var uuids = memberList.stream()
|
||||||
|
.map(member -> addressResolver.resolveSignalServiceAddress(member).getAci().uuid());
|
||||||
|
var candidates = Utils.zip(uuids,
|
||||||
|
credentials,
|
||||||
|
(uuid, credential) -> new GroupCandidate(uuid, Optional.fromNullable(credential)))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
final var aci = getSelfAci();
|
final var aci = getSelfAci();
|
||||||
|
@ -333,7 +340,7 @@ public class GroupV2Helper {
|
||||||
final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
||||||
|
|
||||||
final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
|
final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
|
||||||
final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
|
final var profileKeyCredential = profileHelper.getRecipientProfileKeyCredential(selfRecipientId);
|
||||||
if (profileKeyCredential == null) {
|
if (profileKeyCredential == null) {
|
||||||
throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
|
throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
|
||||||
}
|
}
|
||||||
|
@ -352,7 +359,7 @@ public class GroupV2Helper {
|
||||||
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
|
|
||||||
final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
|
final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
|
||||||
final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
|
final var profileKeyCredential = profileHelper.getRecipientProfileKeyCredential(selfRecipientId);
|
||||||
if (profileKeyCredential == null) {
|
if (profileKeyCredential == null) {
|
||||||
throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
|
throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,11 @@ import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Maybe;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
|
||||||
public final class ProfileHelper {
|
public final class ProfileHelper {
|
||||||
|
@ -69,33 +69,35 @@ public final class ProfileHelper {
|
||||||
getRecipientProfile(recipientId, true);
|
getRecipientProfile(recipientId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ProfileKeyCredential> getRecipientProfileKeyCredential(List<RecipientId> recipientIds) {
|
||||||
|
final var profileFetches = recipientIds.stream().map(recipientId -> {
|
||||||
|
var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
|
||||||
|
if (profileKeyCredential != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retrieveProfile(recipientId,
|
||||||
|
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL).onErrorComplete();
|
||||||
|
}).filter(Objects::nonNull).toList();
|
||||||
|
Maybe.merge(profileFetches).blockingSubscribe();
|
||||||
|
|
||||||
|
return recipientIds.stream().map(r -> account.getProfileStore().getProfileKeyCredential(r)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
public ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) {
|
public ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) {
|
||||||
var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
|
var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
|
||||||
if (profileKeyCredential != null) {
|
if (profileKeyCredential != null) {
|
||||||
return profileKeyCredential;
|
return profileKeyCredential;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileAndCredential profileAndCredential;
|
|
||||||
try {
|
try {
|
||||||
profileAndCredential = retrieveProfileAndCredential(recipientId,
|
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL));
|
||||||
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
|
logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
|
return account.getProfileStore().getProfileKeyCredential(recipientId);
|
||||||
account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential);
|
|
||||||
|
|
||||||
var profileKey = account.getProfileStore().getProfileKey(recipientId);
|
|
||||||
if (profileKey != null) {
|
|
||||||
final var profile = decryptProfileAndDownloadAvatar(recipientId,
|
|
||||||
profileKey,
|
|
||||||
profileAndCredential.getProfile());
|
|
||||||
account.getProfileStore().storeProfile(recipientId, profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return profileKeyCredential;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,73 +166,43 @@ public final class ProfileHelper {
|
||||||
account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
|
account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Set<RecipientId> pendingProfileRequest = new HashSet<>();
|
public List<Profile> getRecipientProfile(List<RecipientId> recipientIds) {
|
||||||
|
final var profileFetches = recipientIds.stream().map(recipientId -> {
|
||||||
|
var profile = account.getProfileStore().getProfile(recipientId);
|
||||||
|
if (!isProfileRefreshRequired(profile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE).onErrorComplete();
|
||||||
|
}).filter(Objects::nonNull).toList();
|
||||||
|
Maybe.merge(profileFetches).blockingSubscribe();
|
||||||
|
|
||||||
|
return recipientIds.stream().map(r -> account.getProfileStore().getProfile(r)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
private Profile getRecipientProfile(RecipientId recipientId, boolean force) {
|
private Profile getRecipientProfile(RecipientId recipientId, boolean force) {
|
||||||
var profile = account.getProfileStore().getProfile(recipientId);
|
var profile = account.getProfileStore().getProfile(recipientId);
|
||||||
|
|
||||||
var now = System.currentTimeMillis();
|
if (!force && !isProfileRefreshRequired(profile)) {
|
||||||
// Profiles are cached for 24h before retrieving them again, unless forced
|
|
||||||
if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 6 * 60 * 60 * 1000) {
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (pendingProfileRequest) {
|
|
||||||
if (pendingProfileRequest.contains(recipientId)) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
pendingProfileRequest.add(recipientId);
|
|
||||||
}
|
|
||||||
final SignalServiceProfile encryptedProfile;
|
|
||||||
try {
|
try {
|
||||||
encryptedProfile = retrieveEncryptedProfile(recipientId);
|
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE));
|
||||||
} finally {
|
|
||||||
synchronized (pendingProfileRequest) {
|
|
||||||
pendingProfileRequest.remove(recipientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Profile newProfile = null;
|
|
||||||
if (encryptedProfile != null) {
|
|
||||||
var profileKey = account.getProfileStore().getProfileKey(recipientId);
|
|
||||||
if (profileKey != null) {
|
|
||||||
newProfile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
|
|
||||||
if (newProfile == null) {
|
|
||||||
account.getProfileStore().storeProfileKey(recipientId, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newProfile == null) {
|
|
||||||
newProfile = (
|
|
||||||
profile == null ? Profile.newBuilder() : Profile.newBuilder(profile)
|
|
||||||
).withLastUpdateTimestamp(System.currentTimeMillis())
|
|
||||||
.withUnidentifiedAccessMode(ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null))
|
|
||||||
.withCapabilities(ProfileUtils.getCapabilities(encryptedProfile))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newProfile == null) {
|
|
||||||
newProfile = (
|
|
||||||
profile == null ? Profile.newBuilder() : Profile.newBuilder(profile)
|
|
||||||
).withLastUpdateTimestamp(now)
|
|
||||||
.withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
|
|
||||||
.withCapabilities(Set.of())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
account.getProfileStore().storeProfile(recipientId, newProfile);
|
|
||||||
|
|
||||||
return newProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) {
|
|
||||||
try {
|
|
||||||
return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
|
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return account.getProfileStore().getProfile(recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isProfileRefreshRequired(final Profile profile) {
|
||||||
|
if (profile == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Profiles are cached for 6h before retrieving them again, unless forced
|
||||||
|
final var now = System.currentTimeMillis();
|
||||||
|
return now - profile.getLastUpdateTimestamp() >= 6 * 60 * 60 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SignalServiceProfile retrieveProfileSync(String username) throws IOException {
|
private SignalServiceProfile retrieveProfileSync(String username) throws IOException {
|
||||||
|
@ -238,29 +210,6 @@ public final class ProfileHelper {
|
||||||
return dependencies.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent(), locale);
|
return dependencies.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent(), locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileAndCredential retrieveProfileAndCredential(
|
|
||||||
final RecipientId recipientId, final SignalServiceProfile.RequestType requestType
|
|
||||||
) throws IOException {
|
|
||||||
final var profileAndCredential = retrieveProfileSync(recipientId, requestType);
|
|
||||||
final var profile = profileAndCredential.getProfile();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var newIdentity = account.getIdentityKeyStore()
|
|
||||||
.saveIdentity(recipientId,
|
|
||||||
new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
|
|
||||||
new Date());
|
|
||||||
|
|
||||||
if (newIdentity) {
|
|
||||||
account.getSessionStore().archiveSessions(recipientId);
|
|
||||||
account.getSenderKeyStore().deleteSharedWith(recipientId);
|
|
||||||
}
|
|
||||||
} catch (InvalidKeyException ignored) {
|
|
||||||
logger.warn("Got invalid identity key in profile for {}",
|
|
||||||
addressResolver.resolveSignalServiceAddress(recipientId).getIdentifier());
|
|
||||||
}
|
|
||||||
return profileAndCredential;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Profile decryptProfileAndDownloadAvatar(
|
private Profile decryptProfileAndDownloadAvatar(
|
||||||
final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
|
final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
|
||||||
) {
|
) {
|
||||||
|
@ -281,11 +230,9 @@ public final class ProfileHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileAndCredential retrieveProfileSync(
|
private ProfileAndCredential blockingGetProfile(Single<ProfileAndCredential> profile) throws IOException {
|
||||||
RecipientId recipientId, SignalServiceProfile.RequestType requestType
|
|
||||||
) throws IOException {
|
|
||||||
try {
|
try {
|
||||||
return retrieveProfile(recipientId, requestType).blockingGet();
|
return profile.blockingGet();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
if (e.getCause() instanceof PushNetworkException) {
|
if (e.getCause() instanceof PushNetworkException) {
|
||||||
throw (PushNetworkException) e.getCause();
|
throw (PushNetworkException) e.getCause();
|
||||||
|
@ -304,7 +251,58 @@ public final class ProfileHelper {
|
||||||
var profileKey = Optional.fromNullable(account.getProfileStore().getProfileKey(recipientId));
|
var profileKey = Optional.fromNullable(account.getProfileStore().getProfileKey(recipientId));
|
||||||
|
|
||||||
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
||||||
return retrieveProfile(address, profileKey, unidentifiedAccess, requestType);
|
return retrieveProfile(address, profileKey, unidentifiedAccess, requestType).doOnSuccess(p -> {
|
||||||
|
final var encryptedProfile = p.getProfile();
|
||||||
|
|
||||||
|
if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) {
|
||||||
|
final var profileKeyCredential = p.getProfileKeyCredential().orNull();
|
||||||
|
account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var profile = account.getProfileStore().getProfile(recipientId);
|
||||||
|
|
||||||
|
Profile newProfile = null;
|
||||||
|
if (profileKey.isPresent()) {
|
||||||
|
newProfile = decryptProfileAndDownloadAvatar(recipientId, profileKey.get(), encryptedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newProfile == null) {
|
||||||
|
newProfile = (
|
||||||
|
profile == null ? Profile.newBuilder() : Profile.newBuilder(profile)
|
||||||
|
).withLastUpdateTimestamp(System.currentTimeMillis())
|
||||||
|
.withUnidentifiedAccessMode(ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null))
|
||||||
|
.withCapabilities(ProfileUtils.getCapabilities(encryptedProfile))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
account.getProfileStore().storeProfile(recipientId, newProfile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var newIdentity = account.getIdentityKeyStore()
|
||||||
|
.saveIdentity(recipientId,
|
||||||
|
new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey())),
|
||||||
|
new Date());
|
||||||
|
|
||||||
|
if (newIdentity) {
|
||||||
|
account.getSessionStore().archiveSessions(recipientId);
|
||||||
|
account.getSenderKeyStore().deleteSharedWith(recipientId);
|
||||||
|
}
|
||||||
|
} catch (InvalidKeyException ignored) {
|
||||||
|
logger.warn("Got invalid identity key in profile for {}",
|
||||||
|
addressResolver.resolveSignalServiceAddress(recipientId).getIdentifier());
|
||||||
|
}
|
||||||
|
}).doOnError(e -> {
|
||||||
|
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
|
||||||
|
final var profile = account.getProfileStore().getProfile(recipientId);
|
||||||
|
final var newProfile = (
|
||||||
|
profile == null ? Profile.newBuilder() : Profile.newBuilder(profile)
|
||||||
|
).withLastUpdateTimestamp(System.currentTimeMillis())
|
||||||
|
.withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
|
||||||
|
.withCapabilities(Set.of())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
account.getProfileStore().storeProfile(recipientId, newProfile);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Single<ProfileAndCredential> retrieveProfile(
|
private Single<ProfileAndCredential> retrieveProfile(
|
||||||
|
@ -376,7 +374,7 @@ public final class ProfileHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) {
|
private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) {
|
||||||
var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId);
|
var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId, true);
|
||||||
|
|
||||||
if (unidentifiedAccess.isPresent()) {
|
if (unidentifiedAccess.isPresent()) {
|
||||||
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class SendHelper {
|
||||||
private final RecipientResolver recipientResolver;
|
private final RecipientResolver recipientResolver;
|
||||||
private final IdentityFailureHandler identityFailureHandler;
|
private final IdentityFailureHandler identityFailureHandler;
|
||||||
private final GroupProvider groupProvider;
|
private final GroupProvider groupProvider;
|
||||||
private final ProfileProvider profileProvider;
|
private final ProfileHelper profileHelper;
|
||||||
private final RecipientRegistrationRefresher recipientRegistrationRefresher;
|
private final RecipientRegistrationRefresher recipientRegistrationRefresher;
|
||||||
|
|
||||||
public SendHelper(
|
public SendHelper(
|
||||||
|
@ -68,7 +68,7 @@ public class SendHelper {
|
||||||
final RecipientResolver recipientResolver,
|
final RecipientResolver recipientResolver,
|
||||||
final IdentityFailureHandler identityFailureHandler,
|
final IdentityFailureHandler identityFailureHandler,
|
||||||
final GroupProvider groupProvider,
|
final GroupProvider groupProvider,
|
||||||
final ProfileProvider profileProvider,
|
final ProfileHelper profileHelper,
|
||||||
final RecipientRegistrationRefresher recipientRegistrationRefresher
|
final RecipientRegistrationRefresher recipientRegistrationRefresher
|
||||||
) {
|
) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
|
@ -78,7 +78,7 @@ public class SendHelper {
|
||||||
this.recipientResolver = recipientResolver;
|
this.recipientResolver = recipientResolver;
|
||||||
this.identityFailureHandler = identityFailureHandler;
|
this.identityFailureHandler = identityFailureHandler;
|
||||||
this.groupProvider = groupProvider;
|
this.groupProvider = groupProvider;
|
||||||
this.profileProvider = profileProvider;
|
this.profileHelper = profileHelper;
|
||||||
this.recipientRegistrationRefresher = recipientRegistrationRefresher;
|
this.recipientRegistrationRefresher = recipientRegistrationRefresher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,16 +356,17 @@ public class SendHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<RecipientId> getSenderKeyCapableRecipientIds(final Set<RecipientId> recipientIds) {
|
private Set<RecipientId> getSenderKeyCapableRecipientIds(final Set<RecipientId> recipientIds) {
|
||||||
final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
|
final var selfProfile = profileHelper.getRecipientProfile(account.getSelfRecipientId());
|
||||||
if (selfProfile == null || !selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
|
if (selfProfile == null || !selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
|
||||||
logger.debug("Not all of our devices support sender key. Using legacy.");
|
logger.debug("Not all of our devices support sender key. Using legacy.");
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
final var senderKeyTargets = new HashSet<RecipientId>();
|
final var senderKeyTargets = new HashSet<RecipientId>();
|
||||||
for (final var recipientId : recipientIds) {
|
final var recipientList = new ArrayList<>(recipientIds);
|
||||||
// TODO filter out unregistered
|
final var profiles = profileHelper.getRecipientProfile(recipientList).iterator();
|
||||||
final var profile = profileProvider.getProfile(recipientId);
|
for (final var recipientId : recipientList) {
|
||||||
|
final var profile = profiles.next();
|
||||||
if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) {
|
if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -433,8 +434,8 @@ public class SendHelper {
|
||||||
List<SignalServiceAddress> addresses = recipientIdList.stream()
|
List<SignalServiceAddress> addresses = recipientIdList.stream()
|
||||||
.map(addressResolver::resolveSignalServiceAddress)
|
.map(addressResolver::resolveSignalServiceAddress)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
List<UnidentifiedAccess> unidentifiedAccesses = recipientIdList.stream()
|
List<UnidentifiedAccess> unidentifiedAccesses = unidentifiedAccessHelper.getAccessFor(recipientIdList)
|
||||||
.map(unidentifiedAccessHelper::getAccessFor)
|
.stream()
|
||||||
.map(Optional::get)
|
.map(Optional::get)
|
||||||
.map(UnidentifiedAccessPair::getTargetUnidentifiedAccess)
|
.map(UnidentifiedAccessPair::getTargetUnidentifiedAccess)
|
||||||
.map(Optional::get)
|
.map(Optional::get)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import org.asamk.signal.manager.storage.recipients.Profile;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
||||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||||
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
@ -89,8 +90,10 @@ public class UnidentifiedAccessHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getSelfUnidentifiedAccessKey() {
|
private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh) {
|
||||||
var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
|
var selfProfile = noRefresh
|
||||||
|
? account.getProfileStore().getProfile(account.getSelfRecipientId())
|
||||||
|
: profileProvider.getProfile(account.getSelfRecipientId());
|
||||||
if (selfProfile != null
|
if (selfProfile != null
|
||||||
&& selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
|
&& selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
|
||||||
return createUnrestrictedUnidentifiedAccess();
|
return createUnrestrictedUnidentifiedAccess();
|
||||||
|
@ -98,15 +101,23 @@ public class UnidentifiedAccessHelper {
|
||||||
return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
|
return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getTargetUnidentifiedAccessKey(RecipientId recipient) {
|
private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId, boolean noRefresh) {
|
||||||
var targetProfile = profileProvider.getProfile(recipient);
|
var targetProfile = noRefresh
|
||||||
|
? account.getProfileStore().getProfile(recipientId)
|
||||||
|
: profileProvider.getProfile(recipientId);
|
||||||
if (targetProfile == null) {
|
if (targetProfile == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var theirProfileKey = account.getProfileStore().getProfileKey(recipientId);
|
||||||
|
return getTargetUnidentifiedAccessKey(targetProfile, theirProfileKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getTargetUnidentifiedAccessKey(
|
||||||
|
final Profile targetProfile, final ProfileKey theirProfileKey
|
||||||
|
) {
|
||||||
switch (targetProfile.getUnidentifiedAccessMode()) {
|
switch (targetProfile.getUnidentifiedAccessMode()) {
|
||||||
case ENABLED:
|
case ENABLED:
|
||||||
var theirProfileKey = account.getProfileStore().getProfileKey(recipient);
|
|
||||||
if (theirProfileKey == null) {
|
if (theirProfileKey == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +131,7 @@ public class UnidentifiedAccessHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<UnidentifiedAccessPair> getAccessForSync() {
|
public Optional<UnidentifiedAccessPair> getAccessForSync() {
|
||||||
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
|
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false);
|
||||||
var selfUnidentifiedAccessCertificate = getSenderCertificate();
|
var selfUnidentifiedAccessCertificate = getSenderCertificate();
|
||||||
|
|
||||||
if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
|
if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
|
||||||
|
@ -141,12 +152,16 @@ public class UnidentifiedAccessHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
|
public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
|
||||||
var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
|
return getAccessFor(recipient, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient, boolean noRefresh) {
|
||||||
|
var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient, noRefresh);
|
||||||
if (recipientUnidentifiedAccessKey == null) {
|
if (recipientUnidentifiedAccessKey == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
|
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
|
||||||
var selfUnidentifiedAccessCertificate = getSenderCertificateFor(recipient);
|
var selfUnidentifiedAccessCertificate = getSenderCertificateFor(recipient);
|
||||||
if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
|
if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
|
|
|
@ -6,5 +6,5 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
|
|
||||||
public interface UnidentifiedAccessProvider {
|
public interface UnidentifiedAccessProvider {
|
||||||
|
|
||||||
Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipientId);
|
Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipientId, boolean noRefresh);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,12 @@ import java.io.InputStream;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.Spliterators;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
|
@ -88,4 +94,16 @@ public class Utils {
|
||||||
|
|
||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <L, R, T> Stream<T> zip(Stream<L> leftStream, Stream<R> rightStream, BiFunction<L, R, T> combiner) {
|
||||||
|
Spliterator<L> lefts = leftStream.spliterator();
|
||||||
|
Spliterator<R> rights = rightStream.spliterator();
|
||||||
|
return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(Long.min(lefts.estimateSize(),
|
||||||
|
rights.estimateSize()), lefts.characteristics() & rights.characteristics()) {
|
||||||
|
@Override
|
||||||
|
public boolean tryAdvance(Consumer<? super T> action) {
|
||||||
|
return lefts.tryAdvance(left -> rights.tryAdvance(right -> action.accept(combiner.apply(left, right))));
|
||||||
|
}
|
||||||
|
}, leftStream.isParallel() || rightStream.isParallel());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue