mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Recreate recipient database with aci column
This commit is contained in:
parent
90df256e85
commit
76fe6ad799
17 changed files with 375 additions and 239 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.asamk.signal.manager.storage.recipients;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
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 java.util.Arrays;
|
||||
|
@ -17,8 +17,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
class MergeRecipientHelperTest {
|
||||
|
||||
static final ServiceId SERVICE_ID_A = ServiceId.ACI.from(UUID.randomUUID());
|
||||
static final ServiceId SERVICE_ID_B = ServiceId.ACI.from(UUID.randomUUID());
|
||||
static final ACI ACI_A = 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_B = PNI.from(UUID.randomUUID());
|
||||
static final String NUMBER_A = "+AAA";
|
||||
|
@ -26,8 +26,8 @@ class MergeRecipientHelperTest {
|
|||
static final String USERNAME_A = "USER.1";
|
||||
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_B = new PartialAddresses(SERVICE_ID_B, PNI_B, NUMBER_B, USERNAME_B);
|
||||
static final PartialAddresses ADDR_A = new PartialAddresses(ACI_A, PNI_A, NUMBER_A, USERNAME_A);
|
||||
static final PartialAddresses ADDR_B = new PartialAddresses(ACI_B, PNI_B, NUMBER_B, USERNAME_B);
|
||||
|
||||
static final T[] testInstancesNone = new T[]{
|
||||
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.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)),
|
||||
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_NUM)), 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))),
|
||||
|
@ -186,7 +184,7 @@ class MergeRecipientHelperTest {
|
|||
@Override
|
||||
public String toString() {
|
||||
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.number().isPresent() ? "NUM" : "",
|
||||
output.size());
|
||||
|
@ -245,17 +243,17 @@ class MergeRecipientHelperTest {
|
|||
RecipientAddress ACI_USERNAME
|
||||
) {
|
||||
|
||||
PartialAddresses(ServiceId serviceId, PNI pni, String number, String username) {
|
||||
this(new RecipientAddress(serviceId, pni, number),
|
||||
new RecipientAddress(serviceId, pni, number, username),
|
||||
new RecipientAddress(serviceId, null, null),
|
||||
PartialAddresses(ACI aci, PNI pni, String number, String username) {
|
||||
this(new RecipientAddress(aci, pni, number),
|
||||
new RecipientAddress(aci, pni, number, username),
|
||||
new RecipientAddress(aci, null, null),
|
||||
new RecipientAddress(null, pni, null),
|
||||
new RecipientAddress(null, null, number),
|
||||
new RecipientAddress(serviceId, null, number),
|
||||
new RecipientAddress(serviceId, null, number, username),
|
||||
new RecipientAddress(aci, null, number),
|
||||
new RecipientAddress(aci, null, number, username),
|
||||
new RecipientAddress(null, pni, number),
|
||||
new RecipientAddress(serviceId, pni, null),
|
||||
new RecipientAddress(serviceId, null, null, username));
|
||||
new RecipientAddress(aci, pni, null),
|
||||
new RecipientAddress(aci, null, null, username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue