Recreate recipient database with aci column

This commit is contained in:
AsamK 2024-01-28 17:57:40 +01:00
parent 90df256e85
commit 76fe6ad799
17 changed files with 375 additions and 239 deletions

View file

@ -5,23 +5,31 @@ import org.whispersystems.signalservice.internal.util.Util;
public record Contact(
String givenName,
String familyName,
String nickName,
String color,
int messageExpirationTime,
long muteUntil,
boolean hideStory,
boolean isBlocked,
boolean isArchived,
boolean isProfileSharingEnabled,
boolean isHidden
boolean isHidden,
Long unregisteredTimestamp
) {
private Contact(final Builder builder) {
this(builder.givenName,
builder.familyName,
builder.nickName,
builder.color,
builder.messageExpirationTime,
builder.muteUntil,
builder.hideStory,
builder.isBlocked,
builder.isArchived,
builder.isProfileSharingEnabled,
builder.isHidden);
builder.isHidden,
builder.unregisteredTimestamp);
}
public static Builder newBuilder() {
@ -32,12 +40,16 @@ public record Contact(
Builder builder = new Builder();
builder.givenName = copy.givenName();
builder.familyName = copy.familyName();
builder.nickName = copy.nickName();
builder.color = copy.color();
builder.messageExpirationTime = copy.messageExpirationTime();
builder.muteUntil = copy.muteUntil();
builder.hideStory = copy.hideStory();
builder.isBlocked = copy.isBlocked();
builder.isArchived = copy.isArchived();
builder.isProfileSharingEnabled = copy.isProfileSharingEnabled();
builder.isHidden = copy.isHidden();
builder.unregisteredTimestamp = copy.unregisteredTimestamp();
return builder;
}
@ -60,12 +72,16 @@ public record Contact(
private String givenName;
private String familyName;
private String nickName;
private String color;
private int messageExpirationTime;
private long muteUntil;
private boolean hideStory;
private boolean isBlocked;
private boolean isArchived;
private boolean isProfileSharingEnabled;
private boolean isHidden;
private Long unregisteredTimestamp;
private Builder() {
}
@ -84,6 +100,11 @@ public record Contact(
return this;
}
public Builder withNickName(final String val) {
nickName = val;
return this;
}
public Builder withColor(final String val) {
color = val;
return this;
@ -94,6 +115,16 @@ public record Contact(
return this;
}
public Builder withMuteUntil(final long val) {
muteUntil = val;
return this;
}
public Builder withHideStory(final boolean val) {
hideStory = val;
return this;
}
public Builder withIsBlocked(final boolean val) {
isBlocked = val;
return this;
@ -114,6 +145,11 @@ public record Contact(
return this;
}
public Builder withUnregisteredTimestamp(final Long val) {
unregisteredTimestamp = val;
return this;
}
public Contact build() {
return new Contact(this);
}

View file

@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.File;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
@ -32,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 = 21;
private static final long DATABASE_VERSION = 22;
private AccountDatabase(final HikariDataSource dataSource) {
super(logger, DATABASE_VERSION, dataSource);
@ -361,60 +362,7 @@ public class AccountDatabase extends Database {
if (oldVersion < 15) {
logger.debug("Updating database: Store serviceId as TEXT");
try (final var statement = connection.createStatement()) {
statement.executeUpdate("""
CREATE TABLE tmp_mapping_table (
uuid BLOB NOT NULL,
address TEXT NOT NULL
) STRICT;
""");
final var sql = (
"""
SELECT r.uuid, r.pni
FROM recipient r
"""
);
final var uuidAddressMapping = new HashMap<UUID, ServiceId>();
try (final var preparedStatement = connection.prepareStatement(sql)) {
try (var result = Utils.executeQueryForStream(preparedStatement, (resultSet) -> {
final var pni = Optional.ofNullable(resultSet.getBytes("pni"))
.map(UuidUtil::parseOrNull)
.map(ServiceId.PNI::from);
final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid"))
.map(UuidUtil::parseOrNull);
final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get()
.equals(pni.get().getRawUuid())
? pni.<ServiceId>map(p -> p)
: serviceIdUuid.<ServiceId>map(ACI::from);
return new Pair<>(serviceId, pni);
})) {
result.forEach(p -> {
final var serviceId = p.first();
final var pni = p.second();
if (serviceId.isPresent()) {
uuidAddressMapping.put(serviceId.get().getRawUuid(), serviceId.get());
}
if (pni.isPresent()) {
uuidAddressMapping.put(pni.get().getRawUuid(), pni.get());
}
});
}
}
final var insertSql = """
INSERT INTO tmp_mapping_table (uuid, address)
VALUES (?,?)
""";
try (final var insertStatement = connection.prepareStatement(insertSql)) {
for (final var entry : uuidAddressMapping.entrySet()) {
final var uuid = entry.getKey();
final var serviceId = entry.getValue();
insertStatement.setBytes(1, UuidUtil.toByteArray(uuid));
insertStatement.setString(2, serviceId.toString());
insertStatement.execute();
}
}
createUuidMappingTable(connection, statement);
statement.executeUpdate("""
CREATE TABLE identity2 (
@ -563,9 +511,120 @@ public class AccountDatabase extends Database {
try (final var statement = connection.createStatement()) {
statement.executeUpdate("""
ALTER TABLE recipient ADD unregistered_timestamp INTEGER;
UPDATE recipient SET pni = NULL WHERE uuid IS NOT NULL;
""");
}
}
if (oldVersion < 22) {
logger.debug("Updating database: Store recipient aci/pni as TEXT");
try (final var statement = connection.createStatement()) {
createUuidMappingTable(connection, statement);
statement.executeUpdate("""
CREATE TABLE recipient2 (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
storage_id BLOB UNIQUE,
storage_record BLOB,
number TEXT UNIQUE,
username TEXT UNIQUE,
aci TEXT UNIQUE,
pni TEXT UNIQUE,
unregistered_timestamp INTEGER,
profile_key BLOB,
profile_key_credential BLOB,
given_name TEXT,
family_name TEXT,
nick_name TEXT,
color TEXT,
expiration_time INTEGER NOT NULL DEFAULT 0,
mute_until INTEGER NOT NULL DEFAULT 0,
blocked INTEGER NOT NULL DEFAULT FALSE,
archived INTEGER NOT NULL DEFAULT FALSE,
profile_sharing INTEGER NOT NULL DEFAULT FALSE,
hide_story INTEGER NOT NULL DEFAULT FALSE,
hidden INTEGER NOT NULL DEFAULT FALSE,
profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0,
profile_given_name TEXT,
profile_family_name TEXT,
profile_about TEXT,
profile_about_emoji TEXT,
profile_avatar_url_path TEXT,
profile_mobile_coin_address BLOB,
profile_unidentified_access_mode TEXT,
profile_capabilities TEXT
) STRICT;
INSERT INTO recipient2 (_id, aci, pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities)
SELECT r._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.uuid AND t.address not like 'PNI:%') aci, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.pni AND t.address like 'PNI:%') pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities
FROM recipient r;
DROP TABLE recipient;
ALTER TABLE recipient2 RENAME TO recipient;
DROP TABLE tmp_mapping_table;
""");
}
}
}
private static void createUuidMappingTable(
final Connection connection, final Statement statement
) throws SQLException {
statement.executeUpdate("""
CREATE TABLE tmp_mapping_table (
uuid BLOB NOT NULL,
address TEXT NOT NULL
) STRICT;
""");
final var sql = (
"""
SELECT r.uuid, r.pni
FROM recipient r
"""
);
final var uuidAddressMapping = new HashMap<UUID, ServiceId>();
try (final var preparedStatement = connection.prepareStatement(sql)) {
try (var result = Utils.executeQueryForStream(preparedStatement, (resultSet) -> {
final var pni = Optional.ofNullable(resultSet.getBytes("pni"))
.map(UuidUtil::parseOrNull)
.map(ServiceId.PNI::from);
final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull);
final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get()
.equals(pni.get().getRawUuid())
? pni.<ServiceId>map(p -> p)
: serviceIdUuid.<ServiceId>map(ACI::from);
return new Pair<>(serviceId, pni);
})) {
result.forEach(p -> {
final var serviceId = p.first();
final var pni = p.second();
if (serviceId.isPresent()) {
final var rawUuid = serviceId.get().getRawUuid();
if (!uuidAddressMapping.containsKey(rawUuid)) {
uuidAddressMapping.put(rawUuid, serviceId.get());
}
}
if (pni.isPresent()) {
uuidAddressMapping.put(pni.get().getRawUuid(), pni.get());
}
});
}
}
final var insertSql = """
INSERT INTO tmp_mapping_table (uuid, address)
VALUES (?,?)
""";
try (final var insertStatement = connection.prepareStatement(insertSql)) {
for (final var entry : uuidAddressMapping.entrySet()) {
final var uuid = entry.getKey();
final var serviceId = entry.getValue();
insertStatement.setBytes(1, UuidUtil.toByteArray(uuid));
insertStatement.setString(2, serviceId.toString());
insertStatement.execute();
}
}
}
}

View file

@ -385,8 +385,15 @@ public class SignalAccount implements Closeable {
}
getRecipientStore().deleteRecipientData(recipientId);
getMessageCache().deleteMessages(recipientId);
if (recipientAddress.serviceId().isPresent()) {
final var serviceId = recipientAddress.serviceId().get();
if (recipientAddress.aci().isPresent()) {
final var serviceId = recipientAddress.aci().get();
aciAccountData.getSessionStore().deleteAllSessions(serviceId);
pniAccountData.getSessionStore().deleteAllSessions(serviceId);
getIdentityKeyStore().deleteIdentity(serviceId);
getSenderKeyStore().deleteAll(serviceId);
}
if (recipientAddress.pni().isPresent()) {
final var serviceId = recipientAddress.pni().get();
aciAccountData.getSessionStore().deleteAllSessions(serviceId);
pniAccountData.getSessionStore().deleteAllSessions(serviceId);
getIdentityKeyStore().deleteIdentity(serviceId);
@ -837,13 +844,17 @@ public class SignalAccount implements Closeable {
final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
getContactStore().storeContact(recipientId,
new Contact(contact.name,
null,
null,
contact.color,
contact.messageExpirationTime,
0,
false,
contact.blocked,
contact.archived,
false,
false));
false,
null));
// Store profile keys only in profile store
var profileKeyString = contact.profileKey;

View file

@ -45,7 +45,7 @@ public class LegacyGroupStore {
if (g instanceof Storage.GroupV1 g1) {
final var members = g1.members.stream().map(m -> {
if (m.recipientId == null) {
return recipientResolver.resolveRecipient(new RecipientAddress(ServiceId.parseOrNull(m.uuid),
return recipientResolver.resolveRecipient(new RecipientAddress(ServiceId.ACI.parseOrNull(m.uuid),
m.number));
}

View file

@ -43,7 +43,9 @@ public class LegacyProfileStore {
if (node.isArray()) {
for (var entry : node) {
var name = entry.hasNonNull("name") ? entry.get("name").asText() : null;
var serviceId = entry.hasNonNull("uuid") ? ServiceId.parseOrNull(entry.get("uuid").asText()) : null;
var serviceId = entry.hasNonNull("uuid")
? ServiceId.ACI.parseOrNull(entry.get("uuid").asText())
: null;
final var address = new RecipientAddress(serviceId, name);
ProfileKey profileKey = null;
try {

View file

@ -36,7 +36,7 @@ public class LegacyRecipientStore {
if (node.isArray()) {
for (var recipient : node) {
var recipientName = recipient.get("name").asText();
var serviceId = ServiceId.parseOrThrow(recipient.get("uuid").asText());
var serviceId = ServiceId.ACI.parseOrThrow(recipient.get("uuid").asText());
addresses.add(new RecipientAddress(serviceId, recipientName));
}
}

View file

@ -39,13 +39,17 @@ public class LegacyRecipientStore2 {
Contact contact = null;
if (r.contact != null) {
contact = new Contact(r.contact.name,
null,
null,
r.contact.color,
r.contact.messageExpirationTime,
0,
false,
r.contact.blocked,
r.contact.archived,
r.contact.profileSharingEnabled,
false);
false,
null);
}
ProfileKey profileKey = null;

View file

@ -32,22 +32,19 @@ public class MergeRecipientHelper {
return new Pair<>(recipient.id(), List.of());
}
if (recipient.address().serviceId().isEmpty() || (
recipient.address().serviceId().equals(address.serviceId())
) || (
recipient.address().pni().isPresent() && recipient.address().pni().equals(address.serviceId())
) || (
recipient.address().serviceId().equals(address.pni())
) || (
address.pni().isPresent() && address.pni().equals(recipient.address().pni())
)) {
if (recipient.address().aci().isEmpty() || (
address.aci().isEmpty() && (
address.pni().isEmpty()
|| recipient.address().pni().equals(address.pni())
)
) || recipient.address().aci().equals(address.aci())) {
logger.debug("Got existing recipient {}, updating with high trust address", recipient.id());
store.updateRecipientAddress(recipient.id(), recipient.address().withIdentifiersFrom(address));
return new Pair<>(recipient.id(), List.of());
}
logger.debug(
"Got recipient {} existing with number/pni/username, but different serviceId, so stripping its number and adding new recipient",
"Got recipient {} existing with number/pni/username, but different aci, so stripping its number and adding new recipient",
recipient.id());
store.updateRecipientAddress(recipient.id(), recipient.address().removeIdentifiersFrom(address));
@ -55,14 +52,10 @@ public class MergeRecipientHelper {
}
var resultingRecipient = recipients.stream()
.filter(r -> r.address().serviceId().equals(address.serviceId()) || r.address()
.pni()
.equals(address.serviceId()))
.filter(r -> r.address().aci().isPresent() && r.address().aci().equals(address.aci()))
.findFirst();
if (resultingRecipient.isEmpty() && address.pni().isPresent()) {
resultingRecipient = recipients.stream().filter(r -> r.address().serviceId().equals(address.pni()) || (
address.serviceId().equals(address.pni()) && r.address().pni().equals(address.pni())
)).findFirst();
resultingRecipient = recipients.stream().filter(r -> r.address().pni().equals(address.pni())).findFirst();
}
final Set<RecipientWithAddress> remainingRecipients;

View file

@ -8,59 +8,60 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Optional;
public record RecipientAddress(
Optional<ServiceId> serviceId, Optional<PNI> pni, Optional<String> number, Optional<String> username
Optional<ACI> aci, Optional<PNI> pni, Optional<String> number, Optional<String> username
) {
/**
* Construct a RecipientAddress.
*
* @param serviceId The ACI or PNI of the user, if available.
* @param number The phone number of the user, if available.
* @param aci The ACI of the user, if available.
* @param pni The PNI of the user, if available.
* @param number The phone number of the user, if available.
* @param username The username of the user, if available.
*/
public RecipientAddress {
if (serviceId.isPresent() && serviceId.get().isUnknown()) {
serviceId = Optional.empty();
if (aci.isPresent() && aci.get().isUnknown()) {
aci = Optional.empty();
}
if (pni.isPresent() && pni.get().isUnknown()) {
pni = Optional.empty();
}
if (serviceId.isEmpty() && pni.isPresent()) {
serviceId = Optional.of(pni.get());
}
if (serviceId.isPresent() && serviceId.get() instanceof PNI sPNI) {
if (pni.isPresent() && !sPNI.equals(pni.get())) {
throw new AssertionError("Must not have two different PNIs!");
}
if (pni.isEmpty()) {
pni = Optional.of(sPNI);
}
}
if (serviceId.isEmpty() && number.isEmpty()) {
if (aci.isEmpty() && pni.isEmpty() && number.isEmpty()) {
throw new AssertionError("Must have either a ServiceId or E164 number!");
}
}
public RecipientAddress(Optional<ServiceId> serviceId, Optional<String> number) {
this(serviceId, Optional.empty(), number, Optional.empty());
this(serviceId.filter(s -> s instanceof ACI).map(s -> (ACI) s),
serviceId.filter(s -> s instanceof PNI).map(s -> (PNI) s),
number,
Optional.empty());
}
public RecipientAddress(ServiceId serviceId, String e164) {
this(Optional.ofNullable(serviceId), Optional.empty(), Optional.ofNullable(e164), Optional.empty());
public RecipientAddress(ACI aci, String e164) {
this(Optional.ofNullable(aci), Optional.empty(), Optional.ofNullable(e164), Optional.empty());
}
public RecipientAddress(ServiceId serviceId, PNI pni, String e164) {
this(Optional.ofNullable(serviceId), Optional.ofNullable(pni), Optional.ofNullable(e164), Optional.empty());
public RecipientAddress(String e164) {
this(Optional.empty(), Optional.empty(), Optional.ofNullable(e164), Optional.empty());
}
public RecipientAddress(ServiceId serviceId, PNI pni, String e164, String username) {
this(Optional.ofNullable(serviceId),
public RecipientAddress(ACI aci, PNI pni, String e164) {
this(Optional.ofNullable(aci), Optional.ofNullable(pni), Optional.ofNullable(e164), Optional.empty());
}
public RecipientAddress(ACI aci, PNI pni, String e164, String username) {
this(Optional.ofNullable(aci),
Optional.ofNullable(pni),
Optional.ofNullable(e164),
Optional.ofNullable(username));
}
public RecipientAddress(SignalServiceAddress address) {
this(Optional.of(address.getServiceId()), Optional.empty(), address.getNumber(), Optional.empty());
this(address.getServiceId() instanceof ACI ? Optional.of((ACI) address.getServiceId()) : Optional.empty(),
address.getServiceId() instanceof PNI ? Optional.of((PNI) address.getServiceId()) : Optional.empty(),
address.getNumber(),
Optional.empty());
}
public RecipientAddress(org.asamk.signal.manager.api.RecipientAddress address) {
@ -72,30 +73,28 @@ public record RecipientAddress(
}
public RecipientAddress withIdentifiersFrom(RecipientAddress address) {
return new RecipientAddress((
this.serviceId.isEmpty() || this.isServiceIdPNI() || this.serviceId.equals(address.pni)
) && !address.isServiceIdPNI() ? address.serviceId : this.serviceId,
return new RecipientAddress(address.aci.or(this::aci),
address.pni.or(this::pni),
address.number.or(this::number),
address.username.or(this::username));
}
public RecipientAddress removeIdentifiersFrom(RecipientAddress address) {
return new RecipientAddress(address.serviceId.equals(this.serviceId) || address.pni.equals(this.serviceId)
? Optional.empty()
: this.serviceId,
address.pni.equals(this.pni) || address.serviceId.equals(this.pni) ? Optional.empty() : this.pni,
return new RecipientAddress(address.aci.equals(this.aci) ? Optional.empty() : this.aci,
address.pni.equals(this.pni) ? Optional.empty() : this.pni,
address.number.equals(this.number) ? Optional.empty() : this.number,
address.username.equals(this.username) ? Optional.empty() : this.username);
}
public Optional<ACI> aci() {
return serviceId.map(s -> s instanceof ServiceId.ACI aci ? aci : null);
public Optional<ServiceId> serviceId() {
return aci.map(aci -> (ServiceId) aci).or(this::pni);
}
public String getIdentifier() {
if (serviceId.isPresent()) {
return serviceId.get().toString();
if (aci.isPresent()) {
return aci.get().toString();
} else if (pni.isPresent()) {
return pni.get().toString();
} else if (number.isPresent()) {
return number.get();
} else {
@ -106,38 +105,31 @@ public record RecipientAddress(
public String getLegacyIdentifier() {
if (number.isPresent()) {
return number.get();
} else if (serviceId.isPresent()) {
return serviceId.get().toString();
} else if (aci.isPresent()) {
return aci.get().toString();
} else if (pni.isPresent()) {
return pni.get().toString();
} else {
throw new AssertionError("Given the checks in the constructor, this should not be possible.");
}
}
public boolean matches(RecipientAddress other) {
return (serviceId.isPresent() && other.serviceId.isPresent() && serviceId.get().equals(other.serviceId.get()))
|| (
pni.isPresent() && other.serviceId.isPresent() && pni.get().equals(other.serviceId.get())
)
|| (
serviceId.isPresent() && other.pni.isPresent() && serviceId.get().equals(other.pni.get())
)
|| (
return (aci.isPresent() && other.aci.isPresent() && aci.get().equals(other.aci.get())) || (
pni.isPresent() && other.pni.isPresent() && pni.get().equals(other.pni.get())
)
|| (
) || (
number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get())
);
}
public boolean hasSingleIdentifier() {
final var identifiersCount = serviceId().map(s -> 1).orElse(0)
+ number().map(s -> 1).orElse(0)
+ username().map(s -> 1).orElse(0);
final var identifiersCount = aci().map(s -> 1).orElse(0) + pni().map(s -> 1).orElse(0) + number().map(s -> 1)
.orElse(0) + username().map(s -> 1).orElse(0);
return identifiersCount == 1;
}
public boolean hasIdentifiersOf(RecipientAddress address) {
return (address.serviceId.isEmpty() || address.serviceId.equals(serviceId) || address.serviceId.equals(pni))
return (address.aci.isEmpty() || address.aci.equals(aci))
&& (address.pni.isEmpty() || address.pni.equals(pni))
&& (address.number.isEmpty() || address.number.equals(number))
&& (address.username.isEmpty() || address.username.equals(username));
@ -145,13 +137,12 @@ public record RecipientAddress(
public boolean hasAdditionalIdentifiersThan(RecipientAddress address) {
return (
serviceId.isPresent() && (
address.serviceId.isEmpty() || (
!address.serviceId.equals(serviceId) && !address.pni.equals(serviceId)
)
aci.isPresent() && (
address.aci.isEmpty() || !address.aci.equals(aci)
)
) || (
pni.isPresent() && !address.serviceId.equals(pni) && (
pni.isPresent() && (
address.pni.isEmpty() || !address.pni.equals(pni)
)
) || (
@ -166,19 +157,15 @@ public record RecipientAddress(
}
public boolean hasOnlyPniAndNumber() {
return pni.isPresent() && serviceId.equals(pni) && number.isPresent();
}
public boolean isServiceIdPNI() {
return serviceId.isPresent() && (pni.isPresent() && serviceId.equals(pni));
return pni.isPresent() && aci.isEmpty() && number.isPresent();
}
public SignalServiceAddress toSignalServiceAddress() {
return new SignalServiceAddress(serviceId.orElse(ACI.UNKNOWN), number);
return new SignalServiceAddress(aci.orElse(ACI.UNKNOWN), number);
}
public org.asamk.signal.manager.api.RecipientAddress toApiRecipientAddress() {
return new org.asamk.signal.manager.api.RecipientAddress(serviceId().map(ServiceId::getRawUuid),
return new org.asamk.signal.manager.api.RecipientAddress(aci().map(ServiceId::getRawUuid),
number(),
username());
}

View file

@ -19,7 +19,6 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
import java.sql.ResultSet;
@ -41,7 +40,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private static final Logger logger = LoggerFactory.getLogger(RecipientStore.class);
private static final String TABLE_RECIPIENT = "recipient";
private static final String SQL_IS_CONTACT = "r.given_name IS NOT NULL OR r.family_name IS NOT NULL OR r.expiration_time > 0 OR r.profile_sharing = TRUE OR r.color IS NOT NULL OR r.blocked = TRUE OR r.archived = TRUE";
private static final String SQL_IS_CONTACT = "r.given_name IS NOT NULL OR r.family_name IS NOT NULL OR r.nick_name IS NOT NULL OR r.expiration_time > 0 OR r.profile_sharing = TRUE OR r.color IS NOT NULL OR r.blocked = TRUE OR r.archived = TRUE";
private final RecipientMergeHandler recipientMergeHandler;
private final SelfAddressProvider selfAddressProvider;
@ -63,20 +62,23 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
storage_record BLOB,
number TEXT UNIQUE,
username TEXT UNIQUE,
uuid BLOB UNIQUE,
pni BLOB UNIQUE,
aci TEXT UNIQUE,
pni TEXT UNIQUE,
unregistered_timestamp INTEGER,
profile_key BLOB,
profile_key_credential BLOB,
given_name TEXT,
family_name TEXT,
nick_name TEXT,
color TEXT,
expiration_time INTEGER NOT NULL DEFAULT 0,
mute_until INTEGER NOT NULL DEFAULT 0,
blocked INTEGER NOT NULL DEFAULT FALSE,
archived INTEGER NOT NULL DEFAULT FALSE,
profile_sharing INTEGER NOT NULL DEFAULT FALSE,
hide_story INTEGER NOT NULL DEFAULT FALSE,
hidden INTEGER NOT NULL DEFAULT FALSE,
profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0,
@ -108,7 +110,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public RecipientAddress resolveRecipientAddress(RecipientId recipientId) {
final var sql = (
"""
SELECT r.number, r.uuid, r.pni, r.username
SELECT r.number, r.aci, r.pni, r.username
FROM %s r
WHERE r._id = ?
"""
@ -310,8 +312,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public RecipientId resolveRecipientTrusted(
final Optional<ACI> aci, final Optional<PNI> pni, final Optional<String> number
) {
final var serviceId = aci.map(a -> (ServiceId) a).or(() -> pni);
return resolveRecipientTrusted(new RecipientAddress(serviceId, pni, number, Optional.empty()));
return resolveRecipientTrusted(new RecipientAddress(aci, pni, number, Optional.empty()));
}
@Override
@ -341,9 +342,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public List<Pair<RecipientId, Contact>> getContacts() {
final var sql = (
"""
SELECT r._id, r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden
SELECT r._id, r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp
FROM %s r
WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s AND r.hidden = FALSE
WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s AND r.hidden = FALSE
"""
).formatted(TABLE_RECIPIENT, SQL_IS_CONTACT);
try (final var connection = database.getConnection()) {
@ -363,9 +364,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
SELECT r._id,
r.number, r.uuid, r.pni, r.username,
r.number, r.aci, r.pni, r.username,
r.profile_key, r.profile_key_credential,
r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden,
r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, 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.storage_record
FROM %s r
@ -382,9 +383,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
SELECT r._id,
r.number, r.uuid, r.pni, r.username,
r.number, r.aci, r.pni, r.username,
r.profile_key, r.profile_key_credential,
r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden,
r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, 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.storage_record
FROM %s r
@ -417,16 +418,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
SELECT r._id,
r.number, r.uuid, r.pni, r.username,
r.number, r.aci, r.pni, r.username,
r.profile_key, r.profile_key_credential,
r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden,
r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, 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.storage_record
FROM %s r
WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s
WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s
"""
).formatted(TABLE_RECIPIENT, sqlWhere.isEmpty() ? "TRUE" : String.join(" AND ", sqlWhere));
final var selfServiceId = selfAddressProvider.getSelfAddress().serviceId();
final var selfAddress = selfAddressProvider.getSelfAddress();
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
if (blocked.isPresent()) {
@ -436,7 +437,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
return result.filter(r -> name.isEmpty() || (
r.getContact() != null && name.get().equals(r.getContact().getName())
) || (r.getProfile() != null && name.get().equals(r.getProfile().getDisplayName()))).map(r -> {
if (r.getAddress().serviceId().equals(selfServiceId)) {
if (r.getAddress().matches(selfAddress)) {
return Recipient.newBuilder(r)
.withProfileKey(selfProfileKeyProvider.getSelfProfileKey())
.build();
@ -482,21 +483,21 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public Map<ServiceId, ProfileKey> getServiceIdToProfileKeyMap() {
final var sql = (
"""
SELECT r.uuid, r.profile_key
SELECT r.aci, r.profile_key
FROM %s r
WHERE r.uuid IS NOT NULL AND r.profile_key IS NOT NULL
WHERE r.aci IS NOT NULL AND r.profile_key IS NOT NULL
"""
).formatted(TABLE_RECIPIENT);
final var selfServiceId = selfAddressProvider.getSelfAddress().serviceId().orElse(null);
final var selfAci = selfAddressProvider.getSelfAddress().aci().orElse(null);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
return Utils.executeQueryForStream(statement, resultSet -> {
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
if (serviceId.equals(selfServiceId)) {
return new Pair<>(serviceId, selfProfileKeyProvider.getSelfProfileKey());
final var aci = ACI.parseOrThrow(resultSet.getString("aci"));
if (aci.equals(selfAci)) {
return new Pair<>(aci, selfProfileKeyProvider.getSelfProfileKey());
}
final var profileKey = getProfileKeyFromResultSet(resultSet);
return new Pair<>(serviceId, profileKey);
return new Pair<>(aci, profileKey);
}).filter(Objects::nonNull).collect(Collectors.toMap(Pair::first, Pair::second));
}
} catch (SQLException e) {
@ -509,7 +510,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
"""
SELECT r._id
FROM %s r
WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL)
WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL)
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
@ -657,7 +658,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public List<StorageId> getStorageIds(Connection connection) throws SQLException {
final var sql = """
SELECT r.storage_id
FROM %s r WHERE r.storage_id IS NOT NULL AND r._id != ? AND (r.uuid IS NOT NULL OR r.pni IS NOT NULL)
FROM %s r WHERE r.storage_id IS NOT NULL AND r._id != ? AND (r.aci IS NOT NULL OR r.pni IS NOT NULL)
""".formatted(TABLE_RECIPIENT);
final var selfRecipientId = resolveRecipient(connection, selfAddressProvider.getSelfAddress());
try (final var statement = connection.prepareStatement(sql)) {
@ -767,7 +768,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
long start = System.nanoTime();
final var sql = (
"""
INSERT INTO %s (_id, number, uuid)
INSERT INTO %s (_id, number, aci)
VALUES (?, ?, ?)
"""
).formatted(TABLE_RECIPIENT);
@ -780,12 +781,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
for (final var recipient : recipients.values()) {
statement.setLong(1, recipient.getRecipientId().id());
statement.setString(2, recipient.getAddress().number().orElse(null));
statement.setBytes(3,
recipient.getAddress()
.serviceId()
.map(ServiceId::getRawUuid)
.map(UuidUtil::toByteArray)
.orElse(null));
statement.setString(3, recipient.getAddress().aci().map(ACI::toString).orElse(null));
statement.executeUpdate();
}
}
@ -829,19 +825,27 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
UPDATE %s
SET given_name = ?, family_name = ?, expiration_time = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?
SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, contact == null ? null : contact.givenName());
statement.setString(2, contact == null ? null : contact.familyName());
statement.setInt(3, contact == null ? 0 : contact.messageExpirationTime());
statement.setBoolean(4, contact != null && contact.isProfileSharingEnabled());
statement.setString(5, contact == null ? null : contact.color());
statement.setBoolean(6, contact != null && contact.isBlocked());
statement.setBoolean(7, contact != null && contact.isArchived());
statement.setLong(8, recipientId.id());
statement.setString(3, contact == null ? null : contact.nickName());
statement.setInt(4, contact == null ? 0 : contact.messageExpirationTime());
statement.setLong(5, contact == null ? 0 : contact.muteUntil());
statement.setBoolean(6, contact != null && contact.hideStory());
statement.setBoolean(7, contact != null && contact.isProfileSharingEnabled());
statement.setString(8, contact == null ? null : contact.color());
statement.setBoolean(9, contact != null && contact.isBlocked());
statement.setBoolean(10, contact != null && contact.isArchived());
if (contact == null || contact.unregisteredTimestamp() == null) {
statement.setNull(11, Types.INTEGER);
} else {
statement.setLong(11, contact.unregisteredTimestamp());
}
statement.setLong(12, recipientId.id());
statement.executeUpdate();
}
rotateStorageId(connection, recipientId);
@ -1055,12 +1059,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private RecipientId resolveRecipientLocked(
Connection connection, RecipientAddress address
) throws SQLException {
final var aci = address.aci().isEmpty()
final var byAci = address.aci().isEmpty()
? Optional.<RecipientWithAddress>empty()
: findByServiceId(connection, address.aci().get());
if (aci.isPresent()) {
return aci.get().id();
if (byAci.isPresent()) {
return byAci.get().id();
}
final var byPni = address.pni().isEmpty()
@ -1104,7 +1108,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
if (recipient.isEmpty()) {
logger.debug("Got new recipient, number is unknown");
return addNewRecipient(connection, new RecipientAddress(null, number));
return addNewRecipient(connection, new RecipientAddress(number));
}
return recipient.get().id();
@ -1115,15 +1119,15 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
) throws SQLException {
final var sql = (
"""
INSERT INTO %s (number, uuid, pni, username)
INSERT INTO %s (number, aci, pni, username)
VALUES (?, ?, ?, ?)
RETURNING _id
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, address.number().orElse(null));
statement.setBytes(2, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null));
statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null));
statement.setString(2, address.aci().map(ACI::toString).orElse(null));
statement.setString(3, address.pni().map(PNI::toString).orElse(null));
statement.setString(4, address.username().orElse(null));
final var generatedKey = Utils.executeQueryForOptional(statement, Utils::getIdMapper);
if (generatedKey.isPresent()) {
@ -1142,7 +1146,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
UPDATE %s
SET number = NULL, uuid = NULL, pni = NULL, username = NULL, storage_id = NULL
SET number = NULL, aci = NULL, pni = NULL, username = NULL, storage_id = NULL
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
@ -1161,14 +1165,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
UPDATE %s
SET number = ?, uuid = ?, pni = ?, username = ?
SET number = ?, aci = ?, pni = ?, username = ?
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, address.number().orElse(null));
statement.setBytes(2, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null));
statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null));
statement.setString(2, address.aci().map(ACI::toString).orElse(null));
statement.setString(3, address.pni().map(PNI::toString).orElse(null));
statement.setString(4, address.username().orElse(null));
statement.setLong(5, recipientId.id());
statement.executeUpdate();
@ -1225,7 +1229,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final Connection connection, final String number
) throws SQLException {
final var sql = """
SELECT r._id, r.number, r.uuid, r.pni, r.username
SELECT r._id, r.number, r.aci, r.pni, r.username
FROM %s r
WHERE r.number = ?
LIMIT 1
@ -1240,7 +1244,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final Connection connection, final String username
) throws SQLException {
final var sql = """
SELECT r._id, r.number, r.uuid, r.pni, r.username
SELECT r._id, r.number, r.aci, r.pni, r.username
FROM %s r
WHERE r.username = ?
LIMIT 1
@ -1259,13 +1263,13 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
return recipientWithAddress;
}
final var sql = """
SELECT r._id, r.number, r.uuid, r.pni, r.username
SELECT r._id, r.number, r.aci, r.pni, r.username
FROM %s r
WHERE %s = ?1
LIMIT 1
""".formatted(TABLE_RECIPIENT, serviceId instanceof ACI ? "r.uuid" : "r.pni");
""".formatted(TABLE_RECIPIENT, serviceId instanceof ACI ? "r.aci" : "r.pni");
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, UuidUtil.toByteArray(serviceId.getRawUuid()));
statement.setString(1, serviceId.toString());
recipientWithAddress = Utils.executeQueryForOptional(statement, this::getRecipientWithAddressFromResultSet);
recipientWithAddress.ifPresent(r -> recipientAddressCache.put(serviceId, r));
return recipientWithAddress;
@ -1276,16 +1280,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final Connection connection, final RecipientAddress address
) throws SQLException {
final var sql = """
SELECT r._id, r.number, r.uuid, r.pni, r.username
SELECT r._id, r.number, r.aci, r.pni, r.username
FROM %s r
WHERE r.uuid = ?1 OR
WHERE r.aci = ?1 OR
r.pni = ?2 OR
r.number = ?3 OR
r.username = ?4
""".formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null));
statement.setBytes(2, address.pni().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null));
statement.setString(1, address.aci().map(ServiceId::toString).orElse(null));
statement.setString(2, address.pni().map(ServiceId::toString).orElse(null));
statement.setString(3, address.number().orElse(null));
statement.setString(4, address.username().orElse(null));
return Utils.executeQueryForStream(statement, this::getRecipientWithAddressFromResultSet)
@ -1296,7 +1300,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.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden
SELECT r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp
FROM %s r
WHERE r._id = ? AND (%s)
"""
@ -1357,13 +1361,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
}
private RecipientAddress getRecipientAddressFromResultSet(ResultSet resultSet) throws SQLException {
final var pni = Optional.ofNullable(resultSet.getBytes("pni")).map(UuidUtil::parseOrNull).map(PNI::from);
final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull);
final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get()
.equals(pni.get().getRawUuid()) ? pni.<ServiceId>map(p -> p) : serviceIdUuid.<ServiceId>map(ACI::from);
final var aci = Optional.ofNullable(resultSet.getString("aci")).map(ACI::parseOrThrow);
final var pni = Optional.ofNullable(resultSet.getString("pni")).map(PNI::parseOrThrow);
final var number = Optional.ofNullable(resultSet.getString("number"));
final var username = Optional.ofNullable(resultSet.getString("username"));
return new RecipientAddress(serviceId, pni, number, username);
return new RecipientAddress(aci, pni, number, username);
}
private RecipientId getRecipientIdFromResultSet(ResultSet resultSet) throws SQLException {
@ -1386,14 +1388,19 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
}
private Contact getContactFromResultSet(ResultSet resultSet) throws SQLException {
final var unregisteredTimestamp = resultSet.getLong("unregistered_timestamp");
return new Contact(resultSet.getString("given_name"),
resultSet.getString("family_name"),
resultSet.getString("nick_name"),
resultSet.getString("color"),
resultSet.getInt("expiration_time"),
resultSet.getLong("mute_until"),
resultSet.getBoolean("hide_story"),
resultSet.getBoolean("blocked"),
resultSet.getBoolean("archived"),
resultSet.getBoolean("profile_sharing"),
resultSet.getBoolean("hidden"));
resultSet.getBoolean("hidden"),
unregisteredTimestamp == 0 ? null : unregisteredTimestamp);
}
private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException {

View file

@ -233,22 +233,38 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
final var profileShared = contact != null && contact.isProfileSharingEnabled();
final var archived = contact != null && contact.isArchived();
final var hidden = contact != null && contact.isHidden();
final var hideStory = contact != null && contact.hideStory();
final var muteUntil = contact == null ? 0 : contact.muteUntil();
final var unregisteredTimestamp = contact == null || contact.unregisteredTimestamp() == null
? 0
: contact.unregisteredTimestamp();
final var contactGivenName = contact == null ? null : contact.givenName();
final var contactFamilyName = contact == null ? null : contact.familyName();
final var contactNickName = contact == null ? null : contact.nickName();
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.getSystemFamilyName().orElse(null), contactFamilyName)
|| !Objects.equals(contactRecord.getSystemNickname().orElse(null), contactNickName)) {
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));
.withFamilyName(contactRecord.getSystemFamilyName().orElse(null))
.withNickName(contactRecord.getSystemNickname().orElse(null))
.withUnregisteredTimestamp(contactRecord.getUnregisteredTimestamp() == 0
? null
: contactRecord.getUnregisteredTimestamp());
account.getRecipientStore().storeContact(connection, recipientId, newContact.build());
}

