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

@ -2619,7 +2619,7 @@
"allDeclaredFields":true, "allDeclaredFields":true,
"queryAllDeclaredMethods":true, "queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true, "queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }] "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"<init>","parameterTypes":["java.lang.String","boolean"] }, {"name":"getKeepLinkHandle","parameterTypes":[] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }]
}, },
{ {
"name":"org.whispersystems.signalservice.internal.push.SetUsernameLinkResponseBody", "name":"org.whispersystems.signalservice.internal.push.SetUsernameLinkResponseBody",
@ -2648,6 +2648,13 @@
"queryAllDeclaredConstructors":true, "queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"getCaptcha","parameterTypes":[] }, {"name":"getMcc","parameterTypes":[] }, {"name":"getMnc","parameterTypes":[] }, {"name":"getPushChallenge","parameterTypes":[] }, {"name":"getPushToken","parameterTypes":[] }, {"name":"getPushTokenType","parameterTypes":[] }] "methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"getCaptcha","parameterTypes":[] }, {"name":"getMcc","parameterTypes":[] }, {"name":"getMnc","parameterTypes":[] }, {"name":"getPushChallenge","parameterTypes":[] }, {"name":"getPushToken","parameterTypes":[] }, {"name":"getPushTokenType","parameterTypes":[] }]
}, },
{
"name":"org.whispersystems.signalservice.internal.push.VerificationCodeFailureResponseBody",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":["boolean","java.lang.String"] }, {"name":"<init>","parameterTypes":["boolean","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }]
},
{ {
"name":"org.whispersystems.signalservice.internal.push.VerificationSessionMetadataRequestBody", "name":"org.whispersystems.signalservice.internal.push.VerificationSessionMetadataRequestBody",
"allDeclaredFields":true, "allDeclaredFields":true,

View file

@ -215,6 +215,6 @@
}]}, }]},
"bundles":[{ "bundles":[{
"name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl", "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl",
"locales":["", "en", "und"] "locales":["", "de", "en", "und"]
}] }]
} }

View file

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

View file

