diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1e23dcf0..6a96c4ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines = "1.10.2" junit = "6.1.2" micronaut-json-schema = "2.1.0" micronaut-core = "5.1.10" -signal-service = "2.15.3_unofficial_151" +signal-service = "2.15.3_unofficial_152" [libraries] bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.85" diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java index 6df4e11f..c3cd98bc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java @@ -15,6 +15,7 @@ public record Contact( long muteUntil, boolean hideStory, boolean isBlocked, + long blockedAt, boolean isArchived, boolean isProfileSharingEnabled, boolean isHidden, @@ -34,6 +35,7 @@ public record Contact( builder.muteUntil, builder.hideStory, builder.isBlocked, + builder.blockedAt, builder.isArchived, builder.isProfileSharingEnabled, builder.isHidden, @@ -58,6 +60,7 @@ public record Contact( builder.muteUntil = copy.muteUntil(); builder.hideStory = copy.hideStory(); builder.isBlocked = copy.isBlocked(); + builder.blockedAt = copy.blockedAt(); builder.isArchived = copy.isArchived(); builder.isProfileSharingEnabled = copy.isProfileSharingEnabled(); builder.isHidden = copy.isHidden(); @@ -109,6 +112,7 @@ public record Contact( private long muteUntil; private boolean hideStory; private boolean isBlocked; + private long blockedAt; private boolean isArchived; private boolean isProfileSharingEnabled; private boolean isHidden; @@ -177,10 +181,20 @@ public record Contact( } public Builder withIsBlocked(final boolean val) { + if (val && !isBlocked) { + blockedAt = System.currentTimeMillis(); + } else if (!val) { + blockedAt = 0; + } isBlocked = val; return this; } + public Builder withBlockedAt(final long val) { + blockedAt = val; + return this; + } + public Builder withIsArchived(final boolean val) { isArchived = val; return this; diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java index 34b2274d..a868fb36 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -739,7 +739,10 @@ public record MessageEnvelope( null, d.getE164(), null)) - .toList(), blockedListMessage.groupIds.stream().map(GroupId::unknownVersion).toList()); + .toList(), + blockedListMessage.groups.stream() + .map(group -> GroupId.unknownVersion(group.getGroupId())) + .toList()); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index 9b71f6fa..ca496226 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -32,7 +32,13 @@ public class ServiceConfig { final var attachmentBackfill = !isPrimaryDevice; final var spqr = true; final var usernameSyncChangeMessage = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, true, attachmentBackfill, spqr, usernameSyncChangeMessage); + final var optionalPhoneNumber = !isPrimaryDevice; + return new AccountAttributes.Capabilities(true, + true, + attachmentBackfill, + spqr, + usernameSyncChangeMessage, + optionalPhoneNumber); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java index 94aac9aa..98a4d8f0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java @@ -50,6 +50,7 @@ import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevic import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -399,13 +400,17 @@ public class AccountHelper { } private void reserveUsername(final List candidates) throws IOException { - final var candidateHashes = new ArrayList(); + final var candidateHashes = new ArrayList(); for (final var candidate : candidates) { - candidateHashes.add(Base64.encodeUrlSafeWithoutPadding(candidate.getHash())); + candidateHashes.add(candidate.getHash()); } - final var response = handleResponseException(dependencies.getAccountApi().reserveUsername(candidateHashes)); - final var hashIndex = candidateHashes.indexOf(response.getUsernameHash()); + final var usernameHash = handleResponseException(dependencies.getAccountApi().reserveUsername(candidateHashes)); + final var hashIndex = candidateHashes.stream() + .filter(candidateHash -> Arrays.equals(candidateHash, usernameHash)) + .findFirst() + .map(candidateHashes::indexOf) + .orElse(-1); if (hashIndex == -1) { logger.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes."); throw new IOException("Unexpected username response"); @@ -498,8 +503,7 @@ public class AccountHelper { final var usernameLink = account.getUsernameLink(); if (usernameLink == null) { - handleResponseException(dependencies.getAccountApi() - .reserveUsername(List.of(Base64.encodeUrlSafeWithoutPadding(username.getHash())))); + handleResponseException(dependencies.getAccountApi().reserveUsername(List.of(username.getHash()))); logger.debug("[reserveUsername] Successfully reserved existing username."); final var linkComponents = confirmUsernameAndCreateNewLink(username); account.setUsernameLink(linkComponents); @@ -534,19 +538,19 @@ public class AccountHelper { public void setDeviceName(String deviceName) { final var encryptedDeviceName = getEncryptedDeviceName(deviceName); - account.setEncryptedDeviceName(encryptedDeviceName); + account.setEncryptedDeviceName(Base64.encodeWithoutPadding(encryptedDeviceName)); } public void setDeviceName(int deviceId, String deviceName) throws IOException { final var encryptedDeviceName = getEncryptedDeviceName(deviceName); - handleResponseException(dependencies.getLinkDeviceApi().setDeviceName(encryptedDeviceName, deviceId)); + handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi() + .setDeviceName(encryptedDeviceName, deviceId, cont)); context.getSyncHelper().sendDeviceNameChange(deviceId); } - private String getEncryptedDeviceName(final String deviceName) { + private byte[] getEncryptedDeviceName(final String deviceName) { final var identityKey = account.getAciIdentityKeyPair(); - return Base64.encodeWithoutPadding(DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), - identityKey)); + return DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), identityKey); } public void refreshDeviceName() throws IOException { @@ -585,7 +589,8 @@ public class AccountHelper { account.getOrCreatePinMasterKey(), account.getOrCreateMediaRootBackupKey(), verificationCode.getVerificationCode(), - null)); + null, + account.getAuthCredentialSalt())); account.setMultiDevice(true); context.getJobExecutor().enqueueJob(new SyncStorageJob()); } @@ -601,16 +606,14 @@ public class AccountHelper { var masterKey = account.getOrCreatePinMasterKey(); context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey); - handleResponseException(dependencies.getAccountApi() - .enableRegistrationLock(masterKey.deriveRegistrationLock())); + handleResponseException(dependencies.getAccountApi().enableRegistrationLock(masterKey)); } public void setRegistrationPin(String pin) throws IOException { var masterKey = account.getOrCreatePinMasterKey(); context.getPinHelper().setRegistrationLockPin(pin, masterKey); - handleResponseException(dependencies.getAccountApi() - .enableRegistrationLock(masterKey.deriveRegistrationLock())); + handleResponseException(dependencies.getAccountApi().enableRegistrationLock(masterKey)); account.setRegistrationLockPin(pin); updateAccountAttributes(); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java index e1944d6a..3f8f2600 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java @@ -85,12 +85,18 @@ public class ContactHelper { } public void setContactBlocked(RecipientId recipientId, boolean blocked) { + setContactBlocked(recipientId, blocked, blocked ? System.currentTimeMillis() : 0); + } + + public void setContactBlocked(RecipientId recipientId, boolean blocked, long blockedAt) { var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); if (blocked) { builder.withIsProfileSharingEnabled(false); } - account.getContactStore().storeContact(recipientId, builder.withIsBlocked(blocked).build()); + account.getContactStore() + .storeContact(recipientId, + builder.withIsBlocked(blocked).withBlockedAt(blocked ? blockedAt : 0).build()); } public void setContactProfileSharing(RecipientId recipientId, boolean profileSharing) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 7d689c34..0144dd04 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -459,12 +459,21 @@ public class GroupHelper { } public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException { + setGroupBlocked(groupId, blocked, blocked ? System.currentTimeMillis() : 0); + } + + public void setGroupBlocked( + final GroupId groupId, + final boolean blocked, + final long blockedAt + ) throws GroupNotFoundException { var group = getGroup(groupId); if (group == null) { throw new GroupNotFoundException(groupId); } group.setBlocked(blocked); + group.setBlockedAt(blocked ? blockedAt : 0); account.getGroupStore().updateGroup(group); context.getJobExecutor().enqueueJob(new SyncStorageJob()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index e33f0fbd..6aa9c5a0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -626,13 +626,12 @@ public final class IncomingMessageHandler { 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); + context.getContactHelper().setContactBlocked(recipientId, true, individual.getBlockedAt()); } - for (var groupId : blockedListMessage.groupIds.stream() - .map(GroupId::unknownVersion) - .collect(Collectors.toSet())) { + for (var group : blockedListMessage.groups) { + final var groupId = GroupId.unknownVersion(group.getGroupId()); try { - context.getGroupHelper().setGroupBlocked(groupId, true); + context.getGroupHelper().setGroupBlocked(groupId, true, group.getBlockedAt()); } catch (GroupNotFoundException e) { logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}", groupId.toBase64()); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java index 31f1c04d..8d44f4fb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java @@ -12,6 +12,7 @@ import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.signal.core.models.ServiceId.ACI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.messages.EnvelopeResponse; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.websocket.SignalWebSocket; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; @@ -147,15 +148,19 @@ public class ReceiveHelper { logger.debug("Retrieved {} envelopes!", batch.size()); isWaitingForMessage = false; for (final var it : batch) { - SignalServiceEnvelope envelope1 = new SignalServiceEnvelope(it.getEnvelope(), - it.getServerDeliveredTimestamp()); - final var sourceServiceId = envelope1.getSourceServiceId(); - final var recipientId = sourceServiceId == null - ? null - : account.getRecipientResolver().resolveRecipient(sourceServiceId); - logger.trace("Storing new message from {}", recipientId); - // store message on disk, before acknowledging receipt to the server - cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId); + if (it instanceof EnvelopeResponse.Unparseable) { + logger.warn("Received unparseable envelope from server, ignoring."); + } else if (it instanceof EnvelopeResponse.Parsed parsed) { + SignalServiceEnvelope envelope1 = new SignalServiceEnvelope(parsed.getEnvelope(), + parsed.getServerDeliveredTimestamp()); + final var sourceServiceId = envelope1.getSourceServiceId(); + final var recipientId = sourceServiceId == null + ? null + : account.getRecipientResolver().resolveRecipient(sourceServiceId); + logger.trace("Storing new message from {}", recipientId); + // store message on disk, before acknowledging receipt to the server + cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId); + } try { signalWebSocket.sendAck(it); } catch (IOException e) { @@ -167,6 +172,9 @@ public class ReceiveHelper { backOffCounter = 0; if (queueNotEmpty) { + if (cachedMessage[0] == null) { + continue; + } if (remainingMessages > 0) { remainingMessages -= 1; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index ba2ccad1..0acb30bf 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -237,18 +237,19 @@ public class SyncHelper { 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))); + address.number().orElse(null), + record.second().blockedAt())); } } } - var groupIds = new ArrayList(); + var groups = new ArrayList(); for (var record : account.getGroupStore().getGroups()) { if (record.isBlocked()) { - groupIds.add(record.getGroupId().serialize()); + groups.add(new BlockedListMessage.Group(record.getGroupId().serialize(), record.getBlockedAt())); } } return context.getSendHelper() - .sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds))); + .sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groups))); } public SendMessageResult sendVerifiedMessage( diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java index 9f2f1683..65da23e5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java @@ -214,6 +214,7 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable { pniIdentity, profileKey, accountEntropyPool, + msg.authCredentialSalt == null ? null : msg.authCredentialSalt.toByteArray(), mediaRootBackupKey); if (msg.readReceipts != null) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index 2d535f64..105422ab 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -33,7 +33,7 @@ import java.util.UUID; public class AccountDatabase extends Database { private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class); - private static final long DATABASE_VERSION = 30; + private static final long DATABASE_VERSION = 31; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -643,6 +643,16 @@ public class AccountDatabase extends Database { """); } } + if (oldVersion < 31) { + logger.debug("Updating database: Add blocked-at timestamps"); + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + ALTER TABLE recipient ADD blocked_at INTEGER NOT NULL DEFAULT 0; + ALTER TABLE group_v1 ADD blocked_at INTEGER NOT NULL DEFAULT 0; + ALTER TABLE group_v2 ADD blocked_at INTEGER NOT NULL DEFAULT 0; + """); + } + } } private static void createUuidMappingTable( diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index 6a6713c9..3e07c5e6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -116,7 +116,7 @@ public class SignalAccount implements Closeable { private static final Logger logger = LoggerFactory.getLogger(SignalAccount.class); private static final int MINIMUM_STORAGE_VERSION = 1; - private static final int CURRENT_STORAGE_VERSION = 10; + private static final int CURRENT_STORAGE_VERSION = 11; private final Object LOCK = new Object(); @@ -141,6 +141,7 @@ public class SignalAccount implements Closeable { private MasterKey pinMasterKey; private StorageKey storageKey; private AccountEntropyPool accountEntropyPool; + private byte[] authCredentialSalt; private MediaRootBackupKey mediaRootBackupKey; private ProfileKey profileKey; @@ -301,6 +302,7 @@ public class SignalAccount implements Closeable { final IdentityKeyPair pniIdentity, final ProfileKey profileKey, final AccountEntropyPool accountEntropyPool, + final byte[] authCredentialSalt, final MediaRootBackupKey mediaRootBackupKey ) { this.deviceId = 0; @@ -325,6 +327,7 @@ public class SignalAccount implements Closeable { this.pinMasterKey = null; this.accountEntropyPool = null; } + this.authCredentialSalt = authCredentialSalt; this.mediaRootBackupKey = mediaRootBackupKey; getKeyValueStore().storeEntry(storageManifestVersion, -1L); this.setStorageManifest(null); @@ -361,6 +364,7 @@ public class SignalAccount implements Closeable { ) { this.pinMasterKey = masterKey; this.accountEntropyPool = null; + this.authCredentialSalt = null; getKeyValueStore().storeEntry(storageManifestVersion, -1L); this.setStorageManifest(null); this.storageKey = null; @@ -526,6 +530,9 @@ public class SignalAccount implements Closeable { if (storage.accountEntropyPool != null) { accountEntropyPool = new AccountEntropyPool(storage.accountEntropyPool); } + if (storage.authCredentialSalt != null) { + authCredentialSalt = base64.decode(storage.authCredentialSalt); + } if (storage.mediaRootBackupKey != null) { mediaRootBackupKey = new MediaRootBackupKey(base64.decode(storage.mediaRootBackupKey)); } @@ -906,6 +913,7 @@ public class SignalAccount implements Closeable { 0, false, contact.blocked, + 0, contact.archived, false, false, @@ -1014,6 +1022,7 @@ public class SignalAccount implements Closeable { pinMasterKey == null ? null : base64.encodeToString(pinMasterKey.serialize()), storageKey == null ? null : base64.encodeToString(storageKey.serialize()), accountEntropyPool == null ? null : accountEntropyPool.getValue(), + authCredentialSalt == null ? null : base64.encodeToString(authCredentialSalt), mediaRootBackupKey == null ? null : base64.encodeToString(mediaRootBackupKey.getValue()), profileKey == null ? null : base64.encodeToString(profileKey.serialize()), usernameLink == null ? null : base64.encodeToString(usernameLink.getEntropy()), @@ -1228,6 +1237,11 @@ public class SignalAccount implements Closeable { return pniAccountData.getSignalServiceAccountDataStore(); } + @Override + public SignalServiceAccountDataStore pniOrNull() { + return getPni() != null ? pniAccountData.getSignalServiceAccountDataStore() : null; + } + @Override public boolean isMultiDevice() { return SignalAccount.this.isMultiDevice(); @@ -1648,6 +1662,10 @@ public class SignalAccount implements Closeable { save(); } + public byte[] getAuthCredentialSalt() { + return authCredentialSalt; + } + public String getRecoveryPassword() { final var masterKey = getPinBackedMasterKey(); if (masterKey == null) { @@ -1987,6 +2005,7 @@ public class SignalAccount implements Closeable { String pinMasterKey, String storageKey, String accountEntropyPool, + String authCredentialSalt, String mediaRootBackupKey, String profileKey, String usernameLinkEntropy, diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java index 102cff85..2decabe8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java @@ -58,6 +58,10 @@ public sealed abstract class GroupInfo permits GroupInfoV1, GroupInfoV2 { public abstract void setBlocked(boolean blocked); + public abstract long getBlockedAt(); + + public abstract void setBlockedAt(long blockedAt); + public abstract boolean isProfileSharingEnabled(); public abstract void setProfileSharingEnabled(boolean profileSharingEnabled); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java index 0fb21868..3111d02a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java @@ -24,6 +24,7 @@ public final class GroupInfoV1 extends GroupInfo { public String color; public int messageExpirationTime; public boolean blocked; + private long blockedAt; public boolean archived; private byte[] storageRecord; @@ -39,6 +40,7 @@ public final class GroupInfoV1 extends GroupInfo { final String color, final int messageExpirationTime, final boolean blocked, + final long blockedAt, final boolean archived, final byte[] storageRecord ) { @@ -49,6 +51,7 @@ public final class GroupInfoV1 extends GroupInfo { this.color = color; this.messageExpirationTime = messageExpirationTime; this.blocked = blocked; + this.blockedAt = blockedAt; this.archived = archived; this.storageRecord = storageRecord; } @@ -91,9 +94,24 @@ public final class GroupInfoV1 extends GroupInfo { @Override public void setBlocked(final boolean blocked) { + if (blocked && !this.blocked) { + blockedAt = System.currentTimeMillis(); + } else if (!blocked) { + blockedAt = 0; + } this.blocked = blocked; } + @Override + public long getBlockedAt() { + return blockedAt; + } + + @Override + public void setBlockedAt(final long blockedAt) { + this.blockedAt = blockedAt; + } + @Override public boolean isProfileSharingEnabled() { return true; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java index b884cd2a..fb5f3af0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java @@ -23,6 +23,7 @@ public final class GroupInfoV2 extends GroupInfo { private final GroupMasterKey masterKey; private final DistributionId distributionId; private boolean blocked; + private long blockedAt; private boolean profileSharingEnabled; private DecryptedGroup group; private byte[] storageRecord; @@ -47,6 +48,7 @@ public final class GroupInfoV2 extends GroupInfo { final DecryptedGroup group, final DistributionId distributionId, final boolean blocked, + final long blockedAt, final boolean profileSharingEnabled, final boolean permissionDenied, final byte[] storageRecord, @@ -57,6 +59,7 @@ public final class GroupInfoV2 extends GroupInfo { this.group = group; this.distributionId = distributionId; this.blocked = blocked; + this.blockedAt = blockedAt; this.profileSharingEnabled = profileSharingEnabled; this.permissionDenied = permissionDenied; this.storageRecord = storageRecord; @@ -186,9 +189,24 @@ public final class GroupInfoV2 extends GroupInfo { @Override public void setBlocked(final boolean blocked) { + if (blocked && !this.blocked) { + blockedAt = System.currentTimeMillis(); + } else if (!blocked) { + blockedAt = 0; + } this.blocked = blocked; } + @Override + public long getBlockedAt() { + return blockedAt; + } + + @Override + public void setBlockedAt(final long blockedAt) { + this.blockedAt = blockedAt; + } + @Override public boolean isProfileSharingEnabled() { return profileSharingEnabled; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java index 512a92cf..aadb199d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java @@ -63,6 +63,7 @@ public class GroupStore { distribution_id BLOB UNIQUE NOT NULL, endorsement_expiration_time INTEGER NOT NULL DEFAULT 0, blocked INTEGER NOT NULL DEFAULT FALSE, + blocked_at INTEGER NOT NULL DEFAULT 0, profile_sharing INTEGER NOT NULL DEFAULT FALSE, permission_denied INTEGER NOT NULL DEFAULT FALSE ) STRICT; @@ -83,6 +84,7 @@ public class GroupStore { color TEXT, expiration_time INTEGER NOT NULL DEFAULT 0, blocked INTEGER NOT NULL DEFAULT FALSE, + blocked_at INTEGER NOT NULL DEFAULT 0, archived INTEGER NOT NULL DEFAULT FALSE ) STRICT; CREATE TABLE group_v1_member ( @@ -401,6 +403,7 @@ public class GroupStore { deleteGroup(connection, groupInfoV1.getGroupId()); final var groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey, recipientResolver); groupInfoV2.setBlocked(groupInfoV1.isBlocked()); + groupInfoV2.setBlockedAt(groupInfoV1.getBlockedAt()); updateGroup(connection, groupInfoV2); logger.debug("Locally migrated group {} to group v2, id: {}", groupInfoV1.getGroupId().toBase64(), @@ -614,9 +617,9 @@ public class GroupStore { } } final var sql = """ - INSERT INTO %s (_id, group_id, group_id_v2, name, color, expiration_time, blocked, archived, storage_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT (_id) DO UPDATE SET group_id=excluded.group_id, group_id_v2=excluded.group_id_v2, name=excluded.name, color=excluded.color, expiration_time=excluded.expiration_time, blocked=excluded.blocked, archived=excluded.archived, storage_id=excluded.storage_id + INSERT INTO %s (_id, group_id, group_id_v2, name, color, expiration_time, blocked, blocked_at, archived, storage_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (_id) DO UPDATE SET group_id=excluded.group_id, group_id_v2=excluded.group_id_v2, name=excluded.name, color=excluded.color, expiration_time=excluded.expiration_time, blocked=excluded.blocked, blocked_at=excluded.blocked_at, archived=excluded.archived, storage_id=excluded.storage_id RETURNING _id """.formatted(TABLE_GROUP_V1); try (final var statement = connection.prepareStatement(sql)) { @@ -631,8 +634,9 @@ public class GroupStore { statement.setString(5, groupV1.color); statement.setLong(6, groupV1.getMessageExpirationTimer()); statement.setBoolean(7, groupV1.isBlocked()); - statement.setBoolean(8, groupV1.archived); - statement.setBytes(9, KeyUtils.createRawStorageId()); + statement.setLong(8, groupV1.getBlockedAt()); + statement.setBoolean(9, groupV1.archived); + statement.setBytes(10, KeyUtils.createRawStorageId()); final var generatedKey = Utils.executeQueryForOptional(statement, Utils::getIdMapper); if (internalId == null) { @@ -658,9 +662,9 @@ public class GroupStore { } else if (group instanceof GroupInfoV2 groupV2) { final var sql = ( """ - INSERT INTO %s (_id, group_id, master_key, group_data, distribution_id, blocked, permission_denied, storage_id, profile_sharing) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT (_id) DO UPDATE SET group_id=excluded.group_id, master_key=excluded.master_key, group_data=excluded.group_data, distribution_id=excluded.distribution_id, blocked=excluded.blocked, permission_denied=excluded.permission_denied, storage_id=excluded.storage_id, profile_sharing=excluded.profile_sharing + INSERT INTO %s (_id, group_id, master_key, group_data, distribution_id, blocked, blocked_at, permission_denied, storage_id, profile_sharing) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (_id) DO UPDATE SET group_id=excluded.group_id, master_key=excluded.master_key, group_data=excluded.group_data, distribution_id=excluded.distribution_id, blocked=excluded.blocked, blocked_at=excluded.blocked_at, permission_denied=excluded.permission_denied, storage_id=excluded.storage_id, profile_sharing=excluded.profile_sharing """ ).formatted(TABLE_GROUP_V2); try (final var statement = connection.prepareStatement(sql)) { @@ -678,9 +682,10 @@ public class GroupStore { } statement.setBytes(5, UuidUtil.toByteArray(groupV2.getDistributionId().asUuid())); statement.setBoolean(6, groupV2.isBlocked()); - statement.setBoolean(7, groupV2.isPermissionDenied()); - statement.setBytes(8, KeyUtils.createRawStorageId()); - statement.setBoolean(9, groupV2.isProfileSharingEnabled()); + statement.setLong(7, groupV2.getBlockedAt()); + statement.setBoolean(8, groupV2.isPermissionDenied()); + statement.setBytes(9, KeyUtils.createRawStorageId()); + statement.setBoolean(10, groupV2.isProfileSharingEnabled()); statement.executeUpdate(); } } else { @@ -691,7 +696,7 @@ public class GroupStore { private List getGroupsV2() { final var sql = ( """ - SELECT g.group_id, g.master_key, g.group_data, g.distribution_id, g.blocked, g.profile_sharing, g.permission_denied, g.storage_record + SELECT g.group_id, g.master_key, g.group_data, g.distribution_id, g.blocked, g.blocked_at, g.profile_sharing, g.permission_denied, g.storage_record FROM %s g """ ).formatted(TABLE_GROUP_V2); @@ -709,7 +714,7 @@ public class GroupStore { public GroupInfoV2 getGroup(Connection connection, GroupIdV2 groupIdV2) throws SQLException { final var sql = ( """ - SELECT g.group_id, g.master_key, g.group_data, g.distribution_id, g.blocked, g.profile_sharing, g.permission_denied, g.storage_record + SELECT g.group_id, g.master_key, g.group_data, g.distribution_id, g.blocked, g.blocked_at, g.profile_sharing, g.permission_denied, g.storage_record FROM %s g WHERE g.group_id = ? """ @@ -743,7 +748,7 @@ public class GroupStore { public GroupInfoV2 getGroupV2(Connection connection, StorageId storageId) throws SQLException { final var sql = ( """ - SELECT g.group_id, g.master_key, g.group_data, g.distribution_id, g.blocked, g.profile_sharing, g.permission_denied, g.storage_record + SELECT g.group_id, g.master_key, g.group_data, g.distribution_id, g.blocked, g.blocked_at, g.profile_sharing, g.permission_denied, g.storage_record FROM %s g WHERE g.storage_id = ? """ @@ -766,6 +771,7 @@ public class GroupStore { final var groupData = resultSet.getBytes("group_data"); final var distributionId = resultSet.getBytes("distribution_id"); final var blocked = resultSet.getBoolean("blocked"); + final var blockedAt = resultSet.getLong("blocked_at"); final var profileSharingEnabled = resultSet.getBoolean("profile_sharing"); final var permissionDenied = resultSet.getBoolean("permission_denied"); final var storageRecord = resultSet.getBytes("storage_record"); @@ -774,6 +780,7 @@ public class GroupStore { groupData == null ? null : DecryptedGroup.ADAPTER.decode(groupData), DistributionId.from(UuidUtil.parseOrThrow(distributionId)), blocked, + blockedAt, profileSharingEnabled, permissionDenied, storageRecord, @@ -800,7 +807,7 @@ public class GroupStore { private List getGroupsV1() { final var sql = ( """ - SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.archived, g.storage_record + SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.blocked_at, g.archived, g.storage_record FROM %s g """ ).formatted(TABLE_GROUP_V1_MEMBER, TABLE_GROUP_V1); @@ -818,7 +825,7 @@ public class GroupStore { public GroupInfoV1 getGroup(Connection connection, GroupIdV1 groupIdV1) throws SQLException { final var sql = ( """ - SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.archived, g.storage_record + SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.blocked_at, g.archived, g.storage_record FROM %s g WHERE g.group_id = ? """ @@ -852,7 +859,7 @@ public class GroupStore { public GroupInfoV1 getGroupV1(Connection connection, StorageId storageId) throws SQLException { final var sql = ( """ - SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.archived, g.storage_record + SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.blocked_at, g.archived, g.storage_record FROM %s g WHERE g.storage_id = ? """ @@ -882,6 +889,7 @@ public class GroupStore { .collect(Collectors.toSet()); final var expirationTime = resultSet.getInt("expiration_time"); final var blocked = resultSet.getBoolean("blocked"); + final var blockedAt = resultSet.getLong("blocked_at"); final var archived = resultSet.getBoolean("archived"); final var storageRecord = resultSet.getBytes("storage_record"); return new GroupInfoV1(GroupId.v1(groupId), @@ -891,6 +899,7 @@ public class GroupStore { color, expirationTime, blocked, + blockedAt, archived, storageRecord); } @@ -902,7 +911,7 @@ public class GroupStore { private GroupInfoV1 getGroupV1ByV2Id(Connection connection, GroupIdV2 groupIdV2) throws SQLException { final var sql = ( """ - SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.archived, g.storage_record + SELECT g.group_id, g.group_id_v2, g.name, g.color, (select group_concat(gm.recipient_id) from %s gm where gm.group_id = g._id) as members, g.expiration_time, g.blocked, g.blocked_at, g.archived, g.storage_record FROM %s g WHERE g.group_id_v2 = ? """ diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java index bab6d0af..504fde1f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java @@ -59,6 +59,7 @@ public class LegacyGroupStore { g1.color, g1.messageExpirationTime, g1.blocked, + 0, g1.archived, null); } @@ -77,6 +78,7 @@ public class LegacyGroupStore { loadDecryptedGroupLocked(groupId, groupCachePath), g2.distributionId == null ? DistributionId.create() : DistributionId.from(g2.distributionId), g2.blocked, + 0, true, g2.permissionDenied, null, diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java index 6bb83ab5..eea5b973 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java @@ -50,6 +50,7 @@ public class LegacyRecipientStore2 { 0, false, r.contact.blocked, + 0, r.contact.archived, r.contact.profileSharingEnabled, false, diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 1c246d3f..486ec874 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -96,6 +96,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re expiration_time_version INTEGER DEFAULT 1 NOT NULL, mute_until INTEGER NOT NULL DEFAULT 0, blocked INTEGER NOT NULL DEFAULT FALSE, + blocked_at INTEGER NOT NULL DEFAULT 0, archived INTEGER NOT NULL DEFAULT FALSE, profile_sharing INTEGER NOT NULL DEFAULT FALSE, hide_story INTEGER NOT NULL DEFAULT FALSE, @@ -351,7 +352,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public List> getContacts() { final var sql = ( """ - SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp + SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.blocked_at, r.archived, r.hidden, r.unregistered_timestamp FROM %s r WHERE (r.number IS NOT NULL OR r.pni IS NOT NULL OR r.aci IS NOT NULL) AND %s AND r.hidden = FALSE """ @@ -376,7 +377,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, r.pni_signature_verified, - r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.blocked_at, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -397,7 +398,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, r.pni_signature_verified, - r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.blocked_at, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -447,7 +448,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, r.pni_signature_verified, - r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.blocked_at, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -892,7 +893,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, expiration_time_version = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ?, hidden = ? + SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, expiration_time_version = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, blocked_at = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ?, hidden = ? WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); @@ -907,17 +908,18 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re statement.setBoolean(8, contact != null && contact.isProfileSharingEnabled()); statement.setString(9, contact == null ? null : contact.color()); statement.setBoolean(10, contact != null && contact.isBlocked()); - statement.setBoolean(11, contact != null && contact.isArchived()); + statement.setLong(11, contact == null ? 0 : contact.blockedAt()); + statement.setBoolean(12, contact != null && contact.isArchived()); if (contact == null || contact.unregisteredTimestamp() == null) { - statement.setNull(12, Types.INTEGER); + statement.setNull(13, Types.INTEGER); } else { - statement.setLong(12, contact.unregisteredTimestamp()); + statement.setLong(13, contact.unregisteredTimestamp()); } - statement.setString(13, contact == null ? null : contact.nickNameGivenName()); - statement.setString(14, contact == null ? null : contact.nickNameFamilyName()); - statement.setString(15, contact == null ? null : contact.note()); - statement.setBoolean(16, contact != null && contact.isHidden()); - statement.setLong(17, recipientId.id()); + statement.setString(14, contact == null ? null : contact.nickNameGivenName()); + statement.setString(15, contact == null ? null : contact.nickNameFamilyName()); + statement.setString(16, contact == null ? null : contact.note()); + statement.setBoolean(17, contact != null && contact.isHidden()); + statement.setLong(18, recipientId.id()); statement.executeUpdate(); } if (contact != null && contact.unregisteredTimestamp() != null) { @@ -1594,7 +1596,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private Contact getContact(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ - SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp + SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.blocked_at, r.archived, r.hidden, r.unregistered_timestamp FROM %s r WHERE r._id = ? AND (%s) """ @@ -1699,6 +1701,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re resultSet.getLong("mute_until"), resultSet.getBoolean("hide_story"), resultSet.getBoolean("blocked"), + resultSet.getLong("blocked_at"), resultSet.getBoolean("archived"), resultSet.getBoolean("profile_sharing"), resultSet.getBoolean("hidden"), diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java index 4f3251e0..84a929e4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java @@ -410,7 +410,7 @@ public class SessionStore implements SignalServiceSessionStore { } private static boolean isActive(SessionRecord record) { - return record != null && record.hasSenderChain(0.0); + return record != null && record.hasSenderChain(); } record Key(String address, int deviceId) {} diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index 54e540cd..f0101c8b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -205,6 +205,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor