"name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
+{
+ "name":"com.squareup.wire.Message",
+ "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
+},
+{
+ "name":"com.squareup.wire.ProtoAdapter"
+},
{
"name":"com.squareup.wire.internal.ImmutableList",
"allDeclaredFields":true,
{
"name":"java.io.FilePermission"
},
+{
+ "name":"java.io.OutputStream"
+},
{
"name":"java.io.Serializable",
- "allDeclaredMethods":true
+ "allDeclaredFields":true,
+ "allDeclaredMethods":true,
+ "allDeclaredClasses":true
},
{
"name":"java.lang.Boolean",
{
"name":"kotlin.String"
},
+{
+ "name":"kotlin.Unit"
+},
{
"name":"kotlin.collections.AbstractCollection",
"allDeclaredFields":true,
{
"name":"long[]"
},
+{
+ "name":"okio.BufferedSink"
+},
{
"name":"okio.ByteString"
},
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "methods":[{"name":"groupId","parameterTypes":[] }, {"name":"type","parameterTypes":[] }]
+ "methods":[{"name":"groupId","parameterTypes":[] }, {"name":"groupName","parameterTypes":[] }, {"name":"revision","parameterTypes":[] }, {"name":"type","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.json.JsonMention",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
- "methods":[{"name":"<init>","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }]
+ "methods":[{"name":"<init>","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"accountEntropyPool","parameterTypes":[] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"mediaRootBackupKey","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }]
+ "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStorageServiceEncryptionV2","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest",
{
"name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"
},
+{
+ "name":"org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["java.lang.String","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }]
+},
{
"name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage",
"allDeclaredFields":true,
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord",
- "allDeclaredFields":true
+ "allDeclaredFields":true,
+ "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Builder"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Companion"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PhoneNumberSharingMode"
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PinnedConversation",
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord",
- "allDeclaredFields":true
+ "allDeclaredFields":true,
+ "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Builder"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Companion"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$IdentityState"
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Name",
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record",
- "allDeclaredFields":true
+ "allDeclaredFields":true,
+ "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record$Builder"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record$Companion"
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record",
- "allDeclaredFields":true
+ "allDeclaredFields":true,
+ "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$Builder"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$Companion"
+},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$StorySendMode"
},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord",
"name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord$Identifier",
"fields":[{"name":"raw_"}, {"name":"type_"}]
},
+{
+ "name":"org.whispersystems.signalservice.internal.storage.protos.OptionalBool"
+},
{
"name":"org.whispersystems.signalservice.internal.storage.protos.Payments",
"allDeclaredFields":true
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
logback = "ch.qos.logback:logback-classic:1.5.12"
-signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_110"
+signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_111"
sqlite = "org.xerial:sqlite-jdbc:3.47.0.0"
hikari = "com.zaxxer:HikariCP:6.2.1"
junit-jupiter = "org.junit.jupiter:junit-jupiter:5.11.3"
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
- return new Blocked(blockedListMessage.getAddresses()
- .stream()
- .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
- .toApiRecipientAddress())
- .toList(), blockedListMessage.getGroupIds().stream().map(GroupId::unknownVersion).toList());
+ return new Blocked(blockedListMessage.individuals.stream()
+ .map(d -> new RecipientAddress(d.getAci() == null ? null : d.getAci().toString(),
+ null,
+ d.getE164(),
+ null))
+ .toList(), blockedListMessage.groupIds.stream().map(GroupId::unknownVersion).toList());
}
}
}
public enum Capability {
- storage;
+ storage,
+ storageServiceEncryptionV2Capability;
public static Capability valueOfOrNull(String value) {
try {
private static final byte[] backupServerPublicParams = Base64.getDecoder()
.decode("AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O");
- private static Environment LIBSIGNAL_NET_ENV = Environment.PRODUCTION;
+ private static final Environment LIBSIGNAL_NET_ENV = Environment.PRODUCTION;
static SignalServiceConfiguration createDefaultServiceConfiguration(
final List<Interceptor> interceptors
proxy,
zkGroupServerPublicParams,
genericServerPublicParams,
- backupServerPublicParams);
+ backupServerPublicParams,
+ false);
}
static ECPublicKey getUnidentifiedSenderTrustRoot() {
public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
final var deleteSync = !isPrimaryDevice;
- return new AccountAttributes.Capabilities(true, deleteSync, true);
+ final var storageEncryptionV2 = !isPrimaryDevice;
+ return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2);
}
public static ServiceEnvironmentConfig getServiceEnvironmentConfig(
private static final byte[] backupServerPublicParams = Base64.getDecoder()
.decode("AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8");
- private static Network.Environment LIBSIGNAL_NET_ENV = Network.Environment.STAGING;
+ private static final Network.Environment LIBSIGNAL_NET_ENV = Network.Environment.STAGING;
static SignalServiceConfiguration createDefaultServiceConfiguration(
final List<Interceptor> interceptors
proxy,
zkGroupServerPublicParams,
genericServerPublicParams,
- backupServerPublicParams);
+ backupServerPublicParams,
+ false);
}
static ECPublicKey getUnidentifiedSenderTrustRoot() {
import org.asamk.signal.manager.api.CaptchaRequiredException;
import org.asamk.signal.manager.api.DeviceLinkUrl;
import org.asamk.signal.manager.api.IncorrectPinException;
-import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.api.RateLimitException;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
+import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import okio.ByteString;
import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
public class AccountHelper {
(sessionId1, verificationCode1, registrationLock) -> {
final var registrationApi = dependencies.getRegistrationApi();
try {
- Utils.handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1));
+ handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1));
} catch (AlreadyVerifiedException e) {
// Already verified so can continue changing number
}
- return Utils.handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(
- sessionId1,
+ return handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(sessionId1,
null,
newNumber,
registrationLock,
dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null));
}
- public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, InvalidDeviceLinkException, org.asamk.signal.manager.api.DeviceLimitExceededException {
- String verificationCode;
+ public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException {
+ final var linkDeviceApi = dependencies.getLinkDeviceApi();
+ final LinkedDeviceVerificationCodeResponse verificationCode;
try {
- verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
+ verificationCode = handleResponseException(linkDeviceApi.getDeviceVerificationCode());
} catch (DeviceLimitExceededException e) {
throw new org.asamk.signal.manager.api.DeviceLimitExceededException("Too many linked devices", e);
}
- try {
- dependencies.getAccountManager()
- .addDevice(deviceLinkInfo.deviceIdentifier(),
- deviceLinkInfo.deviceKey(),
- account.getAciIdentityKeyPair(),
- account.getPniIdentityKeyPair(),
- account.getProfileKey(),
- account.getOrCreatePinMasterKey(),
- verificationCode);
- } catch (InvalidKeyException e) {
- throw new InvalidDeviceLinkException("Invalid device link", e);
- }
+ handleResponseException(dependencies.getLinkDeviceApi()
+ .linkDevice(account.getNumber(),
+ account.getAci(),
+ account.getPni(),
+ deviceLinkInfo.deviceIdentifier(),
+ deviceLinkInfo.deviceKey(),
+ account.getAciIdentityKeyPair(),
+ account.getPniIdentityKeyPair(),
+ account.getProfileKey(),
+ account.getOrCreatePinMasterKey(),
+ account.getOrCreateMediaRootBackupKey(),
+ verificationCode.getVerificationCode(),
+ null));
account.setMultiDevice(true);
context.getJobExecutor().enqueueJob(new SyncStorageJob());
}
final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
return dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupsV2AuthorizationString);
} catch (NonSuccessfulResponseCodeException e) {
- if (e.getCode() == 403) {
+ if (e.code == 403) {
throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null);
}
logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage());
false,
sendEndorsementsExpirationMs);
} catch (NonSuccessfulResponseCodeException e) {
- if (e.getCode() == 403) {
+ if (e.code == 403) {
throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null);
}
logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage());
import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.StickerPack;
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
}
if (syncMessage.getBlockedList().isPresent()) {
final var blockedListMessage = syncMessage.getBlockedList().get();
- for (var address : blockedListMessage.getAddresses()) {
- context.getContactHelper()
- .setContactBlocked(account.getRecipientResolver().resolveRecipient(address), true);
+ for (var individual : blockedListMessage.individuals) {
+ final var address = new RecipientAddress(individual.getAci(), individual.getE164());
+ final var recipientId = account.getRecipientResolver().resolveRecipient(address);
+ context.getContactHelper().setContactBlocked(recipientId, true);
}
- for (var groupId : blockedListMessage.getGroupIds()
- .stream()
+ for (var groupId : blockedListMessage.groupIds.stream()
.map(GroupId::unknownVersion)
.collect(Collectors.toSet())) {
try {
}
if (syncMessage.getKeys().isPresent()) {
final var keysMessage = syncMessage.getKeys().get();
- if (keysMessage.getStorageService().isPresent()) {
- final var storageKey = keysMessage.getStorageService().get();
+ if (keysMessage.getAccountEntropyPool() != null) {
+ final var aep = keysMessage.getAccountEntropyPool();
+ account.setAccountEntropyPool(aep);
+ actions.add(SyncStorageDataAction.create());
+ } else if (keysMessage.getMaster() != null) {
+ final var masterKey = keysMessage.getMaster();
+ account.setMasterKey(masterKey);
+ actions.add(SyncStorageDataAction.create());
+ } else if (keysMessage.getStorageService() != null) {
+ final var storageKey = keysMessage.getStorageService();
account.setStorageKey(storageKey);
actions.add(SyncStorageDataAction.create());
}
- if (keysMessage.getMaster().isPresent()) {
- final var masterKey = keysMessage.getMaster().get();
- account.setMasterKey(masterKey);
+ if (keysMessage.getMediaRootBackupKey() != null) {
+ final var mrb = keysMessage.getMediaRootBackupKey();
+ account.setMediaRootBackupKey(mrb);
actions.add(SyncStorageDataAction.create());
}
}
// This can happen when the primary device has changed phone number
logger.warn("Failed to updated pre keys: {}", e.getMessage());
} catch (NonSuccessfulResponseCodeException e) {
- if (serviceIdType != ServiceIdType.PNI || e.getCode() != 422) {
+ if (serviceIdType != ServiceIdType.PNI || e.code != 422) {
throw e;
}
logger.warn("Failed to set PNI pre keys, ignoring for now. Account needs to be reregistered to fix this.");
final var profile = account.getProfileStore().getProfile(recipientId);
- if (recipientId.equals(account.getSelfRecipientId())) {
- final var isUnrestricted = encryptedProfile.isUnrestrictedUnidentifiedAccess();
- if (account.isUnrestrictedUnidentifiedAccess() != isUnrestricted) {
- account.setUnrestrictedUnidentifiedAccess(isUnrestricted);
- }
- }
-
Profile newProfile = null;
if (profileKey.isPresent()) {
logger.trace("Decrypting profile");
.build();
}
+ if (recipientId.equals(account.getSelfRecipientId())) {
+ final var isUnrestricted = encryptedProfile.isUnrestrictedUnidentifiedAccess();
+ if (account.isUnrestrictedUnidentifiedAccess() != isUnrestricted) {
+ account.setUnrestrictedUnidentifiedAccess(isUnrestricted);
+ }
+ if (account.isPrimaryDevice() && profile != null && newProfile.getCapabilities()
+ .contains(Profile.Capability.storageServiceEncryptionV2Capability) && !profile.getCapabilities()
+ .contains(Profile.Capability.storageServiceEncryptionV2Capability)) {
+ context.getJobExecutor().enqueueJob(new SyncStorageJob(true));
+ }
+ }
+
try {
logger.trace("Storing identity");
final var identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey()));
newNumbers,
account.getRecipientStore().getServiceIdToProfileKeyMap(),
token,
- dependencies.getServiceEnvironmentConfig().cdsiMrenclave(),
null,
dependencies.getLibSignalNetwork(),
newToken -> {
import org.asamk.signal.manager.api.GroupIdV1;
import org.asamk.signal.manager.api.GroupIdV2;
+import org.asamk.signal.manager.api.Profile;
import org.asamk.signal.manager.internal.SignalDependencies;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.storage.RecordIkm;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.storage.StorageKey;
+import org.whispersystems.signalservice.api.storage.StorageRecordConvertersKt;
+import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
+import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestIfDifferentVersionResult;
+import org.whispersystems.signalservice.api.storage.StorageServiceRepository.WriteStorageRecordsResult;
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
+import org.whispersystems.signalservice.internal.storage.protos.StorageRecord;
import java.io.IOException;
import java.sql.Connection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.Collectors;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
+
public class StorageHelper {
private static final Logger logger = LoggerFactory.getLogger(StorageHelper.class);
}
public void syncDataWithStorage() throws IOException {
- final var storageKey = account.getOrCreateStorageKey();
+ var storageKey = account.getOrCreateStorageKey();
if (storageKey == null) {
if (!account.isPrimaryDevice()) {
logger.debug("Storage key unknown, requesting from primary device.");
logger.trace("Reading manifest from remote storage");
final var localManifestVersion = account.getStorageManifestVersion();
- final var localManifest = account.getStorageManifest().orElse(SignalStorageManifest.EMPTY);
- SignalStorageManifest remoteManifest;
- try {
- remoteManifest = dependencies.getAccountManager()
- .getStorageManifestIfDifferentVersion(storageKey, localManifestVersion)
- .orElse(localManifest);
- } catch (InvalidKeyException e) {
- logger.warn("Manifest couldn't be decrypted.");
- if (account.isPrimaryDevice()) {
- try {
- forcePushToStorage(storageKey);
- } catch (RetryLaterException rle) {
- // TODO retry later
- return;
+ final var localManifest = account.getStorageManifest().orElse(SignalStorageManifest.Companion.getEMPTY());
+ final var storageServiceRepository = dependencies.getStorageServiceRepository();
+ final var result = storageServiceRepository.getStorageManifestIfDifferentVersion(storageKey,
+ localManifestVersion);
+
+ var needsForcePush = false;
+ final var remoteManifest = switch (result) {
+ case ManifestIfDifferentVersionResult.DifferentVersion diff -> {
+ final var manifest = diff.getManifest();
+ storeManifestLocally(manifest);
+ yield manifest;
+ }
+ case ManifestIfDifferentVersionResult.DecryptionError ignore -> {
+ logger.warn("Manifest couldn't be decrypted.");
+ if (account.isPrimaryDevice()) {
+ needsForcePush = true;
+ } else {
+ context.getSyncHelper().requestSyncKeys();
}
+ yield null;
}
- return;
- }
+ case ManifestIfDifferentVersionResult.SameVersion ignored -> localManifest;
+ case ManifestIfDifferentVersionResult.NetworkError e -> throw e.getException();
+ case ManifestIfDifferentVersionResult.StatusCodeError e -> throw e.getException();
+ default -> throw new RuntimeException("Unhandled ManifestIfDifferentVersionResult type");
+ };
- logger.trace("Manifest versions: local {}, remote {}", localManifestVersion, remoteManifest.getVersion());
+ if (remoteManifest != null) {
+ logger.trace("Manifest versions: local {}, remote {}", localManifestVersion, remoteManifest.version);
- var needsForcePush = false;
- if (remoteManifest.getVersion() > localManifestVersion) {
- logger.trace("Remote version was newer, reading records.");
- needsForcePush = readDataFromStorage(storageKey, localManifest, remoteManifest);
- } else if (remoteManifest.getVersion() < localManifest.getVersion()) {
- logger.debug("Remote storage manifest version was older. User might have switched accounts.");
- }
- logger.trace("Done reading data from remote storage");
+ if (remoteManifest.version > localManifestVersion) {
+ logger.trace("Remote version was newer, reading records.");
+ needsForcePush = readDataFromStorage(storageKey, localManifest, remoteManifest);
+ } else if (remoteManifest.version < localManifest.version) {
+ logger.debug("Remote storage manifest version was older. User might have switched accounts.");
+ }
+ logger.trace("Done reading data from remote storage");
- if (localManifest != remoteManifest) {
- storeManifestLocally(remoteManifest);
+ readRecordsWithPreviouslyUnknownTypes(storageKey, remoteManifest);
}
- readRecordsWithPreviouslyUnknownTypes(storageKey);
-
logger.trace("Adding missing storageIds to local data");
account.getRecipientStore().setMissingStorageIds();
account.getGroupStore().setMissingStorageIds();
var needsMultiDeviceSync = false;
- try {
- needsMultiDeviceSync = writeToStorage(storageKey, remoteManifest, needsForcePush);
- } catch (RetryLaterException e) {
- // TODO retry later
- return;
+
+ if (account.needsStorageKeyMigration()) {
+ logger.debug("Storage needs force push due to new account entropy pool");
+ // Set new aep and reset previous master key and storage key
+ account.setAccountEntropyPool(account.getOrCreateAccountEntropyPool());
+ storageKey = account.getOrCreateStorageKey();
+ context.getSyncHelper().sendKeysMessage();
+ needsForcePush = true;
+ } else if (remoteManifest == null) {
+ if (account.isPrimaryDevice()) {
+ needsForcePush = true;
+ }
+ } else if (remoteManifest.recordIkm == null && account.getSelfRecipientProfile()
+ .getCapabilities()
+ .contains(Profile.Capability.storageServiceEncryptionV2Capability)) {
+ logger.debug("The SSRE2 capability is supported, but no recordIkm is set! Force pushing.");
+ needsForcePush = true;
+ } else {
+ try {
+ needsMultiDeviceSync = writeToStorage(storageKey, remoteManifest, needsForcePush);
+ } catch (RetryLaterException e) {
+ // TODO retry later
+ return;
+ }
}
if (needsForcePush) {
logger.debug("Done syncing data with remote storage");
}
+ public void forcePushToStorage() throws IOException {
+ if (!account.isPrimaryDevice()) {
+ return;
+ }
+
+ final var storageKey = account.getOrCreateStorageKey();
+ if (storageKey == null) {
+ return;
+ }
+
+ try {
+ forcePushToStorage(storageKey);
+ } catch (RetryLaterException e) {
+ // TODO retry later
+ }
+ }
+
private boolean readDataFromStorage(
final StorageKey storageKey,
final SignalStorageManifest localManifest,
try (final var connection = account.getAccountDatabase().getConnection()) {
connection.setAutoCommit(false);
- var idDifference = findIdDifference(remoteManifest.getStorageIds(), localManifest.getStorageIds());
+ var idDifference = findIdDifference(remoteManifest.storageIds, localManifest.storageIds);
if (idDifference.hasTypeMismatches() && account.isPrimaryDevice()) {
logger.debug("Found type mismatches in the ID sets! Scheduling a force push after this sync completes.");
needsForcePush = true;
}
- logger.debug("Pre-Merge ID Difference :: " + idDifference);
+ logger.debug("Pre-Merge ID Difference :: {}", idDifference);
if (!idDifference.localOnlyIds().isEmpty()) {
final var updated = account.getRecipientStore()
}
if (!idDifference.isEmpty()) {
- final var remoteOnlyRecords = getSignalStorageRecords(storageKey, idDifference.remoteOnlyIds());
+ final var remoteOnlyRecords = getSignalStorageRecords(storageKey,
+ remoteManifest,
+ idDifference.remoteOnlyIds());
if (remoteOnlyRecords.size() != idDifference.remoteOnlyIds().size()) {
- logger.debug("Could not find all remote-only records! Requested: "
- + idDifference.remoteOnlyIds()
- .size()
- + ", Found: "
- + remoteOnlyRecords.size()
- + ". These stragglers should naturally get deleted during the sync.");
+ logger.debug(
+ "Could not find all remote-only records! Requested: {}, Found: {}. These stragglers should naturally get deleted during the sync.",
+ idDifference.remoteOnlyIds().size(),
+ remoteOnlyRecords.size());
}
final var unknownInserts = processKnownRecords(connection, remoteOnlyRecords);
return needsForcePush;
}
- private void readRecordsWithPreviouslyUnknownTypes(final StorageKey storageKey) throws IOException {
+ private void readRecordsWithPreviouslyUnknownTypes(
+ final StorageKey storageKey,
+ final SignalStorageManifest remoteManifest
+ ) throws IOException {
try (final var connection = account.getAccountDatabase().getConnection()) {
connection.setAutoCommit(false);
final var knownUnknownIds = account.getUnknownStorageIdStore()
.getUnknownStorageIds(connection, KNOWN_TYPES);
if (!knownUnknownIds.isEmpty()) {
- logger.debug("We have " + knownUnknownIds.size() + " unknown records that we can now process.");
+ logger.debug("We have {} unknown records that we can now process.", knownUnknownIds.size());
- final var remote = getSignalStorageRecords(storageKey, knownUnknownIds);
+ final var remote = getSignalStorageRecords(storageKey, remoteManifest, knownUnknownIds);
- logger.debug("Found " + remote.size() + " of the known-unknowns remotely.");
+ logger.debug("Found {} of the known-unknowns remotely.", remote.size());
processKnownRecords(connection, remote);
account.getUnknownStorageIdStore()
connection.setAutoCommit(false);
final var localStorageIds = getAllLocalStorageIds(connection);
- final var idDifference = findIdDifference(remoteManifest.getStorageIds(), localStorageIds);
- logger.debug("ID Difference :: " + idDifference);
+ final var idDifference = findIdDifference(remoteManifest.storageIds, localStorageIds);
+ logger.debug("ID Difference :: {}", idDifference);
final var remoteDeletes = idDifference.remoteOnlyIds().stream().map(StorageId::getRaw).toList();
final var remoteInserts = buildLocalStorageRecords(connection, idDifference.localOnlyIds());
// TODO check if local storage record proto matches remote, then reset to remote storage_id
- remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.getVersion() + 1,
+ remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.version + 1,
account.getDeviceId(),
+ remoteManifest.recordIkm,
localStorageIds), remoteInserts, remoteDeletes);
connection.commit();
}
if (remoteWriteOperation.isEmpty()) {
- logger.debug("No remote writes needed. Still at version: " + remoteManifest.getVersion());
+ logger.debug("No remote writes needed. Still at version: {}", remoteManifest.version);
return false;
}
logger.debug("We have something to write remotely.");
- logger.debug("WriteOperationResult :: " + remoteWriteOperation);
+ logger.debug("WriteOperationResult :: {}", remoteWriteOperation);
StorageSyncValidations.validate(remoteWriteOperation,
remoteManifest,
needsForcePush,
account.getSelfRecipientAddress());
- final Optional<SignalStorageManifest> conflict;
- try {
- conflict = dependencies.getAccountManager()
- .writeStorageRecords(storageKey,
- remoteWriteOperation.manifest(),
- remoteWriteOperation.inserts(),
- remoteWriteOperation.deletes());
- } catch (InvalidKeyException e) {
- logger.warn("Failed to decrypt conflicting storage manifest: {}", e.getMessage());
- throw new IOException(e);
- }
-
- if (conflict.isPresent()) {
- logger.debug("Hit a conflict when trying to resolve the conflict! Retrying.");
- throw new RetryLaterException();
+ final var result = dependencies.getStorageServiceRepository()
+ .writeStorageRecords(storageKey,
+ remoteWriteOperation.manifest(),
+ remoteWriteOperation.inserts(),
+ remoteWriteOperation.deletes());
+ switch (result) {
+ case WriteStorageRecordsResult.ConflictError ignored -> {
+ logger.debug("Hit a conflict when trying to resolve the conflict! Retrying.");
+ throw new RetryLaterException();
+ }
+ case WriteStorageRecordsResult.NetworkError networkError -> throw networkError.getException();
+ case WriteStorageRecordsResult.StatusCodeError statusCodeError -> throw statusCodeError.getException();
+ case WriteStorageRecordsResult.Success ignored -> {
+ logger.debug("Saved new manifest. Now at version: {}", remoteWriteOperation.manifest().version);
+ storeManifestLocally(remoteWriteOperation.manifest());
+ return true;
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + result);
}
-
- logger.debug("Saved new manifest. Now at version: " + remoteWriteOperation.manifest().getVersion());
- storeManifestLocally(remoteWriteOperation.manifest());
-
- return true;
}
private void forcePushToStorage(
) throws IOException, RetryLaterException {
logger.debug("Force pushing local state to remote storage");
- final var currentVersion = dependencies.getAccountManager().getStorageManifestVersion();
+ final var currentVersion = handleResponseException(dependencies.getStorageServiceRepository()
+ .getManifestVersion());
final var newVersion = currentVersion + 1;
final var newStorageRecords = new ArrayList<SignalStorageRecord>();
final Map<RecipientId, StorageId> newContactStorageIds;
final var recipient = account.getRecipientStore().getRecipient(connection, recipientId);
final var accountRecord = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
recipient,
- account.getUsernameLink(),
- storageId.getRaw());
- newStorageRecords.add(accountRecord);
+ account.getUsernameLink());
+ newStorageRecords.add(new SignalStorageRecord(storageId,
+ new StorageRecord.Builder().account(accountRecord).build()));
} else {
final var recipient = account.getRecipientStore().getRecipient(connection, recipientId);
final var address = recipient.getAddress().getIdentifier();
final var identity = account.getIdentityKeyStore().getIdentityInfo(connection, address);
- final var record = StorageSyncModels.localToRemoteRecord(recipient, identity, storageId.getRaw());
- newStorageRecords.add(record);
+ final var record = StorageSyncModels.localToRemoteRecord(recipient, identity);
+ newStorageRecords.add(new SignalStorageRecord(storageId,
+ new StorageRecord.Builder().contact(record).build()));
}
}
for (final var groupId : groupV1Ids) {
final var storageId = newGroupV1StorageIds.get(groupId);
final var group = account.getGroupStore().getGroup(connection, groupId);
- final var record = StorageSyncModels.localToRemoteRecord(group, storageId.getRaw());
- newStorageRecords.add(record);
+ final var record = StorageSyncModels.localToRemoteRecord(group);
+ newStorageRecords.add(new SignalStorageRecord(storageId,
+ new StorageRecord.Builder().groupV1(record).build()));
}
final var groupV2Ids = account.getGroupStore().getGroupV2Ids(connection);
for (final var groupId : groupV2Ids) {
final var storageId = newGroupV2StorageIds.get(groupId);
final var group = account.getGroupStore().getGroup(connection, groupId);
- final var record = StorageSyncModels.localToRemoteRecord(group, storageId.getRaw());
- newStorageRecords.add(record);
+ final var record = StorageSyncModels.localToRemoteRecord(group);
+ newStorageRecords.add(new SignalStorageRecord(storageId,
+ new StorageRecord.Builder().groupV2(record).build()));
}
connection.commit();
}
final var newStorageIds = newStorageRecords.stream().map(SignalStorageRecord::getId).toList();
- final var manifest = new SignalStorageManifest(newVersion, account.getDeviceId(), newStorageIds);
+ final RecordIkm recordIkm;
+ if (account.getSelfRecipientProfile()
+ .getCapabilities()
+ .contains(Profile.Capability.storageServiceEncryptionV2Capability)) {
+ logger.debug("Generating and including a new recordIkm.");
+ recordIkm = RecordIkm.Companion.generate();
+ } else {
+ logger.debug("SSRE2 not yet supported. Not including recordIkm.");
+ recordIkm = null;
+ }
+
+ final var manifest = new SignalStorageManifest(newVersion, account.getDeviceId(), recordIkm, newStorageIds);
StorageSyncValidations.validateForcePush(manifest, newStorageRecords, account.getSelfRecipientAddress());
- final Optional<SignalStorageManifest> conflict;
- try {
- if (newVersion > 1) {
- logger.trace("Force-pushing data. Inserting {} IDs.", newStorageRecords.size());
- conflict = dependencies.getAccountManager()
- .resetStorageRecords(storageServiceKey, manifest, newStorageRecords);
- } else {
- logger.trace("First version, normal push. Inserting {} IDs.", newStorageRecords.size());
- conflict = dependencies.getAccountManager()
- .writeStorageRecords(storageServiceKey, manifest, newStorageRecords, Collections.emptyList());
- }
- } catch (InvalidKeyException e) {
- logger.debug("Hit an invalid key exception, which likely indicates a conflict.", e);
- throw new RetryLaterException();
+ final WriteStorageRecordsResult result;
+ if (newVersion > 1) {
+ logger.trace("Force-pushing data. Inserting {} IDs.", newStorageRecords.size());
+ result = dependencies.getStorageServiceRepository()
+ .resetAndWriteStorageRecords(storageServiceKey, manifest, newStorageRecords);
+ } else {
+ logger.trace("First version, normal push. Inserting {} IDs.", newStorageRecords.size());
+ result = dependencies.getStorageServiceRepository()
+ .writeStorageRecords(storageServiceKey, manifest, newStorageRecords, Collections.emptyList());
}
- if (conflict.isPresent()) {
- logger.debug("Hit a conflict. Trying again.");
- throw new RetryLaterException();
+ switch (result) {
+ case WriteStorageRecordsResult.ConflictError ignored -> {
+ logger.debug("Hit a conflict. Trying again.");
+ throw new RetryLaterException();
+ }
+ case WriteStorageRecordsResult.NetworkError networkError -> throw networkError.getException();
+ case WriteStorageRecordsResult.StatusCodeError statusCodeError -> throw statusCodeError.getException();
+ case WriteStorageRecordsResult.Success ignored -> {
+ logger.debug("Force push succeeded. Updating local manifest version to: {}", manifest.version);
+ storeManifestLocally(manifest);
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + result);
}
- logger.debug("Force push succeeded. Updating local manifest version to: " + manifest.getVersion());
- storeManifestLocally(manifest);
-
try (final var connection = account.getAccountDatabase().getConnection()) {
connection.setAutoCommit(false);
account.getRecipientStore().updateStorageIds(connection, newContactStorageIds);
private void storeManifestLocally(
final SignalStorageManifest remoteManifest
) {
- account.setStorageManifestVersion(remoteManifest.getVersion());
+ account.setStorageManifestVersion(remoteManifest.version);
account.setStorageManifest(remoteManifest);
}
private List<SignalStorageRecord> getSignalStorageRecords(
final StorageKey storageKey,
+ final SignalStorageManifest manifest,
final List<StorageId> storageIds
) throws IOException {
- List<SignalStorageRecord> records;
- try {
- records = dependencies.getAccountManager().readStorageRecords(storageKey, storageIds);
- } catch (InvalidKeyException e) {
- logger.warn("Failed to read storage records, ignoring.");
- return List.of();
- }
- return records;
+ final var result = dependencies.getStorageServiceRepository()
+ .readStorageRecords(storageKey, manifest.recordIkm, storageIds);
+ return switch (result) {
+ case StorageServiceRepository.StorageRecordResult.DecryptionError decryptionError -> {
+ if (decryptionError.getException() instanceof InvalidKeyException) {
+ logger.warn("Failed to read storage records, ignoring.");
+ yield List.of();
+ } else if (decryptionError.getException() instanceof IOException ioe) {
+ throw ioe;
+ } else {
+ throw new IOException(decryptionError.getException());
+ }
+ }
+ case StorageServiceRepository.StorageRecordResult.NetworkError networkError ->
+ throw networkError.getException();
+ case StorageServiceRepository.StorageRecordResult.StatusCodeError statusCodeError ->
+ throw statusCodeError.getException();
+ case StorageServiceRepository.StorageRecordResult.Success success -> success.getRecords();
+ default -> throw new IllegalStateException("Unexpected value: " + result);
+ };
}
private List<StorageId> getAllLocalStorageIds(final Connection connection) throws SQLException {
final Connection connection,
final List<StorageId> storageIds
) throws SQLException {
- final var records = new ArrayList<SignalStorageRecord>();
+ final var records = new ArrayList<SignalStorageRecord>(storageIds.size());
for (final var storageId : storageIds) {
final var record = buildLocalStorageRecord(connection, storageId);
- if (record != null) {
- records.add(record);
- }
+ records.add(record);
}
return records;
}
final var recipient = account.getRecipientStore().getRecipient(connection, storageId);
final var address = recipient.getAddress().getIdentifier();
final var identity = account.getIdentityKeyStore().getIdentityInfo(connection, address);
- yield StorageSyncModels.localToRemoteRecord(recipient, identity, storageId.getRaw());
+ final var record = StorageSyncModels.localToRemoteRecord(recipient, identity);
+ yield new SignalStorageRecord(storageId, new StorageRecord.Builder().contact(record).build());
}
case ManifestRecord.Identifier.Type.GROUPV1 -> {
final var groupV1 = account.getGroupStore().getGroupV1(connection, storageId);
- yield StorageSyncModels.localToRemoteRecord(groupV1, storageId.getRaw());
+ final var record = StorageSyncModels.localToRemoteRecord(groupV1);
+ yield new SignalStorageRecord(storageId, new StorageRecord.Builder().groupV1(record).build());
}
case ManifestRecord.Identifier.Type.GROUPV2 -> {
final var groupV2 = account.getGroupStore().getGroupV2(connection, storageId);
- yield StorageSyncModels.localToRemoteRecord(groupV2, storageId.getRaw());
+ final var record = StorageSyncModels.localToRemoteRecord(groupV2);
+ yield new SignalStorageRecord(storageId, new StorageRecord.Builder().groupV2(record).build());
}
case ManifestRecord.Identifier.Type.ACCOUNT -> {
final var selfRecipient = account.getRecipientStore()
.getRecipient(connection, account.getSelfRecipientId());
- yield StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
+
+ final var record = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
selfRecipient,
- account.getUsernameLink(),
- storageId.getRaw());
+ account.getUsernameLink());
+ yield new SignalStorageRecord(storageId, new StorageRecord.Builder().account(record).build());
+ }
+ case null, default -> {
+ throw new AssertionError("Got unknown local storage record type: " + storageId);
}
- case null, default -> throw new AssertionError("Got unknown local storage record type: " + storageId);
};
}
final var groupV2RecordProcessor = new GroupV2RecordProcessor(account, connection);
for (final var record : records) {
- logger.debug("Reading record of type {}", record.getType());
- switch (ManifestRecord.Identifier.Type.fromValue(record.getType())) {
- case ACCOUNT -> accountRecordProcessor.process(record.getAccount().get());
- case GROUPV1 -> groupV1RecordProcessor.process(record.getGroupV1().get());
- case GROUPV2 -> groupV2RecordProcessor.process(record.getGroupV2().get());
- case CONTACT -> contactRecordProcessor.process(record.getContact().get());
- case null, default -> unknownRecords.add(record.getId());
+ if (record.getProto().account != null) {
+ logger.debug("Reading record {} of type account", record.getId());
+ accountRecordProcessor.process(StorageRecordConvertersKt.toSignalAccountRecord(record.getProto().account,
+ record.getId()));
+ } else if (record.getProto().groupV1 != null) {
+ logger.debug("Reading record {} of type groupV1", record.getId());
+ groupV1RecordProcessor.process(StorageRecordConvertersKt.toSignalGroupV1Record(record.getProto().groupV1,
+ record.getId()));
+ } else if (record.getProto().groupV2 != null) {
+ logger.debug("Reading record {} of type groupV2", record.getId());
+ groupV2RecordProcessor.process(StorageRecordConvertersKt.toSignalGroupV2Record(record.getProto().groupV2,
+ record.getId()));
+ } else if (record.getProto().contact != null) {
+ logger.debug("Reading record {} of type contact", record.getId());
+ contactRecordProcessor.process(StorageRecordConvertersKt.toSignalContactRecord(record.getProto().contact,
+ record.getId()));
+ } else {
+ unknownRecords.add(record.getId());
}
}
}
public SendMessageResult sendBlockedList() {
- var addresses = new ArrayList<SignalServiceAddress>();
+ var addresses = new ArrayList<BlockedListMessage.Individual>();
for (var record : account.getContactStore().getContacts()) {
if (record.second().isBlocked()) {
- addresses.add(context.getRecipientHelper().resolveSignalServiceAddress(record.first()));
+ final var address = account.getRecipientAddressResolver().resolveRecipientAddress(record.first());
+ if (address.aci().isPresent() || address.number().isPresent()) {
+ addresses.add(new BlockedListMessage.Individual(address.aci().orElse(null),
+ address.number().orElse(null)));
+ }
}
}
var groupIds = new ArrayList<byte[]>();
}
public SendMessageResult sendKeysMessage() {
- var keysMessage = new KeysMessage(Optional.ofNullable(account.getOrCreateStorageKey()),
- Optional.ofNullable(account.getOrCreatePinMasterKey()));
+ var keysMessage = new KeysMessage(account.getOrCreateStorageKey(),
+ account.getOrCreatePinMasterKey(),
+ account.getOrCreateAccountEntropyPool(),
+ account.getOrCreateMediaRootBackupKey());
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forKeys(keysMessage));
}
builder.withMessageExpirationTimeVersion(c.getExpirationTimerVersion().get());
} else {
logger.debug(
- "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: ${}",
+ "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: {}",
recipientId,
c.getExpirationTimerVersion(),
contact == null ? 1 : contact.messageExpirationTimeVersion());
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
+import org.whispersystems.signalservice.api.link.LinkDeviceApi;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.registration.RegistrationApi;
import org.whispersystems.signalservice.api.services.ProfileService;
+import org.whispersystems.signalservice.api.storage.StorageServiceApi;
+import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
private SignalServiceAccountManager accountManager;
private GroupsV2Api groupsV2Api;
private RegistrationApi registrationApi;
+ private LinkDeviceApi linkDeviceApi;
+ private StorageServiceApi storageServiceApi;
private GroupsV2Operations groupsV2Operations;
private ClientZkOperations clientZkOperations;
return getOrCreate(() -> registrationApi, () -> registrationApi = getAccountManager().getRegistrationApi());
}
+ public LinkDeviceApi getLinkDeviceApi() {
+ return getOrCreate(() -> linkDeviceApi, () -> linkDeviceApi = new LinkDeviceApi(getPushServiceSocket()));
+ }
+
+ private StorageServiceApi getStorageServiceApi() {
+ return getOrCreate(() -> storageServiceApi,
+ () -> storageServiceApi = new StorageServiceApi(getPushServiceSocket()));
+ }
+
+ public StorageServiceRepository getStorageServiceRepository() {
+ return new StorageServiceRepository(getStorageServiceApi());
+ }
+
public GroupsV2Operations getGroupsV2Operations() {
return getOrCreate(() -> groupsV2Operations,
() -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()),
public class SyncStorageJob implements Job {
+ private final boolean forcePush;
+
private static final Logger logger = LoggerFactory.getLogger(SyncStorageJob.class);
+ public SyncStorageJob() {
+ this.forcePush = false;
+ }
+
+ public SyncStorageJob(final boolean forcePush) {
+ this.forcePush = forcePush;
+ }
+
@Override
public void run(Context context) {
logger.trace("Running storage sync job");
try {
- context.getStorageHelper().syncDataWithStorage();
+ if (forcePush) {
+ context.getStorageHelper().forcePushToStorage();
+ } else {
+ context.getStorageHelper().syncDataWithStorage();
+ }
} catch (IOException e) {
logger.warn("Failed to sync storage data", e);
}
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.AccountEntropyPool;
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
+import org.whispersystems.signalservice.api.backup.MediaRootBackupKey;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.ServiceId;
private String registrationLockPin;
private MasterKey pinMasterKey;
private StorageKey storageKey;
+ private AccountEntropyPool accountEntropyPool;
+ private MediaRootBackupKey mediaRootBackupKey;
private ProfileKey profileKey;
private Settings settings;
this.isMultiDevice = true;
setLastReceiveTimestamp(0L);
this.pinMasterKey = masterKey;
+ this.accountEntropyPool = null;
getKeyValueStore().storeEntry(storageManifestVersion, -1L);
this.setStorageManifest(null);
this.storageKey = null;
final PreKeyCollection pniPreKeys
) {
this.pinMasterKey = masterKey;
+ this.accountEntropyPool = null;
getKeyValueStore().storeEntry(storageManifestVersion, -1L);
this.setStorageManifest(null);
this.storageKey = null;
if (storage.storageKey != null) {
storageKey = new StorageKey(base64.decode(storage.storageKey));
}
+ if (storage.accountEntropyPool != null) {
+ accountEntropyPool = new AccountEntropyPool(storage.accountEntropyPool);
+ }
+ if (storage.mediaRootBackupKey != null) {
+ mediaRootBackupKey = new MediaRootBackupKey(base64.decode(storage.mediaRootBackupKey));
+ }
if (storage.profileKey != null) {
try {
profileKey = new ProfileKey(base64.decode(storage.profileKey));
registrationLockPin,
pinMasterKey == null ? null : base64.encodeToString(pinMasterKey.serialize()),
storageKey == null ? null : base64.encodeToString(storageKey.serialize()),
+ accountEntropyPool == null ? null : accountEntropyPool.getValue(),
+ mediaRootBackupKey == null ? null : base64.encodeToString(mediaRootBackupKey.getValue()),
profileKey == null ? null : base64.encodeToString(profileKey.serialize()),
usernameLink == null ? null : base64.encodeToString(usernameLink.getEntropy()),
usernameLink == null ? null : usernameLink.getServerId().toString());
return selfRecipientId;
}
+ public Profile getSelfRecipientProfile() {
+ return recipientStore.getProfile(selfRecipientId);
+ }
+
public String getSessionId(final String forNumber) {
final var keyValueStore = getKeyValueStore();
final var sessionNumber = keyValueStore.getEntry(verificationSessionNumber);
public MasterKey getPinBackedMasterKey() {
if (registrationLockPin == null) {
return null;
+ } else if (!isPrimaryDevice()) {
+ return getMasterKey();
}
- return pinMasterKey;
+ return getOrCreatePinMasterKey();
}
public MasterKey getOrCreatePinMasterKey() {
- if (pinMasterKey == null) {
- pinMasterKey = KeyUtils.createMasterKey();
- save();
+ final var key = getMasterKey();
+ if (key != null) {
+ return key;
}
+
+ pinMasterKey = KeyUtils.createMasterKey();
+ save();
return pinMasterKey;
}
+ private MasterKey getMasterKey() {
+ if (pinMasterKey != null) {
+ return pinMasterKey;
+ } else if (accountEntropyPool != null) {
+ return accountEntropyPool.deriveMasterKey();
+ }
+ return null;
+ }
+
public void setMasterKey(MasterKey masterKey) {
if (isPrimaryDevice()) {
return;
}
this.pinMasterKey = masterKey;
+ if (masterKey != null) {
+ this.storageKey = null;
+ }
save();
}
public StorageKey getOrCreateStorageKey() {
- if (pinMasterKey != null) {
- return pinMasterKey.deriveStorageServiceKey();
- } else if (storageKey != null) {
+ if (storageKey != null) {
return storageKey;
+ } else if (pinMasterKey != null) {
+ return pinMasterKey.deriveStorageServiceKey();
+ } else if (accountEntropyPool != null) {
+ return accountEntropyPool.deriveMasterKey().deriveStorageServiceKey();
} else if (!isPrimaryDevice() || !isMultiDevice()) {
// Only upload storage, if a pin master key already exists or linked devices exist
return null;
save();
}
+ public AccountEntropyPool getOrCreateAccountEntropyPool() {
+ if (accountEntropyPool == null) {
+ accountEntropyPool = AccountEntropyPool.Companion.generate();
+ save();
+ }
+ return accountEntropyPool;
+ }
+
+ public void setAccountEntropyPool(final AccountEntropyPool accountEntropyPool) {
+ this.accountEntropyPool = accountEntropyPool;
+ if (accountEntropyPool != null) {
+ this.storageKey = null;
+ this.pinMasterKey = null;
+ }
+ save();
+ }
+
+ public boolean needsStorageKeyMigration() {
+ return isPrimaryDevice() && (storageKey != null || pinMasterKey != null);
+ }
+
+ public MediaRootBackupKey getOrCreateMediaRootBackupKey() {
+ if (mediaRootBackupKey == null) {
+ mediaRootBackupKey = KeyUtils.createMediaRootBackupKey();
+ save();
+ }
+ return mediaRootBackupKey;
+ }
+
+ public void setMediaRootBackupKey(final MediaRootBackupKey mediaRootBackupKey) {
+ this.mediaRootBackupKey = mediaRootBackupKey;
+ save();
+ }
+
public String getRecoveryPassword() {
final var masterKey = getPinBackedMasterKey();
if (masterKey == null) {
return Optional.empty();
}
try (var inputStream = new FileInputStream(storageManifestFile)) {
- return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
+ return Optional.of(SignalStorageManifest.Companion.deserialize(inputStream.readAllBytes()));
} catch (IOException e) {
logger.warn("Failed to read local storage manifest.", e);
return Optional.empty();
String registrationLockPin,
String pinMasterKey,
String storageKey,
+ String accountEntropyPool,
+ String mediaRootBackupKey,
String profileKey,
String usernameLinkEntropy,
String usernameLinkServerId
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
-import org.whispersystems.signalservice.api.util.OptionalUtil;
+import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Optional;
+import static org.asamk.signal.manager.util.Utils.firstNonEmpty;
+import static org.asamk.signal.manager.util.Utils.firstNonNull;
+
/**
* Processes {@link SignalAccountRecord}s.
*/
final var selfRecipientId = account.getSelfRecipientId();
final var recipient = account.getRecipientStore().getRecipient(connection, selfRecipientId);
final var storageId = account.getRecipientStore().getSelfStorageId(connection);
- this.localAccountRecord = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
- recipient,
- account.getUsernameLink(),
- storageId.getRaw()).getAccount().get();
+ this.localAccountRecord = new SignalAccountRecord(storageId,
+ StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
+ recipient,
+ account.getUsernameLink()));
}
@Override
}
@Override
- protected SignalAccountRecord merge(SignalAccountRecord remote, SignalAccountRecord local) {
+ protected SignalAccountRecord merge(SignalAccountRecord remoteRecord, SignalAccountRecord localRecord) {
+ final var remote = remoteRecord.getProto();
+ final var local = localRecord.getProto();
String givenName;
String familyName;
- if (remote.getGivenName().isPresent() || remote.getFamilyName().isPresent()) {
- givenName = remote.getGivenName().orElse("");
- familyName = remote.getFamilyName().orElse("");
+ if (!remote.givenName.isEmpty() || !remote.familyName.isEmpty()) {
+ givenName = remote.givenName;
+ familyName = remote.familyName;
} else {
- givenName = local.getGivenName().orElse("");
- familyName = local.getFamilyName().orElse("");
+ givenName = local.givenName;
+ familyName = local.familyName;
}
- final var payments = remote.getPayments().getEntropy().isPresent() ? remote.getPayments() : local.getPayments();
- final var subscriber = remote.getSubscriber().getId().isPresent()
- ? remote.getSubscriber()
- : local.getSubscriber();
- final var storyViewReceiptsState = remote.getStoryViewReceiptsState() == OptionalBool.UNSET
- ? local.getStoryViewReceiptsState()
- : remote.getStoryViewReceiptsState();
- final var unknownFields = remote.serializeUnknownFields();
- final var avatarUrlPath = OptionalUtil.or(remote.getAvatarUrlPath(), local.getAvatarUrlPath()).orElse("");
- final var profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
- final var noteToSelfArchived = remote.isNoteToSelfArchived();
- final var noteToSelfForcedUnread = remote.isNoteToSelfForcedUnread();
- final var readReceipts = remote.isReadReceiptsEnabled();
- final var typingIndicators = remote.isTypingIndicatorsEnabled();
- final var sealedSenderIndicators = remote.isSealedSenderIndicatorsEnabled();
- final var linkPreviews = remote.isLinkPreviewsEnabled();
- final var unlisted = remote.isPhoneNumberUnlisted();
- final var pinnedConversations = remote.getPinnedConversations();
- final var phoneNumberSharingMode = remote.getPhoneNumberSharingMode();
- final var preferContactAvatars = remote.isPreferContactAvatars();
- final var universalExpireTimer = remote.getUniversalExpireTimer();
- final var e164 = account.isPrimaryDevice() ? local.getE164() : remote.getE164();
- final var defaultReactions = !remote.getDefaultReactions().isEmpty()
- ? remote.getDefaultReactions()
- : local.getDefaultReactions();
- final var displayBadgesOnProfile = remote.isDisplayBadgesOnProfile();
- final var subscriptionManuallyCancelled = remote.isSubscriptionManuallyCancelled();
- final var keepMutedChatsArchived = remote.isKeepMutedChatsArchived();
- final var hasSetMyStoriesPrivacy = remote.hasSetMyStoriesPrivacy();
- final var hasViewedOnboardingStory = remote.hasViewedOnboardingStory() || local.hasViewedOnboardingStory();
- final var storiesDisabled = remote.isStoriesDisabled();
- final var hasSeenGroupStoryEducation = remote.hasSeenGroupStoryEducationSheet()
- || local.hasSeenGroupStoryEducationSheet();
- boolean hasSeenUsernameOnboarding = remote.hasCompletedUsernameOnboarding()
- || local.hasCompletedUsernameOnboarding();
- final var username = remote.getUsername();
- final var usernameLink = remote.getUsernameLink();
-
- final var mergedBuilder = new SignalAccountRecord.Builder(remote.getId().getRaw(), unknownFields).setGivenName(
- givenName)
- .setFamilyName(familyName)
- .setAvatarUrlPath(avatarUrlPath)
- .setProfileKey(profileKey)
- .setNoteToSelfArchived(noteToSelfArchived)
- .setNoteToSelfForcedUnread(noteToSelfForcedUnread)
- .setReadReceiptsEnabled(readReceipts)
- .setTypingIndicatorsEnabled(typingIndicators)
- .setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
- .setLinkPreviewsEnabled(linkPreviews)
- .setUnlistedPhoneNumber(unlisted)
- .setPhoneNumberSharingMode(phoneNumberSharingMode)
- .setPinnedConversations(pinnedConversations)
- .setPreferContactAvatars(preferContactAvatars)
- .setPayments(payments.isEnabled(), payments.getEntropy().orElse(null))
- .setUniversalExpireTimer(universalExpireTimer)
- .setDefaultReactions(defaultReactions)
- .setSubscriber(subscriber)
- .setDisplayBadgesOnProfile(displayBadgesOnProfile)
- .setSubscriptionManuallyCancelled(subscriptionManuallyCancelled)
- .setKeepMutedChatsArchived(keepMutedChatsArchived)
- .setHasSetMyStoriesPrivacy(hasSetMyStoriesPrivacy)
- .setHasViewedOnboardingStory(hasViewedOnboardingStory)
- .setStoriesDisabled(storiesDisabled)
- .setHasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducation)
- .setHasCompletedUsernameOnboarding(hasSeenUsernameOnboarding)
- .setStoryViewReceiptsState(storyViewReceiptsState)
- .setUsername(username)
- .setUsernameLink(usernameLink)
- .setE164(e164);
+ final var mergedBuilder = SignalAccountRecord.Companion.newBuilder(remote.unknownFields().toByteArray())
+ .givenName(givenName)
+ .familyName(familyName)
+ .avatarUrlPath(firstNonEmpty(remote.avatarUrlPath, local.avatarUrlPath))
+ .profileKey(firstNonEmpty(remote.profileKey, local.profileKey))
+ .noteToSelfArchived(remote.noteToSelfArchived)
+ .noteToSelfMarkedUnread(remote.noteToSelfMarkedUnread)
+ .readReceipts(remote.readReceipts)
+ .typingIndicators(remote.typingIndicators)
+ .sealedSenderIndicators(remote.sealedSenderIndicators)
+ .linkPreviews(remote.linkPreviews)
+ .unlistedPhoneNumber(remote.unlistedPhoneNumber)
+ .phoneNumberSharingMode(remote.phoneNumberSharingMode)
+ .pinnedConversations(remote.pinnedConversations)
+ .preferContactAvatars(remote.preferContactAvatars)
+ .universalExpireTimer(remote.universalExpireTimer)
+ .preferredReactionEmoji(firstNonEmpty(remote.preferredReactionEmoji, local.preferredReactionEmoji))
+ .subscriberId(firstNonEmpty(remote.subscriberId, local.subscriberId))
+ .subscriberCurrencyCode(firstNonEmpty(remote.subscriberCurrencyCode, local.subscriberCurrencyCode))
+ .backupsSubscriberId(firstNonEmpty(remote.backupsSubscriberId, local.backupsSubscriberId))
+ .backupsSubscriberCurrencyCode(firstNonEmpty(remote.backupsSubscriberCurrencyCode,
+ local.backupsSubscriberCurrencyCode))
+ .displayBadgesOnProfile(remote.displayBadgesOnProfile)
+ .subscriptionManuallyCancelled(remote.subscriptionManuallyCancelled)
+ .keepMutedChatsArchived(remote.keepMutedChatsArchived)
+ .hasSetMyStoriesPrivacy(remote.hasSetMyStoriesPrivacy)
+ .hasViewedOnboardingStory(remote.hasViewedOnboardingStory || local.hasViewedOnboardingStory)
+ .storiesDisabled(remote.storiesDisabled)
+ .hasSeenGroupStoryEducationSheet(remote.hasSeenGroupStoryEducationSheet
+ || local.hasSeenGroupStoryEducationSheet)
+ .hasCompletedUsernameOnboarding(remote.hasCompletedUsernameOnboarding
+ || local.hasCompletedUsernameOnboarding)
+ .storyViewReceiptsEnabled(remote.storyViewReceiptsEnabled == OptionalBool.UNSET
+ ? local.storyViewReceiptsEnabled
+ : remote.storyViewReceiptsEnabled)
+ .username(remote.username)
+ .usernameLink(remote.usernameLink)
+ .e164(account.isPrimaryDevice() ? local.e164 : remote.e164);
+ if (firstNonNull(remote.payments, local.payments) != null) {
+ mergedBuilder.payments(firstNonNull(remote.payments, local.payments));
+ }
final var merged = mergedBuilder.build();
final var matchesRemote = doProtosMatch(merged, remote);
if (matchesRemote) {
- return remote;
+ return remoteRecord;
}
final var matchesLocal = doProtosMatch(merged, local);
if (matchesLocal) {
- return local;
+ return localRecord;
}
- return mergedBuilder.setId(KeyUtils.createRawStorageId()).build();
+ return new SignalAccountRecord(StorageId.forAccount(KeyUtils.createRawStorageId()), mergedBuilder.build());
}
@Override
@Override
protected void updateLocal(StorageRecordUpdate<SignalAccountRecord> update) throws SQLException {
final var accountRecord = update.newRecord();
+ final var accountProto = accountRecord.getProto();
- if (!accountRecord.getE164().equals(account.getNumber())) {
+ if (!accountProto.e164.equals(account.getNumber())) {
jobExecutor.enqueueJob(new CheckWhoAmIJob());
}
- account.getConfigurationStore().setReadReceipts(connection, accountRecord.isReadReceiptsEnabled());
- account.getConfigurationStore().setTypingIndicators(connection, accountRecord.isTypingIndicatorsEnabled());
+ account.getConfigurationStore().setReadReceipts(connection, accountProto.readReceipts);
+ account.getConfigurationStore().setTypingIndicators(connection, accountProto.typingIndicators);
account.getConfigurationStore()
- .setUnidentifiedDeliveryIndicators(connection, accountRecord.isSealedSenderIndicatorsEnabled());
- account.getConfigurationStore().setLinkPreviews(connection, accountRecord.isLinkPreviewsEnabled());
+ .setUnidentifiedDeliveryIndicators(connection, accountProto.sealedSenderIndicators);
+ account.getConfigurationStore().setLinkPreviews(connection, accountProto.linkPreviews);
account.getConfigurationStore()
.setPhoneNumberSharingMode(connection,
- StorageSyncModels.remoteToLocal(accountRecord.getPhoneNumberSharingMode()));
- account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountRecord.isPhoneNumberUnlisted());
-
- account.setUsername(accountRecord.getUsername() != null && !accountRecord.getUsername().isEmpty()
- ? accountRecord.getUsername()
- : null);
- if (accountRecord.getUsernameLink() != null) {
- final var usernameLink = accountRecord.getUsernameLink();
+ StorageSyncModels.remoteToLocal(accountProto.phoneNumberSharingMode));
+ account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountProto.unlistedPhoneNumber);
+
+ account.setUsername(!accountProto.username.isEmpty() ? accountProto.username : null);
+ if (accountProto.usernameLink != null) {
+ final var usernameLink = accountProto.usernameLink;
account.setUsernameLink(new UsernameLinkComponents(usernameLink.entropy.toByteArray(),
UuidUtil.parseOrThrow(usernameLink.serverId.toByteArray())));
account.getConfigurationStore().setUsernameLinkColor(connection, usernameLink.color.name());
}
- if (accountRecord.getProfileKey().isPresent()) {
+ if (accountProto.profileKey.size() > 0) {
ProfileKey profileKey;
try {
- profileKey = new ProfileKey(accountRecord.getProfileKey().get());
+ profileKey = new ProfileKey(accountProto.profileKey.toByteArray());
} catch (InvalidInputException e) {
logger.debug("Received invalid profile key from storage");
profileKey = null;
}
if (profileKey != null) {
account.setProfileKey(profileKey);
- final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null);
+ final var avatarPath = accountProto.avatarUrlPath.isEmpty() ? null : accountProto.avatarUrlPath;
jobExecutor.enqueueJob(new DownloadProfileAvatarJob(avatarPath));
}
}
final var profile = account.getRecipientStore().getProfile(connection, account.getSelfRecipientId());
final var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
- builder.withGivenName(accountRecord.getGivenName().orElse(null));
- builder.withFamilyName(accountRecord.getFamilyName().orElse(null));
+ builder.withGivenName(accountProto.givenName);
+ builder.withFamilyName(accountProto.familyName);
account.getRecipientStore().storeProfile(connection, account.getSelfRecipientId(), builder.build());
account.getRecipientStore()
.storeStorageRecord(connection,
account.getSelfRecipientId(),
accountRecord.getId(),
- accountRecord.toProto().encode());
+ accountProto.encode());
}
@Override
return 0;
}
- private static boolean doProtosMatch(SignalAccountRecord merged, SignalAccountRecord other) {
- return Arrays.equals(merged.toProto().encode(), other.toProto().encode());
+ private static boolean doProtosMatch(AccountRecord merged, AccountRecord other) {
+ return Arrays.equals(merged.encode(), other.encode());
}
}
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
-import org.whispersystems.signalservice.api.util.OptionalUtil;
+import org.whispersystems.signalservice.api.storage.StorageId;
+import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState;
import java.sql.Connection;
import java.util.Optional;
import java.util.regex.Pattern;
+import okio.ByteString;
+
+import static org.asamk.signal.manager.util.Utils.firstNonEmpty;
+import static org.asamk.signal.manager.util.Utils.nullIfEmpty;
+
public class ContactRecordProcessor extends DefaultStorageRecordProcessor<SignalContactRecord> {
private static final Logger logger = LoggerFactory.getLogger(ContactRecordProcessor.class);
* - You can't have a contact record for yourself. That should be an account record.
*/
@Override
- protected boolean isInvalid(SignalContactRecord remote) {
- boolean hasAci = remote.getAci().isPresent() && remote.getAci().get().isValid();
- boolean hasPni = remote.getPni().isPresent() && remote.getPni().get().isValid();
+ protected boolean isInvalid(SignalContactRecord remoteRecord) {
+ final var remote = remoteRecord.getProto();
+ final var aci = ACI.parseOrNull(remote.aci);
+ final var pni = PNI.parseOrNull(remote.pni);
+ final var e164 = nullIfEmpty(remote.e164);
+ boolean hasAci = aci != null && aci.isValid();
+ boolean hasPni = pni != null && pni.isValid();
if (!hasAci && !hasPni) {
logger.debug("Found a ContactRecord with neither an ACI nor a PNI -- marking as invalid.");
return true;
- } else if (selfAci != null && selfAci.equals(remote.getAci().orElse(null)) || (
- selfPni != null && selfPni.equals(remote.getPni().orElse(null))
- ) || (selfNumber != null && selfNumber.equals(remote.getNumber().orElse(null)))) {
+ } else if (selfAci != null && selfAci.equals(aci) || (
+ selfPni != null && selfPni.equals(pni)
+ ) || (selfNumber != null && selfNumber.equals(e164))) {
logger.debug("Found a ContactRecord for ourselves -- marking as invalid.");
return true;
- } else if (remote.getNumber().isPresent() && !isValidE164(remote.getNumber().get())) {
- logger.debug("Found a record with an invalid E164. Marking as invalid.");
+ } else if (e164 != null && !isValidE164(e164)) {
+ logger.debug("Found a record with an invalid E164 ({}). Marking as invalid.", e164);
return true;
} else {
return false;
@Override
protected Optional<SignalContactRecord> getMatching(SignalContactRecord remote) throws SQLException {
- final var address = getRecipientAddress(remote);
+ final var address = getRecipientAddress(remote.getProto());
final var recipientId = account.getRecipientStore().resolveRecipient(connection, address);
final var recipient = account.getRecipientStore().getRecipient(connection, recipientId);
final var identity = account.getIdentityKeyStore().getIdentityInfo(connection, identifier);
final var storageId = account.getRecipientStore().getStorageId(connection, recipientId);
- return Optional.of(StorageSyncModels.localToRemoteRecord(recipient, identity, storageId.getRaw())
- .getContact()
- .get());
+ return Optional.of(new SignalContactRecord(storageId,
+ StorageSyncModels.localToRemoteRecord(recipient, identity)));
}
@Override
- protected SignalContactRecord merge(SignalContactRecord remote, SignalContactRecord local) {
+ protected SignalContactRecord merge(SignalContactRecord remoteRecord, SignalContactRecord localRecord) {
+ final var remote = remoteRecord.getProto();
+ final var local = localRecord.getProto();
+
String profileGivenName;
String profileFamilyName;
- if (remote.getProfileGivenName().isPresent() || remote.getProfileFamilyName().isPresent()) {
- profileGivenName = remote.getProfileGivenName().orElse("");
- profileFamilyName = remote.getProfileFamilyName().orElse("");
+ if (!remote.givenName.isEmpty() || !remote.familyName.isEmpty()) {
+ profileGivenName = remote.givenName;
+ profileFamilyName = remote.familyName;
} else {
- profileGivenName = local.getProfileGivenName().orElse("");
- profileFamilyName = local.getProfileFamilyName().orElse("");
+ profileGivenName = local.givenName;
+ profileFamilyName = local.familyName;
}
IdentityState identityState;
- byte[] identityKey;
- if (remote.getIdentityKey().isPresent() && (
- remote.getIdentityState() != local.getIdentityState()
- || local.getIdentityKey().isEmpty()
- || !account.isPrimaryDevice()
+ ByteString identityKey;
+ if (remote.identityKey.size() > 0 && (
+ !account.isPrimaryDevice()
+ || remote.identityState != local.identityState
+ || local.identityKey.size() == 0
)) {
- identityState = remote.getIdentityState();
- identityKey = remote.getIdentityKey().get();
+ identityState = remote.identityState;
+ identityKey = remote.identityKey;
} else {
- identityState = local.getIdentityState();
- identityKey = local.getIdentityKey().orElse(null);
+ identityState = local.identityState;
+ identityKey = local.identityKey.size() > 0 ? local.identityKey : ByteString.EMPTY;
}
- if (local.getAci().isPresent()
- && local.getIdentityKey().isPresent()
- && remote.getIdentityKey().isPresent()
- && !Arrays.equals(local.getIdentityKey().get(), remote.getIdentityKey().get())) {
+ if (!local.aci.isEmpty()
+ && local.identityKey.size() > 0
+ && remote.identityKey.size() > 0
+ && !local.identityKey.equals(remote.identityKey)) {
logger.debug("The local and remote identity keys do not match for {}. Enqueueing a profile fetch.",
- local.getAci().orElse(null));
+ local.aci);
final var address = getRecipientAddress(local);
jobExecutor.enqueueJob(new DownloadProfileJob(address));
}
- final var e164sMatchButPnisDont = local.getNumber().isPresent()
- && local.getNumber()
- .get()
- .equals(remote.getNumber().orElse(null))
- && local.getPni().isPresent()
- && remote.getPni().isPresent()
- && !local.getPni().get().equals(remote.getPni().get());
-
- final var pnisMatchButE164sDont = local.getPni().isPresent()
- && local.getPni()
- .get()
- .equals(remote.getPni().orElse(null))
- && local.getNumber().isPresent()
- && remote.getNumber().isPresent()
- && !local.getNumber().get().equals(remote.getNumber().get());
-
- PNI pni;
+ String pni;
String e164;
- if (!account.isPrimaryDevice() && (e164sMatchButPnisDont || pnisMatchButE164sDont)) {
- if (e164sMatchButPnisDont) {
- logger.debug("Matching E164s, but the PNIs differ! Trusting our local pair.");
- } else if (pnisMatchButE164sDont) {
- logger.debug("Matching PNIs, but the E164s differ! Trusting our local pair.");
+ if (account.isPrimaryDevice()) {
+ final var e164sMatchButPnisDont = !local.e164.isEmpty()
+ && local.e164.equals(remote.e164)
+ && !local.pni.isEmpty()
+ && !remote.pni.isEmpty()
+ && !local.pni.equals(remote.pni);
+
+ final var pnisMatchButE164sDont = !local.pni.isEmpty()
+ && local.pni.equals(remote.pni)
+ && !local.e164.isEmpty()
+ && !remote.e164.isEmpty()
+ && !local.e164.equals(remote.e164);
+
+ if (e164sMatchButPnisDont || pnisMatchButE164sDont) {
+ if (e164sMatchButPnisDont) {
+ logger.debug("Matching E164s, but the PNIs differ! Trusting our local pair.");
+ } else if (pnisMatchButE164sDont) {
+ logger.debug("Matching PNIs, but the E164s differ! Trusting our local pair.");
+ }
+ jobExecutor.enqueueJob(new RefreshRecipientsJob());
+ pni = local.pni;
+ e164 = local.e164;
+ } else {
+ pni = firstNonEmpty(remote.pni, local.pni);
+ e164 = firstNonEmpty(remote.e164, local.e164);
}
- jobExecutor.enqueueJob(new RefreshRecipientsJob());
- pni = local.getPni().get();
- e164 = local.getNumber().get();
} else {
- pni = OptionalUtil.or(remote.getPni(), local.getPni()).orElse(null);
- e164 = OptionalUtil.or(remote.getNumber(), local.getNumber()).orElse(null);
+ pni = firstNonEmpty(remote.pni, local.pni);
+ e164 = firstNonEmpty(remote.e164, local.e164);
}
- final var unknownFields = remote.serializeUnknownFields();
- final var aci = local.getAci().isEmpty() ? remote.getAci().orElse(null) : local.getAci().get();
- final var profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
- final var username = OptionalUtil.or(remote.getUsername(), local.getUsername()).orElse("");
- final var blocked = remote.isBlocked();
- final var profileSharing = remote.isProfileSharingEnabled();
- final var archived = remote.isArchived();
- final var forcedUnread = remote.isForcedUnread();
- final var muteUntil = remote.getMuteUntil();
- final var hideStory = remote.shouldHideStory();
- final var unregisteredTimestamp = remote.getUnregisteredTimestamp();
- final var hidden = remote.isHidden();
- final var systemGivenName = account.isPrimaryDevice()
- ? local.getSystemGivenName().orElse("")
- : remote.getSystemGivenName().orElse("");
- final var systemFamilyName = account.isPrimaryDevice()
- ? local.getSystemFamilyName().orElse("")
- : remote.getSystemFamilyName().orElse("");
- final var systemNickname = remote.getSystemNickname().orElse("");
- final var nicknameGivenName = remote.getNicknameGivenName().orElse("");
- final var nicknameFamilyName = remote.getNicknameFamilyName().orElse("");
- final var pniSignatureVerified = remote.isPniSignatureVerified() || local.isPniSignatureVerified();
- final var note = remote.getNote().or(local::getNote).orElse("");
-
- final var mergedBuilder = new SignalContactRecord.Builder(remote.getId().getRaw(), aci, unknownFields).setE164(
- e164)
- .setPni(pni)
- .setProfileGivenName(profileGivenName)
- .setProfileFamilyName(profileFamilyName)
- .setSystemGivenName(systemGivenName)
- .setSystemFamilyName(systemFamilyName)
- .setSystemNickname(systemNickname)
- .setProfileKey(profileKey)
- .setUsername(username)
- .setIdentityState(identityState)
- .setIdentityKey(identityKey)
- .setBlocked(blocked)
- .setProfileSharingEnabled(profileSharing)
- .setArchived(archived)
- .setForcedUnread(forcedUnread)
- .setMuteUntil(muteUntil)
- .setHideStory(hideStory)
- .setUnregisteredTimestamp(unregisteredTimestamp)
- .setHidden(hidden)
- .setPniSignatureVerified(pniSignatureVerified)
- .setNicknameGivenName(nicknameGivenName)
- .setNicknameFamilyName(nicknameFamilyName)
- .setNote(note);
+ final var mergedBuilder = SignalContactRecord.Companion.newBuilder(remote.unknownFields().toByteArray())
+ .aci(local.aci.isEmpty() ? remote.aci : local.aci)
+ .e164(e164)
+ .pni(pni)
+ .givenName(profileGivenName)
+ .familyName(profileFamilyName)
+ .systemGivenName(account.isPrimaryDevice() ? local.systemGivenName : remote.systemGivenName)
+ .systemFamilyName(account.isPrimaryDevice() ? local.systemFamilyName : remote.systemFamilyName)
+ .systemNickname(remote.systemNickname)
+ .profileKey(firstNonEmpty(remote.profileKey, local.profileKey))
+ .username(firstNonEmpty(remote.username, local.username))
+ .identityState(identityState)
+ .identityKey(identityKey)
+ .blocked(remote.blocked)
+ .whitelisted(remote.whitelisted)
+ .archived(remote.archived)
+ .markedUnread(remote.markedUnread)
+ .mutedUntilTimestamp(remote.mutedUntilTimestamp)
+ .hideStory(remote.hideStory)
+ .unregisteredAtTimestamp(remote.unregisteredAtTimestamp)
+ .hidden(remote.hidden)
+ .pniSignatureVerified(remote.pniSignatureVerified || local.pniSignatureVerified)
+ .nickname(remote.nickname)
+ .note(remote.note);
final var merged = mergedBuilder.build();
final var matchesRemote = doProtosMatch(merged, remote);
if (matchesRemote) {
- return remote;
+ return remoteRecord;
}
final var matchesLocal = doProtosMatch(merged, local);
if (matchesLocal) {
- return local;
+ return localRecord;
}
- return mergedBuilder.setId(KeyUtils.createRawStorageId()).build();
+ return new SignalContactRecord(StorageId.forContact(KeyUtils.createRawStorageId()), mergedBuilder.build());
}
@Override
@Override
protected void updateLocal(StorageRecordUpdate<SignalContactRecord> update) throws SQLException {
final var contactRecord = update.newRecord();
- final var address = getRecipientAddress(contactRecord);
+ final var contactProto = contactRecord.getProto();
+ final var address = getRecipientAddress(contactProto);
final var recipientId = account.getRecipientStore().resolveRecipientTrusted(connection, address);
final var recipient = account.getRecipientStore().getRecipient(connection, recipientId);
final var contactNickGivenName = contact == null ? null : contact.nickNameGivenName();
final var contactNickFamilyName = contact == null ? null : contact.nickNameFamilyName();
final var contactNote = contact == null ? null : contact.note();
- if (blocked != contactRecord.isBlocked()
- || profileShared != contactRecord.isProfileSharingEnabled()
- || archived != contactRecord.isArchived()
- || hidden != contactRecord.isHidden()
- || hideStory != contactRecord.shouldHideStory()
- || muteUntil != contactRecord.getMuteUntil()
- || unregisteredTimestamp != contactRecord.getUnregisteredTimestamp()
- || !Objects.equals(contactRecord.getSystemGivenName().orElse(null), contactGivenName)
- || !Objects.equals(contactRecord.getSystemFamilyName().orElse(null), contactFamilyName)
- || !Objects.equals(contactRecord.getSystemNickname().orElse(null), contactNickName)
- || !Objects.equals(contactRecord.getNicknameGivenName().orElse(null), contactNickGivenName)
- || !Objects.equals(contactRecord.getNicknameFamilyName().orElse(null), contactNickFamilyName)
- || !Objects.equals(contactRecord.getNote().orElse(null), contactNote)) {
+ if (blocked != contactProto.blocked
+ || profileShared != contactProto.whitelisted
+ || archived != contactProto.archived
+ || hidden != contactProto.hidden
+ || hideStory != contactProto.hideStory
+ || muteUntil != contactProto.mutedUntilTimestamp
+ || unregisteredTimestamp != contactProto.unregisteredAtTimestamp
+ || !Objects.equals(nullIfEmpty(contactProto.systemGivenName), contactGivenName)
+ || !Objects.equals(nullIfEmpty(contactProto.systemFamilyName), contactFamilyName)
+ || !Objects.equals(nullIfEmpty(contactProto.systemNickname), contactNickName)
+ || !Objects.equals(nullIfEmpty(contactProto.nickname == null ? "" : contactProto.nickname.given),
+ contactNickGivenName)
+ || !Objects.equals(nullIfEmpty(contactProto.nickname == null ? "" : contactProto.nickname.family),
+ contactNickFamilyName)
+ || !Objects.equals(nullIfEmpty(contactProto.note), contactNote)) {
logger.debug("Storing new or updated contact {}", recipientId);
final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
- final var newContact = contactBuilder.withIsBlocked(contactRecord.isBlocked())
- .withIsProfileSharingEnabled(contactRecord.isProfileSharingEnabled())
- .withIsArchived(contactRecord.isArchived())
- .withIsHidden(contactRecord.isHidden())
- .withMuteUntil(contactRecord.getMuteUntil())
- .withHideStory(contactRecord.shouldHideStory())
- .withGivenName(contactRecord.getSystemGivenName().orElse(null))
- .withFamilyName(contactRecord.getSystemFamilyName().orElse(null))
- .withNickName(contactRecord.getSystemNickname().orElse(null))
- .withNickNameGivenName(contactRecord.getNicknameGivenName().orElse(null))
- .withNickNameFamilyName(contactRecord.getNicknameFamilyName().orElse(null))
- .withNote(contactRecord.getNote().orElse(null))
- .withUnregisteredTimestamp(contactRecord.getUnregisteredTimestamp() == 0
- ? null
- : contactRecord.getUnregisteredTimestamp());
+ final var newContact = contactBuilder.withIsBlocked(contactProto.blocked)
+ .withIsProfileSharingEnabled(contactProto.whitelisted)
+ .withIsArchived(contactProto.archived)
+ .withIsHidden(contactProto.hidden)
+ .withMuteUntil(contactProto.mutedUntilTimestamp)
+ .withHideStory(contactProto.hideStory)
+ .withGivenName(nullIfEmpty(contactProto.systemGivenName))
+ .withFamilyName(nullIfEmpty(contactProto.systemFamilyName))
+ .withNickName(nullIfEmpty(contactProto.systemNickname))
+ .withNickNameGivenName(nullIfEmpty(contactProto.givenName))
+ .withNickNameFamilyName(nullIfEmpty(contactProto.familyName))
+ .withNote(nullIfEmpty(contactProto.note))
+ .withUnregisteredTimestamp(contactProto.unregisteredAtTimestamp);
account.getRecipientStore().storeContact(connection, recipientId, newContact.build());
}
final var profile = recipient.getProfile();
final var profileGivenName = profile == null ? null : profile.getGivenName();
final var profileFamilyName = profile == null ? null : profile.getFamilyName();
- if (!Objects.equals(contactRecord.getProfileGivenName().orElse(null), profileGivenName) || !Objects.equals(
- contactRecord.getProfileFamilyName().orElse(null),
- profileFamilyName)) {
+ if (!Objects.equals(nullIfEmpty(contactProto.givenName), profileGivenName) || !Objects.equals(nullIfEmpty(
+ contactProto.familyName), profileFamilyName)) {
final var profileBuilder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
- final var newProfile = profileBuilder.withGivenName(contactRecord.getProfileGivenName().orElse(null))
- .withFamilyName(contactRecord.getProfileFamilyName().orElse(null))
+ final var newProfile = profileBuilder.withGivenName(nullIfEmpty(contactProto.givenName))
+ .withFamilyName(nullIfEmpty(contactProto.familyName))
.build();
account.getRecipientStore().storeProfile(connection, recipientId, newProfile);
}
- if (contactRecord.getProfileKey().isPresent()) {
+ if (contactProto.profileKey.size() > 0) {
try {
logger.trace("Storing profile key {}", recipientId);
- final var profileKey = new ProfileKey(contactRecord.getProfileKey().get());
+ final var profileKey = new ProfileKey(contactProto.profileKey.toByteArray());
account.getRecipientStore().storeProfileKey(connection, recipientId, profileKey);
} catch (InvalidInputException e) {
logger.warn("Received invalid contact profile key from storage");
}
}
- if (contactRecord.getIdentityKey().isPresent() && contactRecord.getAci().isPresent()) {
+ if (contactProto.identityKey.size() > 0 && address.aci().isPresent()) {
try {
logger.trace("Storing identity key {}", recipientId);
- final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
- account.getIdentityKeyStore()
- .saveIdentity(connection, contactRecord.getAci().orElse(null), identityKey);
+ final var identityKey = new IdentityKey(contactProto.identityKey.toByteArray());
+ account.getIdentityKeyStore().saveIdentity(connection, address.aci().get(), identityKey);
- final var trustLevel = StorageSyncModels.remoteToLocal(contactRecord.getIdentityState());
+ final var trustLevel = StorageSyncModels.remoteToLocal(contactProto.identityState);
if (trustLevel != null) {
account.getIdentityKeyStore()
- .setIdentityTrustLevel(connection,
- contactRecord.getAci().orElse(null),
- identityKey,
- trustLevel);
+ .setIdentityTrustLevel(connection, address.aci().get(), identityKey, trustLevel);
}
} catch (InvalidKeyException e) {
logger.warn("Received invalid contact identity key from storage");
}
}
account.getRecipientStore()
- .storeStorageRecord(connection, recipientId, contactRecord.getId(), contactRecord.toProto().encode());
+ .storeStorageRecord(connection, recipientId, contactRecord.getId(), contactProto.encode());
}
- private static RecipientAddress getRecipientAddress(final SignalContactRecord contactRecord) {
- return new RecipientAddress(contactRecord.getAci().orElse(null),
- contactRecord.getPni().orElse(null),
- contactRecord.getNumber().orElse(null),
- contactRecord.getUsername().orElse(null));
+ private static RecipientAddress getRecipientAddress(final ContactRecord contactRecord) {
+ return new RecipientAddress(ACI.parseOrNull(contactRecord.aci),
+ PNI.parseOrNull(contactRecord.pni),
+ nullIfEmpty(contactRecord.e164),
+ nullIfEmpty(contactRecord.username));
}
@Override
- public int compare(SignalContactRecord lhs, SignalContactRecord rhs) {
- if ((lhs.getAci().isPresent() && Objects.equals(lhs.getAci(), rhs.getAci())) || (
- lhs.getNumber().isPresent() && Objects.equals(lhs.getNumber(), rhs.getNumber())
- ) || (lhs.getPni().isPresent() && Objects.equals(lhs.getPni(), rhs.getPni()))) {
+ public int compare(SignalContactRecord lhsRecord, SignalContactRecord rhsRecord) {
+ final var lhs = lhsRecord.getProto();
+ final var rhs = rhsRecord.getProto();
+ if ((!lhs.aci.isEmpty() && Objects.equals(lhs.aci, rhs.aci)) || (
+ !lhs.e164.isEmpty() && Objects.equals(lhs.e164, rhs.e164)
+ ) || (!lhs.pni.isEmpty() && Objects.equals(lhs.pni, rhs.pni))) {
return 0;
} else {
return 1;
return E164_PATTERN.matcher(value).matches();
}
- private static boolean doProtosMatch(SignalContactRecord merged, SignalContactRecord other) {
- return Arrays.equals(merged.toProto().encode(), other.toProto().encode());
+ private static boolean doProtosMatch(ContactRecord merged, ContactRecord other) {
+ return Arrays.equals(merged.encode(), other.encode());
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
+import org.whispersystems.signalservice.api.storage.StorageId;
+import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record;
import java.sql.Connection;
import java.sql.SQLException;
@Override
protected boolean isInvalid(SignalGroupV1Record remote) throws SQLException {
try {
- final var id = GroupId.unknownVersion(remote.getGroupId());
+ final var id = GroupId.unknownVersion(remote.getProto().id.toByteArray());
if (!(id instanceof GroupIdV1)) {
return true;
}
@Override
protected Optional<SignalGroupV1Record> getMatching(SignalGroupV1Record remote) throws SQLException {
- final var id = GroupId.v1(remote.getGroupId());
+ final var id = GroupId.v1(remote.getProto().id.toByteArray());
final var group = account.getGroupStore().getGroup(connection, id);
if (group == null) {
}
final var storageId = account.getGroupStore().getGroupStorageId(connection, id);
- return Optional.of(StorageSyncModels.localToRemoteRecord(group, storageId.getRaw()).getGroupV1().get());
+ return Optional.of(new SignalGroupV1Record(storageId, StorageSyncModels.localToRemoteRecord(group)));
}
@Override
- protected SignalGroupV1Record merge(SignalGroupV1Record remote, SignalGroupV1Record local) {
- final var unknownFields = remote.serializeUnknownFields();
- final var blocked = remote.isBlocked();
- final var profileSharing = remote.isProfileSharingEnabled();
- final var archived = remote.isArchived();
- final var forcedUnread = remote.isForcedUnread();
- final var muteUntil = remote.getMuteUntil();
-
- final var mergedBuilder = new SignalGroupV1Record.Builder(remote.getId().getRaw(),
- remote.getGroupId(),
- unknownFields).setBlocked(blocked)
- .setProfileSharingEnabled(profileSharing)
- .setForcedUnread(forcedUnread)
- .setMuteUntil(muteUntil)
- .setArchived(archived);
+ protected SignalGroupV1Record merge(SignalGroupV1Record remoteRecord, SignalGroupV1Record localRecord) {
+ final var remote = remoteRecord.getProto();
+ final var local = localRecord.getProto();
+
+ final var mergedBuilder = SignalGroupV1Record.Companion.newBuilder(remote.unknownFields().toByteArray())
+ .id(remote.id)
+ .blocked(remote.blocked)
+ .whitelisted(remote.whitelisted)
+ .markedUnread(remote.markedUnread)
+ .mutedUntilTimestamp(remote.mutedUntilTimestamp)
+ .archived(remote.archived);
final var merged = mergedBuilder.build();
final var matchesRemote = doProtosMatch(merged, remote);
if (matchesRemote) {
- return remote;
+ return remoteRecord;
}
final var matchesLocal = doProtosMatch(merged, local);
if (matchesLocal) {
- return local;
+ return localRecord;
}
- return mergedBuilder.setId(KeyUtils.createRawStorageId()).build();
+ return new SignalGroupV1Record(StorageId.forGroupV1(KeyUtils.createRawStorageId()), mergedBuilder.build());
}
@Override
@Override
protected void updateLocal(StorageRecordUpdate<SignalGroupV1Record> update) throws SQLException {
final var groupV1Record = update.newRecord();
- final var groupIdV1 = GroupId.v1(groupV1Record.getGroupId());
+ final var groupV1Proto = groupV1Record.getProto();
+ final var groupIdV1 = GroupId.v1(groupV1Proto.id.toByteArray());
final var group = account.getGroupStore().getOrCreateGroupV1(connection, groupIdV1);
if (group != null) {
- group.setBlocked(groupV1Record.isBlocked());
+ group.setBlocked(groupV1Proto.blocked);
account.getGroupStore().updateGroup(connection, group);
account.getGroupStore()
- .storeStorageRecord(connection,
- group.getGroupId(),
- groupV1Record.getId(),
- groupV1Record.toProto().encode());
+ .storeStorageRecord(connection, group.getGroupId(), groupV1Record.getId(), groupV1Proto.encode());
}
}
@Override
public int compare(SignalGroupV1Record lhs, SignalGroupV1Record rhs) {
- if (Arrays.equals(lhs.getGroupId(), rhs.getGroupId())) {
+ if (lhs.getProto().id.equals(rhs.getProto().id)) {
return 0;
} else {
return 1;
}
}
- private static boolean doProtosMatch(SignalGroupV1Record merged, SignalGroupV1Record other) {
- return Arrays.equals(merged.toProto().encode(), other.toProto().encode());
+ private static boolean doProtosMatch(GroupV1Record merged, GroupV1Record other) {
+ return Arrays.equals(merged.encode(), other.encode());
}
}
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.KeyUtils;
+import org.jetbrains.annotations.NotNull;
+import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
+import org.whispersystems.signalservice.api.storage.StorageId;
+import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Optional;
+import okio.ByteString;
+
public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<SignalGroupV2Record> {
private static final Logger logger = LoggerFactory.getLogger(GroupV2RecordProcessor.class);
@Override
protected boolean isInvalid(SignalGroupV2Record remote) {
- return remote.getMasterKeyBytes().length != GroupMasterKey.SIZE;
+ return remote.getProto().masterKey.size() != GroupMasterKey.SIZE;
}
@Override
protected Optional<SignalGroupV2Record> getMatching(SignalGroupV2Record remote) throws SQLException {
- final var id = GroupUtils.getGroupIdV2(remote.getMasterKeyOrThrow());
+ final var id = GroupUtils.getGroupIdV2(getGroupMasterKeyOrThrow(remote.getProto().masterKey));
final var group = account.getGroupStore().getGroup(connection, id);
if (group == null) {
}
final var storageId = account.getGroupStore().getGroupStorageId(connection, id);
- return Optional.of(StorageSyncModels.localToRemoteRecord(group, storageId.getRaw()).getGroupV2().get());
+ return Optional.of(new SignalGroupV2Record(storageId, StorageSyncModels.localToRemoteRecord(group)));
}
@Override
- protected SignalGroupV2Record merge(SignalGroupV2Record remote, SignalGroupV2Record local) {
- final var unknownFields = remote.serializeUnknownFields();
- final var blocked = remote.isBlocked();
- final var profileSharing = remote.isProfileSharingEnabled();
- final var archived = remote.isArchived();
- final var forcedUnread = remote.isForcedUnread();
- final var muteUntil = remote.getMuteUntil();
- final var notifyForMentionsWhenMuted = remote.notifyForMentionsWhenMuted();
- final var hideStory = remote.shouldHideStory();
- final var storySendMode = remote.getStorySendMode();
-
- final var mergedBuilder = new SignalGroupV2Record.Builder(remote.getId().getRaw(),
- remote.getMasterKeyBytes(),
- unknownFields).setBlocked(blocked)
- .setProfileSharingEnabled(profileSharing)
- .setArchived(archived)
- .setForcedUnread(forcedUnread)
- .setMuteUntil(muteUntil)
- .setNotifyForMentionsWhenMuted(notifyForMentionsWhenMuted)
- .setHideStory(hideStory)
- .setStorySendMode(storySendMode);
+ protected SignalGroupV2Record merge(SignalGroupV2Record remoteRecord, SignalGroupV2Record localRecord) {
+ final var remote = remoteRecord.getProto();
+ final var local = localRecord.getProto();
+
+ final var mergedBuilder = SignalGroupV2Record.Companion.newBuilder(remote.unknownFields().toByteArray())
+ .masterKey(remote.masterKey)
+ .blocked(remote.blocked)
+ .whitelisted(remote.whitelisted)
+ .archived(remote.archived)
+ .markedUnread(remote.markedUnread)
+ .mutedUntilTimestamp(remote.mutedUntilTimestamp)
+ .dontNotifyForMentionsIfMuted(remote.dontNotifyForMentionsIfMuted)
+ .hideStory(remote.hideStory)
+ .storySendMode(remote.storySendMode);
final var merged = mergedBuilder.build();
final var matchesRemote = doProtosMatch(merged, remote);
if (matchesRemote) {
- return remote;
+ return remoteRecord;
}
final var matchesLocal = doProtosMatch(merged, local);
if (matchesLocal) {
- return local;
+ return localRecord;
}
- return mergedBuilder.setId(KeyUtils.createRawStorageId()).build();
+ return new SignalGroupV2Record(StorageId.forGroupV2(KeyUtils.createRawStorageId()), mergedBuilder.build());
}
@Override
@Override
protected void updateLocal(StorageRecordUpdate<SignalGroupV2Record> update) throws SQLException {
final var groupV2Record = update.newRecord();
- final var groupMasterKey = groupV2Record.getMasterKeyOrThrow();
+ final var groupV2Proto = groupV2Record.getProto();
+ final var groupMasterKey = getGroupMasterKeyOrThrow(groupV2Proto.masterKey);
final var group = account.getGroupStore().getGroupOrPartialMigrate(connection, groupMasterKey);
- group.setBlocked(groupV2Record.isBlocked());
- group.setProfileSharingEnabled(groupV2Record.isProfileSharingEnabled());
+ group.setBlocked(groupV2Proto.blocked);
+ group.setProfileSharingEnabled(groupV2Proto.whitelisted);
account.getGroupStore().updateGroup(connection, group);
account.getGroupStore()
- .storeStorageRecord(connection,
- group.getGroupId(),
- groupV2Record.getId(),
- groupV2Record.toProto().encode());
+ .storeStorageRecord(connection, group.getGroupId(), groupV2Record.getId(), groupV2Proto.encode());
+ }
+
+ @NotNull
+ private static GroupMasterKey getGroupMasterKeyOrThrow(final ByteString masterKey) {
+ try {
+ return new GroupMasterKey(masterKey.toByteArray());
+ } catch (InvalidInputException e) {
+ throw new AssertionError(e);
+ }
}
@Override
public int compare(SignalGroupV2Record lhs, SignalGroupV2Record rhs) {
- if (Arrays.equals(lhs.getMasterKeyBytes(), rhs.getMasterKeyBytes())) {
+ if (lhs.getProto().masterKey.equals(rhs.getProto().masterKey)) {
return 0;
} else {
return 1;
}
}
- private static boolean doProtosMatch(SignalGroupV2Record merged, SignalGroupV2Record other) {
- return Arrays.equals(merged.toProto().encode(), other.toProto().encode());
+ private static boolean doProtosMatch(GroupV2Record merged, GroupV2Record other) {
+ return Arrays.equals(merged.encode(), other.encode());
}
}
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.recipients.Recipient;
+import org.whispersystems.signalservice.api.push.ServiceId.ACI;
+import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
-import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord.UsernameLink;
+import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState;
+import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record;
+import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
import java.util.Optional;
import okio.ByteString;
+import static org.signal.core.util.StringExtensionsKt.emptyIfNull;
+
public final class StorageSyncModels {
private StorageSyncModels() {
};
}
- public static SignalStorageRecord localToRemoteRecord(
+ public static AccountRecord localToRemoteRecord(
ConfigurationStore configStore,
Recipient self,
- UsernameLinkComponents usernameLinkComponents,
- byte[] rawStorageId
+ UsernameLinkComponents usernameLinkComponents
) {
- final var builder = new SignalAccountRecord.Builder(rawStorageId, self.getStorageRecord());
+ final var builder = SignalAccountRecord.Companion.newBuilder(self.getStorageRecord());
if (self.getProfileKey() != null) {
- builder.setProfileKey(self.getProfileKey().serialize());
+ builder.profileKey(ByteString.of(self.getProfileKey().serialize()));
}
if (self.getProfile() != null) {
- builder.setGivenName(self.getProfile().getGivenName())
- .setFamilyName(self.getProfile().getFamilyName())
- .setAvatarUrlPath(self.getProfile().getAvatarUrlPath());
+ builder.givenName(emptyIfNull(self.getProfile().getGivenName()))
+ .familyName(emptyIfNull(self.getProfile().getFamilyName()))
+ .avatarUrlPath(emptyIfNull(self.getProfile().getAvatarUrlPath()));
}
- builder.setTypingIndicatorsEnabled(Optional.ofNullable(configStore.getTypingIndicators()).orElse(true))
- .setReadReceiptsEnabled(Optional.ofNullable(configStore.getReadReceipts()).orElse(true))
- .setSealedSenderIndicatorsEnabled(Optional.ofNullable(configStore.getUnidentifiedDeliveryIndicators())
+ builder.typingIndicators(Optional.ofNullable(configStore.getTypingIndicators()).orElse(true))
+ .readReceipts(Optional.ofNullable(configStore.getReadReceipts()).orElse(true))
+ .sealedSenderIndicators(Optional.ofNullable(configStore.getUnidentifiedDeliveryIndicators())
.orElse(true))
- .setLinkPreviewsEnabled(Optional.ofNullable(configStore.getLinkPreviews()).orElse(true))
- .setUnlistedPhoneNumber(Optional.ofNullable(configStore.getPhoneNumberUnlisted()).orElse(false))
- .setPhoneNumberSharingMode(Optional.ofNullable(configStore.getPhoneNumberSharingMode())
+ .linkPreviews(Optional.ofNullable(configStore.getLinkPreviews()).orElse(true))
+ .unlistedPhoneNumber(Optional.ofNullable(configStore.getPhoneNumberUnlisted()).orElse(false))
+ .phoneNumberSharingMode(Optional.ofNullable(configStore.getPhoneNumberSharingMode())
.map(StorageSyncModels::localToRemote)
.orElse(AccountRecord.PhoneNumberSharingMode.UNKNOWN))
- .setE164(self.getAddress().number().orElse(""))
- .setUsername(self.getAddress().username().orElse(null));
+ .e164(self.getAddress().number().orElse(""))
+ .username(self.getAddress().username().orElse(""));
if (usernameLinkComponents != null) {
final var linkColor = configStore.getUsernameLinkColor();
- builder.setUsernameLink(new UsernameLink.Builder().entropy(ByteString.of(usernameLinkComponents.getEntropy()))
+ builder.usernameLink(new UsernameLink.Builder().entropy(ByteString.of(usernameLinkComponents.getEntropy()))
.serverId(UuidUtil.toByteString(usernameLinkComponents.getServerId()))
.color(linkColor == null ? UsernameLink.Color.UNKNOWN : UsernameLink.Color.valueOf(linkColor))
.build());
}
- return SignalStorageRecord.forAccount(builder.build());
+ return builder.build();
}
- public static SignalStorageRecord localToRemoteRecord(
- Recipient recipient,
- IdentityInfo identity,
- byte[] rawStorageId
- ) {
+ public static ContactRecord localToRemoteRecord(Recipient recipient, IdentityInfo identity) {
final var address = recipient.getAddress();
- final var builder = new SignalContactRecord.Builder(rawStorageId,
- address.aci().orElse(null),
- recipient.getStorageRecord()).setE164(address.number().orElse(null))
- .setPni(address.pni().orElse(null))
- .setUsername(address.username().orElse(null))
- .setProfileKey(recipient.getProfileKey() == null ? null : recipient.getProfileKey().serialize());
+ final var builder = SignalContactRecord.Companion.newBuilder(recipient.getStorageRecord())
+ .aci(address.aci().map(ACI::toString).orElse(""))
+ .e164(address.number().orElse(""))
+ .pni(address.pni().map(PNI::toString).orElse(""))
+ .username(address.username().orElse(""))
+ .profileKey(recipient.getProfileKey() == null
+ ? ByteString.EMPTY
+ : ByteString.of(recipient.getProfileKey().serialize()));
if (recipient.getProfile() != null) {
- builder.setProfileGivenName(recipient.getProfile().getGivenName())
- .setProfileFamilyName(recipient.getProfile().getFamilyName());
+ builder.givenName(emptyIfNull(recipient.getProfile().getGivenName()))
+ .familyName(emptyIfNull(recipient.getProfile().getFamilyName()));
}
if (recipient.getContact() != null) {
- builder.setSystemGivenName(recipient.getContact().givenName())
- .setSystemFamilyName(recipient.getContact().familyName())
- .setSystemNickname(recipient.getContact().nickName())
- .setNicknameGivenName(recipient.getContact().nickNameGivenName() == null
- ? ""
- : recipient.getContact().nickNameGivenName())
- .setNicknameFamilyName(recipient.getContact().nickNameFamilyName() == null
- ? ""
- : recipient.getContact().nickNameFamilyName())
- .setNote(recipient.getContact().note())
- .setBlocked(recipient.getContact().isBlocked())
- .setProfileSharingEnabled(recipient.getContact().isProfileSharingEnabled())
- .setMuteUntil(recipient.getContact().muteUntil())
- .setHideStory(recipient.getContact().hideStory())
- .setUnregisteredTimestamp(recipient.getContact().unregisteredTimestamp() == null
+ builder.systemGivenName(emptyIfNull(recipient.getContact().givenName()))
+ .systemFamilyName(emptyIfNull(recipient.getContact().familyName()))
+ .systemNickname(emptyIfNull(recipient.getContact().nickName()))
+ .nickname(new ContactRecord.Name.Builder().given(emptyIfNull(recipient.getContact()
+ .nickNameGivenName()))
+ .family(emptyIfNull(recipient.getContact().nickNameFamilyName()))
+ .build())
+ .note(emptyIfNull(recipient.getContact().note()))
+ .blocked(recipient.getContact().isBlocked())
+ .whitelisted(recipient.getContact().isProfileSharingEnabled())
+ .mutedUntilTimestamp(recipient.getContact().muteUntil())
+ .hideStory(recipient.getContact().hideStory())
+ .unregisteredAtTimestamp(recipient.getContact().unregisteredTimestamp() == null
? 0
: recipient.getContact().unregisteredTimestamp())
- .setArchived(recipient.getContact().isArchived())
- .setHidden(recipient.getContact().isHidden());
+ .archived(recipient.getContact().isArchived())
+ .hidden(recipient.getContact().isHidden());
}
if (identity != null) {
- builder.setIdentityKey(identity.getIdentityKey().serialize())
- .setIdentityState(localToRemote(identity.getTrustLevel()));
+ builder.identityKey(ByteString.of(identity.getIdentityKey().serialize()))
+ .identityState(localToRemote(identity.getTrustLevel()));
}
- return SignalStorageRecord.forContact(builder.build());
+ return builder.build();
}
- public static SignalStorageRecord localToRemoteRecord(GroupInfoV1 group, byte[] rawStorageId) {
- final var builder = new SignalGroupV1Record.Builder(rawStorageId,
- group.getGroupId().serialize(),
- group.getStorageRecord());
- builder.setBlocked(group.isBlocked());
- builder.setArchived(group.archived);
- builder.setProfileSharingEnabled(true);
- return SignalStorageRecord.forGroupV1(builder.build());
+ public static GroupV1Record localToRemoteRecord(GroupInfoV1 group) {
+ final var builder = SignalGroupV1Record.Companion.newBuilder(group.getStorageRecord());
+ builder.id(ByteString.of(group.getGroupId().serialize()));
+ builder.blocked(group.isBlocked());
+ builder.archived(group.archived);
+ builder.whitelisted(true);
+ return builder.build();
}
- public static SignalStorageRecord localToRemoteRecord(GroupInfoV2 group, byte[] rawStorageId) {
- final var builder = new SignalGroupV2Record.Builder(rawStorageId,
- group.getMasterKey(),
- group.getStorageRecord());
- builder.setBlocked(group.isBlocked());
- builder.setProfileSharingEnabled(group.isProfileSharingEnabled());
- return SignalStorageRecord.forGroupV2(builder.build());
+ public static GroupV2Record localToRemoteRecord(GroupInfoV2 group) {
+ final var builder = SignalGroupV2Record.Companion.newBuilder(group.getStorageRecord());
+ builder.masterKey(ByteString.of(group.getMasterKey().serialize()));
+ builder.blocked(group.isBlocked());
+ builder.whitelisted(group.isProfileSharingEnabled());
+ return builder.build();
}
public static TrustLevel remoteToLocal(IdentityState identityState) {
import org.signal.core.util.SetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.push.ServiceId.ACI;
+import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
validateManifestAndInserts(result.manifest(), result.inserts(), self);
if (!result.deletes().isEmpty()) {
- Set<String> allSetEncoded = result.manifest()
- .getStorageIds()
- .stream()
+ Set<String> allSetEncoded = result.manifest().storageIds.stream()
.map(StorageId::getRaw)
.map(Base64::encodeWithPadding)
.collect(Collectors.toSet());
}
}
- if (previousManifest.getVersion() == 0) {
+ if (previousManifest.version == 0) {
logger.debug(
"Previous manifest is empty, not bothering with additional validations around the diffs between the two manifests.");
return;
}
- if (result.manifest().getVersion() != previousManifest.getVersion() + 1) {
+ if (result.manifest().version != previousManifest.version + 1) {
throw new IncorrectManifestVersionError();
}
return;
}
- Set<ByteBuffer> previousIds = previousManifest.getStorageIds()
- .stream()
+ Set<ByteBuffer> previousIds = previousManifest.storageIds.stream()
.map(id -> ByteBuffer.wrap(id.getRaw()))
.collect(Collectors.toSet());
- Set<ByteBuffer> newIds = result.manifest()
- .getStorageIds()
- .stream()
+ Set<ByteBuffer> newIds = result.manifest().storageIds.stream()
.map(id -> ByteBuffer.wrap(id.getRaw()))
.collect(Collectors.toSet());
Set<ByteBuffer> declaredDeletes = result.deletes().stream().map(ByteBuffer::wrap).collect(Collectors.toSet());
if (declaredInserts.size() > manifestInserts.size()) {
- logger.debug("DeclaredInserts: " + declaredInserts.size() + ", ManifestInserts: " + manifestInserts.size());
+ logger.debug("DeclaredInserts: {}, ManifestInserts: {}", declaredInserts.size(), manifestInserts.size());
throw new MoreInsertsThanExpectedError();
}
if (declaredInserts.size() < manifestInserts.size()) {
- logger.debug("DeclaredInserts: " + declaredInserts.size() + ", ManifestInserts: " + manifestInserts.size());
+ logger.debug("DeclaredInserts: {}, ManifestInserts: {}", declaredInserts.size(), manifestInserts.size());
throw new LessInsertsThanExpectedError();
}
}
if (declaredDeletes.size() > manifestDeletes.size()) {
- logger.debug("DeclaredDeletes: " + declaredDeletes.size() + ", ManifestDeletes: " + manifestDeletes.size());
+ logger.debug("DeclaredDeletes: {}, ManifestDeletes: {}", declaredDeletes.size(), manifestDeletes.size());
throw new MoreDeletesThanExpectedError();
}
if (declaredDeletes.size() < manifestDeletes.size()) {
- logger.debug("DeclaredDeletes: " + declaredDeletes.size() + ", ManifestDeletes: " + manifestDeletes.size());
+ logger.debug("DeclaredDeletes: {}, ManifestDeletes: {}", declaredDeletes.size(), manifestDeletes.size());
throw new LessDeletesThanExpectedError();
}
RecipientAddress self
) {
int accountCount = 0;
- for (StorageId id : manifest.getStorageIds()) {
+ for (StorageId id : manifest.storageIds) {
accountCount += id.getType() == ManifestRecord.Identifier.Type.ACCOUNT.getValue() ? 1 : 0;
}
throw new MissingAccountError();
}
- Set<StorageId> allSet = new HashSet<>(manifest.getStorageIds());
+ Set<StorageId> allSet = new HashSet<>(manifest.storageIds);
Set<StorageId> insertSet = inserts.stream().map(SignalStorageRecord::getId).collect(Collectors.toSet());
Set<ByteBuffer> rawIdSet = allSet.stream().map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet());
- if (allSet.size() != manifest.getStorageIds().size()) {
+ if (allSet.size() != manifest.storageIds.size()) {
throw new DuplicateStorageIdError();
}
throw new DuplicateDistributionListIdError();
}
+ ids = manifest.getStorageIdsByType().get(ManifestRecord.Identifier.Type.CALL_LINK.getValue());
+ if (ids.size() != new HashSet<>(ids).size()) {
+ throw new DuplicateCallLinkError();
+ }
+
throw new DuplicateRawIdAcrossTypesError();
}
throw new UnknownInsertError();
}
- if (insert.getContact().isPresent()) {
- final var contact = insert.getContact().get();
- final var aci = contact.getAci();
- final var pni = contact.getPni();
- final var number = contact.getNumber();
- final var username = contact.getUsername();
+ if (insert.getProto().contact != null) {
+ final var contact = insert.getProto().contact;
+ final var aci = ACI.parseOrNull(contact.aci);
+ final var pni = PNI.parseOrNull(contact.pni);
+ final var number = contact.e164.isEmpty() ? null : contact.e164;
+ final var username = contact.username.isEmpty() ? null : contact.username;
final var address = new RecipientAddress(aci, pni, number, username);
if (self.matches(address)) {
throw new SelfAddedAsContactError();
}
}
- if (insert.getAccount().isPresent() && insert.getAccount().get().getProfileKey().isEmpty()) {
+ if (insert.getProto().account != null && insert.getProto().account.profileKey.size() == 0) {
logger.debug("Uploading a null profile key in our AccountRecord!");
}
}
private static final class DuplicateDistributionListIdError extends Error {}
+ private static final class DuplicateCallLinkError extends Error {}
+
private static final class DuplicateInsertInWriteError extends Error {}
private static final class InsertNotPresentInFullIdSetError extends Error {}
} else {
return String.format(Locale.ROOT,
"ManifestVersion: %d, Total Keys: %d, Inserts: %d, Deletes: %d",
- manifest.getVersion(),
- manifest.getStorageIds().size(),
+ manifest.version,
+ manifest.storageIds.size(),
inserts.size(),
deletes.size());
}
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
+import org.whispersystems.signalservice.api.backup.MediaRootBackupKey;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import java.security.SecureRandom;
return MasterKey.createNew(secureRandom);
}
+ public static MediaRootBackupKey createMediaRootBackupKey() {
+ return new MediaRootBackupKey(getSecretBytes(32));
+ }
+
public static byte[] createRawStorageId() {
return getSecretBytes(16);
}
TokenNotAcceptedException _e) {
throw new CaptchaRequiredException("Captcha not accepted");
} catch (NonSuccessfulResponseCodeException e) {
- if (e.getCode() == 400) {
+ if (e.code == 400) {
throw new CaptchaRequiredException("Captcha has invalid format");
}
throw e;
if (encryptedProfile.getCapabilities().isStorage()) {
capabilities.add(Profile.Capability.storage);
}
+ if (encryptedProfile.getCapabilities().isStorageServiceEncryptionV2()) {
+ capabilities.add(Profile.Capability.storageServiceEncryptionV2Capability);
+ }
return capabilities;
}
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+import okio.ByteString;
+
public class Utils {
private static final Logger logger = LoggerFactory.getLogger(Utils.class);
}
return response.successOrThrow();
}
+
+ public static ByteString firstNonEmpty(ByteString... strings) {
+ for (final var s : strings) {
+ if (s.size() > 0) {
+ return s;
+ }
+ }
+ return ByteString.EMPTY;
+ }
+
+ @SafeVarargs
+ public static <T> List<T> firstNonEmpty(List<T>... values) {
+ for (final var s : values) {
+ if (!s.isEmpty()) {
+ return s;
+ }
+ }
+ return List.of();
+ }
+
+ public static String firstNonEmpty(String... strings) {
+ for (final var s : strings) {
+ if (!s.isEmpty()) {
+ return s;
+ }
+ }
+ return "";
+ }
+
+ @SafeVarargs
+ public static <T> T firstNonNull(T... values) {
+ for (final var v : values) {
+ if (v != null) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ public static String nullIfEmpty(String string) {
+ return string == null || string.isEmpty() ? null : string;
+ }
}