@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.File; import java.io.File;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap; import java.util.HashMap;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -32,7 +33,7 @@ import java.util.UUID;
public class AccountDatabase extends Database { public class AccountDatabase extends Database {
private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class); 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) { private AccountDatabase(final HikariDataSource dataSource) {
super(logger, DATABASE_VERSION, dataSource); super(logger, DATABASE_VERSION, dataSource);
@ -361,60 +362,7 @@ public class AccountDatabase extends Database {
if (oldVersion < 15) { if (oldVersion < 15) {
logger.debug("Updating database: Store serviceId as TEXT"); logger.debug("Updating database: Store serviceId as TEXT");
try (final var statement = connection.createStatement()) { try (final var statement = connection.createStatement()) {
statement.executeUpdate(""" createUuidMappingTable(connection, statement);
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();
}
}
statement.executeUpdate(""" statement.executeUpdate("""
CREATE TABLE identity2 ( CREATE TABLE identity2 (
@ -563,9 +511,120 @@ public class AccountDatabase extends Database {
try (final var statement = connection.createStatement()) { try (final var statement = connection.createStatement()) {
statement.executeUpdate(""" statement.executeUpdate("""
ALTER TABLE recipient ADD unregistered_timestamp INTEGER; 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); getRecipientStore().deleteRecipientData(recipientId);
getMessageCache().deleteMessages(recipientId); getMessageCache().deleteMessages(recipientId);
if (recipientAddress.serviceId().isPresent()) { if (recipientAddress.aci().isPresent()) {
final var serviceId = recipientAddress.serviceId().get(); 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); aciAccountData.getSessionStore().deleteAllSessions(serviceId);
pniAccountData.getSessionStore().deleteAllSessions(serviceId); pniAccountData.getSessionStore().deleteAllSessions(serviceId);
getIdentityKeyStore().deleteIdentity(serviceId); getIdentityKeyStore().deleteIdentity(serviceId);
@ -837,13 +844,17 @@ public class SignalAccount implements Closeable {
final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress()); final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
getContactStore().storeContact(recipientId, getContactStore().storeContact(recipientId,
new Contact(contact.name, new Contact(contact.name,
null,
null, null,
contact.color, contact.color,
contact.messageExpirationTime, contact.messageExpirationTime,
0,
false,
contact.blocked, contact.blocked,
contact.archived, contact.archived,
false, false,
false)); false,
null));
// Store profile keys only in profile store // Store profile keys only in profile store
var profileKeyString = contact.profileKey; var profileKeyString = contact.profileKey;

View file

@ -45,7 +45,7 @@ public class LegacyGroupStore {
if (g instanceof Storage.GroupV1 g1) { if (g instanceof Storage.GroupV1 g1) {
final var members = g1.members.stream().map(m -> { final var members = g1.members.stream().map(m -> {
if (m.recipientId == null) { 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)); m.number));
} }

View file

@ -43,7 +43,9 @@ public class LegacyProfileStore {
if (node.isArray()) { if (node.isArray()) {
for (var entry : node) { for (var entry : node) {
var name = entry.hasNonNull("name") ? entry.get("name").asText() : null; 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); final var address = new RecipientAddress(serviceId, name);
ProfileKey profileKey = null; ProfileKey profileKey = null;
try { try {

View file

@ -36,7 +36,7 @@ public class LegacyRecipientStore {
if (node.isArray()) { if (node.isArray()) {
for (var recipient : node) { for (var recipient : node) {
var recipientName = recipient.get("name").asText(); 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)); addresses.add(new RecipientAddress(serviceId, recipientName));
} }
} }

View file

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

View file

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

View file

@ -8,59 +8,60 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Optional; import java.util.Optional;
public record RecipientAddress( 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. * Construct a RecipientAddress.
* *
* @param serviceId The ACI or PNI of the user, if available. * @param aci The ACI of the user, if available.
* @param number The phone number 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 { public RecipientAddress {
if (serviceId.isPresent() && serviceId.get().isUnknown()) { if (aci.isPresent() && aci.get().isUnknown()) {
serviceId = Optional.empty(); aci = Optional.empty();
} }
if (pni.isPresent() && pni.get().isUnknown()) { if (pni.isPresent() && pni.get().isUnknown()) {
pni = Optional.empty(); pni = Optional.empty();
} }
if (serviceId.isEmpty() && pni.isPresent()) { if (aci.isEmpty() && pni.isEmpty() && number.isEmpty()) {
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()) {
throw new AssertionError("Must have either a ServiceId or E164 number!"); throw new AssertionError("Must have either a ServiceId or E164 number!");
} }
} }
public RecipientAddress(Optional<ServiceId> serviceId, Optional<String> 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) { public RecipientAddress(ACI aci, String e164) {
this(Optional.ofNullable(serviceId), Optional.empty(), Optional.ofNullable(e164), Optional.empty()); this(Optional.ofNullable(aci), Optional.empty(), Optional.ofNullable(e164), Optional.empty());
} }
public RecipientAddress(ServiceId serviceId, PNI pni, String e164) { public RecipientAddress(String e164) {
this(Optional.ofNullable(serviceId), Optional.ofNullable(pni), Optional.ofNullable(e164), Optional.empty()); this(Optional.empty(), Optional.empty(), Optional.ofNullable(e164), Optional.empty());
} }
public RecipientAddress(ServiceId serviceId, PNI pni, String e164, String username) { public RecipientAddress(ACI aci, PNI pni, String e164) {
this(Optional.ofNullable(serviceId), 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(pni),
Optional.ofNullable(e164), Optional.ofNullable(e164),
Optional.ofNullable(username)); Optional.ofNullable(username));
} }
public RecipientAddress(SignalServiceAddress address) { 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) { public RecipientAddress(org.asamk.signal.manager.api.RecipientAddress address) {
@ -72,30 +73,28 @@ public record RecipientAddress(
} }
public RecipientAddress withIdentifiersFrom(RecipientAddress address) { public RecipientAddress withIdentifiersFrom(RecipientAddress address) {
return new RecipientAddress(( return new RecipientAddress(address.aci.or(this::aci),
this.serviceId.isEmpty() || this.isServiceIdPNI() || this.serviceId.equals(address.pni)
) && !address.isServiceIdPNI() ? address.serviceId : this.serviceId,
address.pni.or(this::pni), address.pni.or(this::pni),
address.number.or(this::number), address.number.or(this::number),
address.username.or(this::username)); address.username.or(this::username));
} }
public RecipientAddress removeIdentifiersFrom(RecipientAddress address) { public RecipientAddress removeIdentifiersFrom(RecipientAddress address) {
return new RecipientAddress(address.serviceId.equals(this.serviceId) || address.pni.equals(this.serviceId) return new RecipientAddress(address.aci.equals(this.aci) ? Optional.empty() : this.aci,
? Optional.empty() address.pni.equals(this.pni) ? Optional.empty() : this.pni,
: this.serviceId,
address.pni.equals(this.pni) || address.serviceId.equals(this.pni) ? Optional.empty() : this.pni,
address.number.equals(this.number) ? Optional.empty() : this.number, address.number.equals(this.number) ? Optional.empty() : this.number,
address.username.equals(this.username) ? Optional.empty() : this.username); address.username.equals(this.username) ? Optional.empty() : this.username);
} }
public Optional<ACI> aci() { public Optional<ServiceId> serviceId() {
return serviceId.map(s -> s instanceof ServiceId.ACI aci ? aci : null); return aci.map(aci -> (ServiceId) aci).or(this::pni);
} }
public String getIdentifier() { public String getIdentifier() {
if (serviceId.isPresent()) { if (aci.isPresent()) {
return serviceId.get().toString(); return aci.get().toString();
} else if (pni.isPresent()) {
return pni.get().toString();
} else if (number.isPresent()) { } else if (number.isPresent()) {
return number.get(); return number.get();
} else { } else {
@ -106,38 +105,31 @@ public record RecipientAddress(
public String getLegacyIdentifier() { public String getLegacyIdentifier() {
if (number.isPresent()) { if (number.isPresent()) {
return number.get(); return number.get();
} else if (serviceId.isPresent()) { } else if (aci.isPresent()) {
return serviceId.get().toString(); return aci.get().toString();
} else if (pni.isPresent()) {
return pni.get().toString();
} else { } else {
throw new AssertionError("Given the checks in the constructor, this should not be possible."); throw new AssertionError("Given the checks in the constructor, this should not be possible.");
} }
} }
public boolean matches(RecipientAddress other) { public boolean matches(RecipientAddress other) {
return (serviceId.isPresent() && other.serviceId.isPresent() && serviceId.get().equals(other.serviceId.get())) return (aci.isPresent() && other.aci.isPresent() && aci.get().equals(other.aci.get())) || (
|| (
pni.isPresent() && other.serviceId.isPresent() && pni.get().equals(other.serviceId.get())
)
|| (
serviceId.isPresent() && other.pni.isPresent() && serviceId.get().equals(other.pni.get())
)
|| (
pni.isPresent() && other.pni.isPresent() && pni.get().equals(other.pni.get()) pni.isPresent() && other.pni.isPresent() && pni.get().equals(other.pni.get())
) ) || (
|| (
number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get()) number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get())
); );
} }
public boolean hasSingleIdentifier() { public boolean hasSingleIdentifier() {
final var identifiersCount = serviceId().map(s -> 1).orElse(0) final var identifiersCount = aci().map(s -> 1).orElse(0) + pni().map(s -> 1).orElse(0) + number().map(s -> 1)
+ number().map(s -> 1).orElse(0) .orElse(0) + username().map(s -> 1).orElse(0);
+ username().map(s -> 1).orElse(0);
return identifiersCount == 1; return identifiersCount == 1;
} }
public boolean hasIdentifiersOf(RecipientAddress address) { 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.pni.isEmpty() || address.pni.equals(pni))
&& (address.number.isEmpty() || address.number.equals(number)) && (address.number.isEmpty() || address.number.equals(number))
&& (address.username.isEmpty() || address.username.equals(username)); && (address.username.isEmpty() || address.username.equals(username));
@ -145,13 +137,12 @@ public record RecipientAddress(
public boolean hasAdditionalIdentifiersThan(RecipientAddress address) { public boolean hasAdditionalIdentifiersThan(RecipientAddress address) {
return ( return (
serviceId.isPresent() && ( aci.isPresent() && (
address.serviceId.isEmpty() || ( address.aci.isEmpty() || !address.aci.equals(aci)
!address.serviceId.equals(serviceId) && !address.pni.equals(serviceId)
)
) )
) || ( ) || (
pni.isPresent() && !address.serviceId.equals(pni) && ( pni.isPresent() && (
address.pni.isEmpty() || !address.pni.equals(pni) address.pni.isEmpty() || !address.pni.equals(pni)
) )
) || ( ) || (
@ -166,19 +157,15 @@ public record RecipientAddress(
} }
public boolean hasOnlyPniAndNumber() { public boolean hasOnlyPniAndNumber() {
return pni.isPresent() && serviceId.equals(pni) && number.isPresent(); return pni.isPresent() && aci.isEmpty() && number.isPresent();
}
public boolean isServiceIdPNI() {
return serviceId.isPresent() && (pni.isPresent() && serviceId.equals(pni));
} }
public SignalServiceAddress toSignalServiceAddress() { 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() { 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(), number(),
username()); 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.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.StorageId; import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; 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 Logger logger = LoggerFactory.getLogger(RecipientStore.class);
private static final String TABLE_RECIPIENT = "recipient"; 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 RecipientMergeHandler recipientMergeHandler;
private final SelfAddressProvider selfAddressProvider; private final SelfAddressProvider selfAddressProvider;
@ -63,20 +62,23 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
storage_record BLOB, storage_record BLOB,
number TEXT UNIQUE, number TEXT UNIQUE,
username TEXT UNIQUE, username TEXT UNIQUE,
uuid BLOB UNIQUE, aci TEXT UNIQUE,
pni BLOB UNIQUE, pni TEXT UNIQUE,
unregistered_timestamp INTEGER, unregistered_timestamp INTEGER,
profile_key BLOB, profile_key BLOB,
profile_key_credential BLOB, profile_key_credential BLOB,
given_name TEXT, given_name TEXT,
family_name TEXT, family_name TEXT,
nick_name TEXT,
color TEXT, color TEXT,
expiration_time INTEGER NOT NULL DEFAULT 0, expiration_time INTEGER NOT NULL DEFAULT 0,
mute_until INTEGER NOT NULL DEFAULT 0,
blocked INTEGER NOT NULL DEFAULT FALSE, blocked INTEGER NOT NULL DEFAULT FALSE,
archived INTEGER NOT NULL DEFAULT FALSE, archived INTEGER NOT NULL DEFAULT FALSE,
profile_sharing 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, hidden INTEGER NOT NULL DEFAULT FALSE,
profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0, 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) { public RecipientAddress resolveRecipientAddress(RecipientId recipientId) {
final var sql = ( final var sql = (
""" """
SELECT r.number, r.uuid, r.pni, r.username SELECT r.number, r.aci, r.pni, r.username
FROM %s r FROM %s r
WHERE r._id = ? WHERE r._id = ?
""" """
@ -310,8 +312,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public RecipientId resolveRecipientTrusted( public RecipientId resolveRecipientTrusted(
final Optional<ACI> aci, final Optional<PNI> pni, final Optional<String> number 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(aci, pni, number, Optional.empty()));
return resolveRecipientTrusted(new RecipientAddress(serviceId, pni, number, Optional.empty()));
} }
@Override @Override
@ -341,9 +342,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public List<Pair<RecipientId, Contact>> getContacts() { public List<Pair<RecipientId, Contact>> getContacts() {
final var sql = ( 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 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); ).formatted(TABLE_RECIPIENT, SQL_IS_CONTACT);
try (final var connection = database.getConnection()) { try (final var connection = database.getConnection()) {
@ -363,9 +364,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = ( final var sql = (
""" """
SELECT r._id, 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.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.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 r.storage_record
FROM %s r FROM %s r
@ -382,9 +383,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = ( final var sql = (
""" """
SELECT r._id, 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.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.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 r.storage_record
FROM %s r FROM %s r
@ -417,16 +418,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = ( final var sql = (
""" """
SELECT r._id, 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.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.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 r.storage_record
FROM %s r 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)); ).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 connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
if (blocked.isPresent()) { if (blocked.isPresent()) {
@ -436,7 +437,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
return result.filter(r -> name.isEmpty() || ( return result.filter(r -> name.isEmpty() || (
r.getContact() != null && name.get().equals(r.getContact().getName()) r.getContact() != null && name.get().equals(r.getContact().getName())
) || (r.getProfile() != null && name.get().equals(r.getProfile().getDisplayName()))).map(r -> { ) || (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) return Recipient.newBuilder(r)
.withProfileKey(selfProfileKeyProvider.getSelfProfileKey()) .withProfileKey(selfProfileKeyProvider.getSelfProfileKey())
.build(); .build();
@ -482,21 +483,21 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
public Map<ServiceId, ProfileKey> getServiceIdToProfileKeyMap() { public Map<ServiceId, ProfileKey> getServiceIdToProfileKeyMap() {
final var sql = ( final var sql = (
""" """
SELECT r.uuid, r.profile_key SELECT r.aci, r.profile_key
FROM %s r 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); ).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 connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
return Utils.executeQueryForStream(statement, resultSet -> { return Utils.executeQueryForStream(statement, resultSet -> {
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid")); final var aci = ACI.parseOrThrow(resultSet.getString("aci"));
if (serviceId.equals(selfServiceId)) { if (aci.equals(selfAci)) {
return new Pair<>(serviceId, selfProfileKeyProvider.getSelfProfileKey()); return new Pair<>(aci, selfProfileKeyProvider.getSelfProfileKey());
} }
final var profileKey = getProfileKeyFromResultSet(resultSet); 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)); }).filter(Objects::nonNull).collect(Collectors.toMap(Pair::first, Pair::second));
} }
} catch (SQLException e) { } catch (SQLException e) {
@ -509,7 +510,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
""" """
SELECT r._id SELECT r._id
FROM %s r 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); ).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) { 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 { public List<StorageId> getStorageIds(Connection connection) throws SQLException {
final var sql = """ final var sql = """
SELECT r.storage_id 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); """.formatted(TABLE_RECIPIENT);
final var selfRecipientId = resolveRecipient(connection, selfAddressProvider.getSelfAddress()); final var selfRecipientId = resolveRecipient(connection, selfAddressProvider.getSelfAddress());
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
@ -767,7 +768,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
long start = System.nanoTime(); long start = System.nanoTime();
final var sql = ( final var sql = (
""" """
INSERT INTO %s (_id, number, uuid) INSERT INTO %s (_id, number, aci)
VALUES (?, ?, ?) VALUES (?, ?, ?)
""" """
).formatted(TABLE_RECIPIENT); ).formatted(TABLE_RECIPIENT);
@ -780,12 +781,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
for (final var recipient : recipients.values()) { for (final var recipient : recipients.values()) {
statement.setLong(1, recipient.getRecipientId().id()); statement.setLong(1, recipient.getRecipientId().id());
statement.setString(2, recipient.getAddress().number().orElse(null)); statement.setString(2, recipient.getAddress().number().orElse(null));
statement.setBytes(3, statement.setString(3, recipient.getAddress().aci().map(ACI::toString).orElse(null));
recipient.getAddress()
.serviceId()
.map(ServiceId::getRawUuid)
.map(UuidUtil::toByteArray)
.orElse(null));
statement.executeUpdate(); statement.executeUpdate();
} }
} }
@ -829,19 +825,27 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = ( final var sql = (
""" """
UPDATE %s 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 = ? WHERE _id = ?
""" """
).formatted(TABLE_RECIPIENT); ).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, contact == null ? null : contact.givenName()); statement.setString(1, contact == null ? null : contact.givenName());
statement.setString(2, contact == null ? null : contact.familyName()); statement.setString(2, contact == null ? null : contact.familyName());
statement.setInt(3, contact == null ? 0 : contact.messageExpirationTime()); statement.setString(3, contact == null ? null : contact.nickName());
statement.setBoolean(4, contact != null && contact.isProfileSharingEnabled()); statement.setInt(4, contact == null ? 0 : contact.messageExpirationTime());
statement.setString(5, contact == null ? null : contact.color()); statement.setLong(5, contact == null ? 0 : contact.muteUntil());
statement.setBoolean(6, contact != null && contact.isBlocked()); statement.setBoolean(6, contact != null && contact.hideStory());
statement.setBoolean(7, contact != null && contact.isArchived()); statement.setBoolean(7, contact != null && contact.isProfileSharingEnabled());
statement.setLong(8, recipientId.id()); 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(); statement.executeUpdate();
} }
rotateStorageId(connection, recipientId); rotateStorageId(connection, recipientId);
@ -1055,12 +1059,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private RecipientId resolveRecipientLocked( private RecipientId resolveRecipientLocked(
Connection connection, RecipientAddress address Connection connection, RecipientAddress address
) throws SQLException { ) throws SQLException {
final var aci = address.aci().isEmpty() final var byAci = address.aci().isEmpty()
? Optional.<RecipientWithAddress>empty() ? Optional.<RecipientWithAddress>empty()
: findByServiceId(connection, address.aci().get()); : findByServiceId(connection, address.aci().get());
if (aci.isPresent()) { if (byAci.isPresent()) {
return aci.get().id(); return byAci.get().id();
} }
final var byPni = address.pni().isEmpty() final var byPni = address.pni().isEmpty()
@ -1104,7 +1108,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
if (recipient.isEmpty()) { if (recipient.isEmpty()) {
logger.debug("Got new recipient, number is unknown"); 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(); return recipient.get().id();
@ -1115,15 +1119,15 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
) throws SQLException { ) throws SQLException {
final var sql = ( final var sql = (
""" """
INSERT INTO %s (number, uuid, pni, username) INSERT INTO %s (number, aci, pni, username)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
RETURNING _id RETURNING _id
""" """
).formatted(TABLE_RECIPIENT); ).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, address.number().orElse(null)); statement.setString(1, address.number().orElse(null));
statement.setBytes(2, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); statement.setString(2, address.aci().map(ACI::toString).orElse(null));
statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); statement.setString(3, address.pni().map(PNI::toString).orElse(null));
statement.setString(4, address.username().orElse(null)); statement.setString(4, address.username().orElse(null));
final var generatedKey = Utils.executeQueryForOptional(statement, Utils::getIdMapper); final var generatedKey = Utils.executeQueryForOptional(statement, Utils::getIdMapper);
if (generatedKey.isPresent()) { if (generatedKey.isPresent()) {
@ -1142,7 +1146,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = ( final var sql = (
""" """
UPDATE %s 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 = ? WHERE _id = ?
""" """
).formatted(TABLE_RECIPIENT); ).formatted(TABLE_RECIPIENT);
@ -1161,14 +1165,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = ( final var sql = (
""" """
UPDATE %s UPDATE %s
SET number = ?, uuid = ?, pni = ?, username = ? SET number = ?, aci = ?, pni = ?, username = ?
WHERE _id = ? WHERE _id = ?
""" """
).formatted(TABLE_RECIPIENT); ).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, address.number().orElse(null)); statement.setString(1, address.number().orElse(null));
statement.setBytes(2, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); statement.setString(2, address.aci().map(ACI::toString).orElse(null));
statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); statement.setString(3, address.pni().map(PNI::toString).orElse(null));
statement.setString(4, address.username().orElse(null)); statement.setString(4, address.username().orElse(null));
statement.setLong(5, recipientId.id()); statement.setLong(5, recipientId.id());
statement.executeUpdate(); statement.executeUpdate();
@ -1225,7 +1229,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final Connection connection, final String number final Connection connection, final String number
) throws SQLException { ) throws SQLException {
final var sql = """ 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 FROM %s r
WHERE r.number = ? WHERE r.number = ?
LIMIT 1 LIMIT 1
@ -1240,7 +1244,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final Connection connection, final String username final Connection connection, final String username
) throws SQLException { ) throws SQLException {
final var sql = """ 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 FROM %s r
WHERE r.username = ? WHERE r.username = ?
LIMIT 1 LIMIT 1
@ -1259,13 +1263,13 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
return recipientWithAddress; return recipientWithAddress;
} }
final var sql = """ 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 FROM %s r
WHERE %s = ?1 WHERE %s = ?1
LIMIT 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)) { 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 = Utils.executeQueryForOptional(statement, this::getRecipientWithAddressFromResultSet);
recipientWithAddress.ifPresent(r -> recipientAddressCache.put(serviceId, r)); recipientWithAddress.ifPresent(r -> recipientAddressCache.put(serviceId, r));
return recipientWithAddress; return recipientWithAddress;
@ -1276,16 +1280,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final Connection connection, final RecipientAddress address final Connection connection, final RecipientAddress address
) throws SQLException { ) throws SQLException {
final var sql = """ 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 FROM %s r
WHERE r.uuid = ?1 OR WHERE r.aci = ?1 OR
r.pni = ?2 OR r.pni = ?2 OR
r.number = ?3 OR r.number = ?3 OR
r.username = ?4 r.username = ?4
""".formatted(TABLE_RECIPIENT); """.formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) { try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); statement.setString(1, address.aci().map(ServiceId::toString).orElse(null));
statement.setBytes(2, address.pni().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); statement.setString(2, address.pni().map(ServiceId::toString).orElse(null));
statement.setString(3, address.number().orElse(null)); statement.setString(3, address.number().orElse(null));
statement.setString(4, address.username().orElse(null)); statement.setString(4, address.username().orElse(null));
return Utils.executeQueryForStream(statement, this::getRecipientWithAddressFromResultSet) 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 { private Contact getContact(final Connection connection, final RecipientId recipientId) throws SQLException {
final var sql = ( 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 FROM %s r
WHERE r._id = ? AND (%s) WHERE r._id = ? AND (%s)
""" """
@ -1357,13 +1361,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
} }
private RecipientAddress getRecipientAddressFromResultSet(ResultSet resultSet) throws SQLException { private RecipientAddress getRecipientAddressFromResultSet(ResultSet resultSet) throws SQLException {
final var pni = Optional.ofNullable(resultSet.getBytes("pni")).map(UuidUtil::parseOrNull).map(PNI::from); final var aci = Optional.ofNullable(resultSet.getString("aci")).map(ACI::parseOrThrow);
final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull); final var pni = Optional.ofNullable(resultSet.getString("pni")).map(PNI::parseOrThrow);
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 number = Optional.ofNullable(resultSet.getString("number")); final var number = Optional.ofNullable(resultSet.getString("number"));
final var username = Optional.ofNullable(resultSet.getString("username")); 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 { 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 { private Contact getContactFromResultSet(ResultSet resultSet) throws SQLException {
final var unregisteredTimestamp = resultSet.getLong("unregistered_timestamp");
return new Contact(resultSet.getString("given_name"), return new Contact(resultSet.getString("given_name"),
resultSet.getString("family_name"), resultSet.getString("family_name"),
resultSet.getString("nick_name"),
resultSet.getString("color"), resultSet.getString("color"),
resultSet.getInt("expiration_time"), resultSet.getInt("expiration_time"),
resultSet.getLong("mute_until"),
resultSet.getBoolean("hide_story"),
resultSet.getBoolean("blocked"), resultSet.getBoolean("blocked"),
resultSet.getBoolean("archived"), resultSet.getBoolean("archived"),
resultSet.getBoolean("profile_sharing"), resultSet.getBoolean("profile_sharing"),
resultSet.getBoolean("hidden")); resultSet.getBoolean("hidden"),
unregisteredTimestamp == 0 ? null : unregisteredTimestamp);
} }
private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException { 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 profileShared = contact != null && contact.isProfileSharingEnabled();
final var archived = contact != null && contact.isArchived(); final var archived = contact != null && contact.isArchived();
final var hidden = contact != null && contact.isHidden(); 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 contactGivenName = contact == null ? null : contact.givenName();
final var contactFamilyName = contact == null ? null : contact.familyName(); final var contactFamilyName = contact == null ? null : contact.familyName();
final var contactNickName = contact == null ? null : contact.nickName();
if (blocked != contactRecord.isBlocked() if (blocked != contactRecord.isBlocked()
|| profileShared != contactRecord.isProfileSharingEnabled() || profileShared != contactRecord.isProfileSharingEnabled()
|| archived != contactRecord.isArchived() || archived != contactRecord.isArchived()
|| hidden != contactRecord.isHidden() || hidden != contactRecord.isHidden()
|| hideStory != contactRecord.shouldHideStory()
|| muteUntil != contactRecord.getMuteUntil()
|| unregisteredTimestamp != contactRecord.getUnregisteredTimestamp()
|| !Objects.equals(contactRecord.getSystemGivenName().orElse(null), contactGivenName) || !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); logger.debug("Storing new or updated contact {}", recipientId);
final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
final var newContact = contactBuilder.withIsBlocked(contactRecord.isBlocked()) final var newContact = contactBuilder.withIsBlocked(contactRecord.isBlocked())
.withIsProfileSharingEnabled(contactRecord.isProfileSharingEnabled()) .withIsProfileSharingEnabled(contactRecord.isProfileSharingEnabled())
.withIsArchived(contactRecord.isArchived()) .withIsArchived(contactRecord.isArchived())
.withIsHidden(contactRecord.isHidden()) .withIsHidden(contactRecord.isHidden())
.withMuteUntil(contactRecord.getMuteUntil())
.withHideStory(contactRecord.shouldHideStory())
.withGivenName(contactRecord.getSystemGivenName().orElse(null)) .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()); account.getRecipientStore().storeContact(connection, recipientId, newContact.build());
} }

View file

@ -94,9 +94,15 @@ public final class StorageSyncModels {
} }
if (recipient.getContact() != null) { if (recipient.getContact() != null) {
builder.setSystemGivenName(recipient.getContact().givenName()) builder.setSystemGivenName(recipient.getContact().givenName())
.setSystemFamilyName((recipient.getContact().familyName())) .setSystemFamilyName(recipient.getContact().familyName())
.setSystemNickname(recipient.getContact().nickName())
.setBlocked(recipient.getContact().isBlocked()) .setBlocked(recipient.getContact().isBlocked())
.setProfileSharingEnabled(recipient.getContact().isProfileSharingEnabled()) .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()) .setArchived(recipient.getContact().isArchived())
.setHidden(recipient.getContact().isHidden()); .setHidden(recipient.getContact().isHidden());
} }

View file

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

View file

@ -3,7 +3,7 @@ package org.asamk.signal.manager.storage.recipients;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import java.util.Arrays; import java.util.Arrays;
@ -17,8 +17,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
class MergeRecipientHelperTest { class MergeRecipientHelperTest {
static final ServiceId SERVICE_ID_A = ServiceId.ACI.from(UUID.randomUUID()); static final ACI ACI_A = ACI.from(UUID.randomUUID());
static final ServiceId SERVICE_ID_B = ServiceId.ACI.from(UUID.randomUUID()); static final ACI ACI_B = ACI.from(UUID.randomUUID());
static final PNI PNI_A = PNI.from(UUID.randomUUID()); static final PNI PNI_A = PNI.from(UUID.randomUUID());
static final PNI PNI_B = PNI.from(UUID.randomUUID()); static final PNI PNI_B = PNI.from(UUID.randomUUID());
static final String NUMBER_A = "+AAA"; static final String NUMBER_A = "+AAA";
@ -26,8 +26,8 @@ class MergeRecipientHelperTest {
static final String USERNAME_A = "USER.1"; static final String USERNAME_A = "USER.1";
static final String USERNAME_B = "USER.2"; static final String USERNAME_B = "USER.2";
static final PartialAddresses ADDR_A = new PartialAddresses(SERVICE_ID_A, PNI_A, NUMBER_A, USERNAME_A); static final PartialAddresses ADDR_A = new PartialAddresses(ACI_A, PNI_A, NUMBER_A, USERNAME_A);
static final PartialAddresses ADDR_B = new PartialAddresses(SERVICE_ID_B, PNI_B, NUMBER_B, USERNAME_B); static final PartialAddresses ADDR_B = new PartialAddresses(ACI_B, PNI_B, NUMBER_B, USERNAME_B);
static final T[] testInstancesNone = new T[]{ static final T[] testInstancesNone = new T[]{
new T(Set.of(), ADDR_A.FULL, Set.of(rec(1000000, ADDR_A.FULL))), new T(Set.of(), ADDR_A.FULL, Set.of(rec(1000000, ADDR_A.FULL))),
@ -53,9 +53,7 @@ class MergeRecipientHelperTest {
new T(Set.of(rec(1, ADDR_A.PNI)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.PNI), rec(1000000, ADDR_A.ACI_NUM))), new T(Set.of(rec(1, ADDR_A.PNI)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.PNI), rec(1000000, ADDR_A.ACI_NUM))),
new T(Set.of(rec(1, ADDR_A.NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.ACI_NUM))), new T(Set.of(rec(1, ADDR_A.NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.ACI_NUM))),
new T(Set.of(rec(1, ADDR_A.ACI_NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.ACI_NUM))), new T(Set.of(rec(1, ADDR_A.ACI_NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.ACI_NUM))),
new T(Set.of(rec(1, ADDR_A.PNI_NUM)), new T(Set.of(rec(1, ADDR_A.PNI_NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.FULL))),
ADDR_A.ACI_NUM,
Set.of(rec(1, ADDR_A.PNI), rec(1000000, ADDR_A.ACI_NUM))),
new T(Set.of(rec(1, ADDR_A.ACI_PNI)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.FULL))), new T(Set.of(rec(1, ADDR_A.ACI_PNI)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.FULL))),
new T(Set.of(rec(1, ADDR_A.FULL)), ADDR_A.PNI_NUM, Set.of(rec(1, ADDR_A.FULL))), new T(Set.of(rec(1, ADDR_A.FULL)), ADDR_A.PNI_NUM, Set.of(rec(1, ADDR_A.FULL))),
@ -186,7 +184,7 @@ class MergeRecipientHelperTest {
@Override @Override
public String toString() { public String toString() {
return "T{#input=%s, request=%s_%s_%s, #output=%s}".formatted(input.size(), return "T{#input=%s, request=%s_%s_%s, #output=%s}".formatted(input.size(),
request.serviceId().isPresent() ? "SVI" : "", request.aci().isPresent() ? "ACI" : "",
request.pni().isPresent() ? "PNI" : "", request.pni().isPresent() ? "PNI" : "",
request.number().isPresent() ? "NUM" : "", request.number().isPresent() ? "NUM" : "",
output.size()); output.size());
@ -245,17 +243,17 @@ class MergeRecipientHelperTest {
RecipientAddress ACI_USERNAME RecipientAddress ACI_USERNAME
) { ) {
PartialAddresses(ServiceId serviceId, PNI pni, String number, String username) { PartialAddresses(ACI aci, PNI pni, String number, String username) {
this(new RecipientAddress(serviceId, pni, number), this(new RecipientAddress(aci, pni, number),
new RecipientAddress(serviceId, pni, number, username), new RecipientAddress(aci, pni, number, username),
new RecipientAddress(serviceId, null, null), new RecipientAddress(aci, null, null),
new RecipientAddress(null, pni, null), new RecipientAddress(null, pni, null),
new RecipientAddress(null, null, number), new RecipientAddress(null, null, number),
new RecipientAddress(serviceId, null, number), new RecipientAddress(aci, null, number),
new RecipientAddress(serviceId, null, number, username), new RecipientAddress(aci, null, number, username),
new RecipientAddress(null, pni, number), new RecipientAddress(null, pni, number),
new RecipientAddress(serviceId, pni, null), new RecipientAddress(aci, pni, null),
new RecipientAddress(serviceId, null, null, username)); new RecipientAddress(aci, null, null, username));
} }
} }
} }

View file

@ -670,7 +670,18 @@ public class DbusManagerImpl implements Manager {
} }
return Recipient.newBuilder() return Recipient.newBuilder()
.withAddress(new RecipientAddress(null, n)) .withAddress(new RecipientAddress(null, n))
.withContact(new Contact(contactName, null, null, 0, contactBlocked, false, false, false)) .withContact(new Contact(contactName,
null,
null,
null,
0,
0,
false,
contactBlocked,
false,
false,
false,
null))
.build(); .build();
}).filter(Objects::nonNull).toList(); }).filter(Objects::nonNull).toList();
} }