View file

@ -94,9 +94,15 @@ public final class StorageSyncModels {
}
if (recipient.getContact() != null) {
builder.setSystemGivenName(recipient.getContact().givenName())
.setSystemFamilyName((recipient.getContact().familyName()))
.setSystemFamilyName(recipient.getContact().familyName())
.setSystemNickname(recipient.getContact().nickName())
.setBlocked(recipient.getContact().isBlocked())
.setProfileSharingEnabled(recipient.getContact().isProfileSharingEnabled())
.setMuteUntil(recipient.getContact().muteUntil())
.setHideStory(recipient.getContact().hideStory())
.setUnregisteredTimestamp(recipient.getContact().unregisteredTimestamp() == null
? 0
: recipient.getContact().unregisteredTimestamp())
.setArchived(recipient.getContact().isArchived())
.setHidden(recipient.getContact().isHidden());
}

View file

@ -5,7 +5,6 @@ import org.signal.core.util.Base64;
import org.signal.core.util.SetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
@ -181,11 +180,11 @@ public final class StorageSyncValidations {
if (insert.getContact().isPresent()) {
final var contact = insert.getContact().get();
final var serviceId = contact.getServiceId().map(ServiceId.class::cast);
final var aci = contact.getAci();
final var pni = contact.getPni();
final var number = contact.getNumber();
final var username = contact.getUsername();
final var address = new RecipientAddress(serviceId, pni, number, username);
final var address = new RecipientAddress(aci, pni, number, username);
if (self.matches(address)) {
throw new SelfAddedAsContactError();
}