Store serviceId in protocol stores as TEXT

ServiceIds are no longer just UUIDs, they can now have prefixes like "PNI:"
This commit is contained in:
AsamK 2023-09-23 11:51:50 +02:00
parent a7744e837c
commit b2a32666e9
17 changed files with 287 additions and 144 deletions

View file

@ -2,6 +2,7 @@ package org.asamk.signal.manager.storage;
import com.zaxxer.hikari.HikariDataSource;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.storage.groups.GroupStore;
import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
import org.asamk.signal.manager.storage.prekeys.KyberPreKeyStore;
@ -15,16 +16,21 @@ import org.asamk.signal.manager.storage.sessions.SessionStore;
import org.asamk.signal.manager.storage.stickers.StickerStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.File;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
public class AccountDatabase extends Database {
private final static Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
private static final long DATABASE_VERSION = 14;
private static final long DATABASE_VERSION = 15;
private AccountDatabase(final HikariDataSource dataSource) {
super(logger, DATABASE_VERSION, dataSource);
@ -346,7 +352,146 @@ 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();
}
}
statement.executeUpdate("""
CREATE TABLE identity2 (
_id INTEGER PRIMARY KEY,
address TEXT UNIQUE NOT NULL,
identity_key BLOB NOT NULL,
added_timestamp INTEGER NOT NULL,
trust_level INTEGER NOT NULL
) STRICT;
INSERT INTO identity2 (_id, address, identity_key, added_timestamp, trust_level)
SELECT i._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = i.uuid) address, i.identity_key, i.added_timestamp, i.trust_level
FROM identity i
WHERE address IS NOT NULL;
DROP TABLE identity;
ALTER TABLE identity2 RENAME TO identity;
CREATE TABLE message_send_log2 (
_id INTEGER PRIMARY KEY,
content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE,
address TEXT NOT NULL,
device_id INTEGER NOT NULL
) STRICT;
INSERT INTO message_send_log2 (_id, content_id, address, device_id)
SELECT m._id, m.content_id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = m.uuid) address, m.device_id
FROM message_send_log m
WHERE address IS NOT NULL;
DROP INDEX msl_recipient_index;
DROP INDEX msl_content_index;
DROP TABLE message_send_log;
ALTER TABLE message_send_log2 RENAME TO message_send_log;
CREATE INDEX msl_recipient_index ON message_send_log (address, device_id, content_id);
CREATE INDEX msl_content_index ON message_send_log (content_id);
CREATE TABLE sender_key2 (
_id INTEGER PRIMARY KEY,
address TEXT NOT NULL,
device_id INTEGER NOT NULL,
distribution_id BLOB NOT NULL,
record BLOB NOT NULL,
created_timestamp INTEGER NOT NULL,
UNIQUE(address, device_id, distribution_id)
) STRICT;
INSERT INTO sender_key2 (_id, address, device_id, distribution_id, record, created_timestamp)
SELECT s._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = s.uuid) address, s.device_id, s.distribution_id, s.record, s.created_timestamp
FROM sender_key s
WHERE address IS NOT NULL;
DROP TABLE sender_key;
ALTER TABLE sender_key2 RENAME TO sender_key;
CREATE TABLE sender_key_shared2 (
_id INTEGER PRIMARY KEY,
address TEXT NOT NULL,
device_id INTEGER NOT NULL,
distribution_id BLOB NOT NULL,
timestamp INTEGER NOT NULL,
UNIQUE(address, device_id, distribution_id)
) STRICT;
INSERT INTO sender_key_shared2 (_id, address, device_id, distribution_id, timestamp)
SELECT s._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = s.uuid) address, s.device_id, s.distribution_id, s.timestamp
FROM sender_key_shared s
WHERE address IS NOT NULL;
DROP TABLE sender_key_shared;
ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
CREATE TABLE session2 (
_id INTEGER PRIMARY KEY,
account_id_type INTEGER NOT NULL,
address TEXT NOT NULL,
device_id INTEGER NOT NULL,
record BLOB NOT NULL,
UNIQUE(account_id_type, address, device_id)
) STRICT;
INSERT INTO session2 (_id, account_id_type, address, device_id, record)
SELECT s._id, s.account_id_type, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = s.uuid) address, s.device_id, s.record
FROM session s
WHERE address IS NOT NULL;
DROP TABLE session;
ALTER TABLE session2 RENAME TO session;
DROP TABLE tmp_mapping_table;
""");
}
}
}
}

View file

@ -1841,8 +1841,7 @@ public class SignalAccount implements Closeable {
public SignalIdentityKeyStore getIdentityKeyStore() {
return getOrCreate(() -> identityKeyStore,
() -> identityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
() -> identityKeyPair,
() -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
localRegistrationId,
SignalAccount.this.getIdentityKeyStore()));
}

View file

@ -56,7 +56,7 @@ public class Utils {
return node;
}
public static RecipientAddress getRecipientAddressFromIdentifier(final String identifier) {
public static RecipientAddress getRecipientAddressFromLegacyIdentifier(final String identifier) {
if (UuidUtil.isUuid(identifier)) {
return new RecipientAddress(ServiceId.parseOrThrow(identifier));
} else {

View file

@ -6,26 +6,26 @@ import org.whispersystems.signalservice.api.push.ServiceId;
public class IdentityInfo {
private final String name;
private final String address;
private final IdentityKey identityKey;
private final TrustLevel trustLevel;
private final long addedTimestamp;
IdentityInfo(
final String name, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp
final String address, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp
) {
this.name = name;
this.address = address;
this.identityKey = identityKey;
this.trustLevel = trustLevel;
this.addedTimestamp = addedTimestamp;
}
public ServiceId getServiceId() {
return ServiceId.parseOrThrow(name);
return ServiceId.parseOrThrow(address);
}
public String getName() {
return name;
public String getAddress() {
return address;
}
public IdentityKey getIdentityKey() {

View file

@ -37,7 +37,7 @@ public class IdentityKeyStore {
statement.executeUpdate("""
CREATE TABLE identity (
_id INTEGER PRIMARY KEY,
uuid BLOB UNIQUE NOT NULL,
address TEXT UNIQUE NOT NULL,
identity_key BLOB NOT NULL,
added_timestamp INTEGER NOT NULL,
trust_level INTEGER NOT NULL
@ -56,18 +56,22 @@ public class IdentityKeyStore {
}
public boolean saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) {
return saveIdentity(serviceId.toString(), identityKey);
}
boolean saveIdentity(final String address, final IdentityKey identityKey) {
if (isRetryingDecryption) {
return false;
}
try (final var connection = database.getConnection()) {
final var identityInfo = loadIdentity(connection, serviceId);
final var identityInfo = loadIdentity(connection, address);
if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) {
// Identity already exists, not updating the trust level
logger.trace("Not storing new identity for recipient {}, identity already stored", serviceId);
logger.trace("Not storing new identity for recipient {}, identity already stored", address);
return false;
}
saveNewIdentity(connection, serviceId, identityKey, identityInfo == null);
saveNewIdentity(connection, address, identityKey, identityInfo == null);
return true;
} catch (SQLException e) {
throw new RuntimeException("Failed update identity store", e);
@ -80,7 +84,8 @@ public class IdentityKeyStore {
public boolean setIdentityTrustLevel(ServiceId serviceId, IdentityKey identityKey, TrustLevel trustLevel) {
try (final var connection = database.getConnection()) {
final var identityInfo = loadIdentity(connection, serviceId);
final var address = serviceId.toString();
final var identityInfo = loadIdentity(connection, address);
if (identityInfo == null) {
logger.debug("Not updating trust level for recipient {}, identity not found", serviceId);
return false;
@ -95,7 +100,7 @@ public class IdentityKeyStore {
}
logger.debug("Updating trust level for recipient {} with trust {}", serviceId, trustLevel);
final var newIdentityInfo = new IdentityInfo(serviceId,
final var newIdentityInfo = new IdentityInfo(address,
identityKey,
trustLevel,
identityInfo.getDateAddedTimestamp());
@ -107,31 +112,35 @@ public class IdentityKeyStore {
}
public boolean isTrustedIdentity(ServiceId serviceId, IdentityKey identityKey, Direction direction) {
return isTrustedIdentity(serviceId.toString(), identityKey, direction);
}
public boolean isTrustedIdentity(String address, IdentityKey identityKey, Direction direction) {
if (trustNewIdentity == TrustNewIdentity.ALWAYS) {
return true;
}
try (final var connection = database.getConnection()) {
// TODO implement possibility for different handling of incoming/outgoing trust decisions
var identityInfo = loadIdentity(connection, serviceId);
var identityInfo = loadIdentity(connection, address);
if (identityInfo == null) {
logger.debug("Initial identity found for {}, saving.", serviceId);
saveNewIdentity(connection, serviceId, identityKey, true);
identityInfo = loadIdentity(connection, serviceId);
logger.debug("Initial identity found for {}, saving.", address);
saveNewIdentity(connection, address, identityKey, true);
identityInfo = loadIdentity(connection, address);
} else if (!identityInfo.getIdentityKey().equals(identityKey)) {
// Identity found, but different
if (direction == Direction.SENDING) {
logger.debug("Changed identity found for {}, saving.", serviceId);
saveNewIdentity(connection, serviceId, identityKey, false);
identityInfo = loadIdentity(connection, serviceId);
logger.debug("Changed identity found for {}, saving.", address);
saveNewIdentity(connection, address, identityKey, false);
identityInfo = loadIdentity(connection, address);
} else {
logger.trace("Trusting identity for {} for {}: {}", serviceId, direction, false);
logger.trace("Trusting identity for {} for {}: {}", address, direction, false);
return false;
}
}
final var isTrusted = identityInfo != null && identityInfo.isTrusted();
logger.trace("Trusting identity for {} for {}: {}", serviceId, direction, isTrusted);
logger.trace("Trusting identity for {} for {}: {}", address, direction, isTrusted);
return isTrusted;
} catch (SQLException e) {
throw new RuntimeException("Failed read from identity store", e);
@ -139,8 +148,12 @@ public class IdentityKeyStore {
}
public IdentityInfo getIdentityInfo(ServiceId serviceId) {
return getIdentityInfo(serviceId.toString());
}
public IdentityInfo getIdentityInfo(String address) {
try (final var connection = database.getConnection()) {
return loadIdentity(connection, serviceId);
return loadIdentity(connection, address);
} catch (SQLException e) {
throw new RuntimeException("Failed read from identity store", e);
}
@ -150,7 +163,7 @@ public class IdentityKeyStore {
try (final var connection = database.getConnection()) {
final var sql = (
"""
SELECT i.uuid, i.identity_key, i.added_timestamp, i.trust_level
SELECT i.address, i.identity_key, i.added_timestamp, i.trust_level
FROM %s AS i
"""
).formatted(TABLE_IDENTITY);
@ -166,7 +179,7 @@ public class IdentityKeyStore {
public void deleteIdentity(final ServiceId serviceId) {
try (final var connection = database.getConnection()) {
deleteIdentity(connection, serviceId);
deleteIdentity(connection, serviceId.toString());
} catch (SQLException e) {
throw new RuntimeException("Failed update identity store", e);
}
@ -188,34 +201,37 @@ public class IdentityKeyStore {
}
private IdentityInfo loadIdentity(
final Connection connection, final ServiceId serviceId
final Connection connection, final String address
) throws SQLException {
final var sql = (
"""
SELECT i.uuid, i.identity_key, i.added_timestamp, i.trust_level
SELECT i.address, i.identity_key, i.added_timestamp, i.trust_level
FROM %s AS i
WHERE i.uuid = ?
WHERE i.address = ?
"""
).formatted(TABLE_IDENTITY);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, address);
return Utils.executeQueryForOptional(statement, this::getIdentityInfoFromResultSet).orElse(null);
}
}
private void saveNewIdentity(
final Connection connection,
final ServiceId serviceId,
final String address,
final IdentityKey identityKey,
final boolean firstIdentity
) throws SQLException {
final var trustLevel = trustNewIdentity == TrustNewIdentity.ALWAYS || (
trustNewIdentity == TrustNewIdentity.ON_FIRST_USE && firstIdentity
) ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
logger.debug("Storing new identity for recipient {} with trust {}", serviceId, trustLevel);
final var newIdentityInfo = new IdentityInfo(serviceId, identityKey, trustLevel, System.currentTimeMillis());
logger.debug("Storing new identity for recipient {} with trust {}", address, trustLevel);
final var newIdentityInfo = new IdentityInfo(address, identityKey, trustLevel, System.currentTimeMillis());
storeIdentity(connection, newIdentityInfo);
identityChanges.onNext(serviceId);
final var serviceId = ServiceId.parseOrNull(address);
if (serviceId != null) {
identityChanges.onNext(serviceId);
}
}
private void storeIdentity(final Connection connection, final IdentityInfo identityInfo) throws SQLException {
@ -225,12 +241,12 @@ public class IdentityKeyStore {
identityInfo.getDateAddedTimestamp());
final var sql = (
"""
INSERT OR REPLACE INTO %s (uuid, identity_key, added_timestamp, trust_level)
INSERT OR REPLACE INTO %s (address, identity_key, added_timestamp, trust_level)
VALUES (?, ?, ?, ?)
"""
).formatted(TABLE_IDENTITY);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, identityInfo.getServiceId().toByteArray());
statement.setString(1, identityInfo.getAddress());
statement.setBytes(2, identityInfo.getIdentityKey().serialize());
statement.setLong(3, identityInfo.getDateAddedTimestamp());
statement.setInt(4, identityInfo.getTrustLevel().ordinal());
@ -238,27 +254,27 @@ public class IdentityKeyStore {
}
}
private void deleteIdentity(final Connection connection, final ServiceId serviceId) throws SQLException {
private void deleteIdentity(final Connection connection, final String address) throws SQLException {
final var sql = (
"""
DELETE FROM %s AS i
WHERE i.uuid = ?
WHERE i.address = ?
"""
).formatted(TABLE_IDENTITY);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, address);
statement.executeUpdate();
}
}
private IdentityInfo getIdentityInfoFromResultSet(ResultSet resultSet) throws SQLException {
try {
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
final var address = resultSet.getString("address");
final var id = new IdentityKey(resultSet.getBytes("identity_key"));
final var trustLevel = TrustLevel.fromInt(resultSet.getInt("trust_level"));
final var added = resultSet.getLong("added_timestamp");
return new IdentityInfo(serviceId, id, trustLevel, added);
return new IdentityInfo(address, id, trustLevel, added);
} catch (InvalidKeyException e) {
logger.warn("Failed to load identity key, resetting: {}", e.getMessage());
return null;

View file

@ -84,7 +84,7 @@ public class LegacyIdentityKeyStore {
var added = storage.addedTimestamp();
final var serviceId = address.serviceId().get();
return new IdentityInfo(serviceId, id, trustLevel, added);
return new IdentityInfo(serviceId.toString(), id, trustLevel, added);
} catch (IOException | InvalidKeyException e) {
logger.warn("Failed to load identity key: {}", e.getMessage());
return null;

View file

@ -1,10 +1,8 @@
package org.asamk.signal.manager.storage.identities;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.function.Supplier;
@ -15,7 +13,6 @@ public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.sta
private final IdentityKeyStore identityKeyStore;
public SignalIdentityKeyStore(
final RecipientResolver resolver,
final Supplier<IdentityKeyPair> identityKeyPairSupplier,
final int localRegistrationId,
final IdentityKeyStore identityKeyStore
@ -37,22 +34,17 @@ public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.sta
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
final var serviceId = ServiceId.parseOrThrow(address.getName());
return identityKeyStore.saveIdentity(serviceId, identityKey);
return identityKeyStore.saveIdentity(address.getName(), identityKey);
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
final var serviceId = ServiceId.parseOrThrow(address.getName());
return identityKeyStore.isTrustedIdentity(serviceId, identityKey, direction);
return identityKeyStore.isTrustedIdentity(address.getName(), identityKey, direction);
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
final var serviceId = ServiceId.parseOrThrow(address.getName());
final var identityInfo = identityKeyStore.getIdentityInfo(serviceId);
final var identityInfo = identityKeyStore.getIdentityInfo(address.getName());
return identityInfo == null ? null : identityInfo.getIdentityKey();
}
}

View file

@ -100,7 +100,7 @@ public class LegacyJsonIdentityKeyStore {
? UuidUtil.parseOrNull(trustedKey.get("uuid").asText())
: null;
final var address = uuid == null
? Utils.getRecipientAddressFromIdentifier(trustedKeyName)
? Utils.getRecipientAddressFromLegacyIdentifier(trustedKeyName)
: new RecipientAddress(ACI.from(uuid), trustedKeyName);
try {
var id = new IdentityKey(Base64.getDecoder().decode(trustedKey.get("identityKey").asText()), 0);

View file

@ -47,7 +47,7 @@ public class LegacyJsonSessionStore {
var uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
final var address = uuid == null
? Utils.getRecipientAddressFromIdentifier(sessionName)
? Utils.getRecipientAddressFromLegacyIdentifier(sessionName)
: new RecipientAddress(ACI.from(uuid), sessionName);
final var deviceId = session.get("deviceId").asInt();
final var record = Base64.getDecoder().decode(session.get("record").asText());

View file

@ -151,8 +151,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
@Override
public RecipientId resolveRecipient(final String identifier) {
if (UuidUtil.isUuid(identifier)) {
return resolveRecipient(ServiceId.parseOrThrow(identifier));
final var serviceId = ServiceId.parseOrNull(identifier);
if (serviceId != null) {
return resolveRecipient(serviceId);
} else {
return resolveRecipientByNumber(identifier);
}

View file

@ -67,7 +67,7 @@ public class MessageSendLogStore implements AutoCloseable {
CREATE TABLE message_send_log (
_id INTEGER PRIMARY KEY,
content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE,
uuid BLOB NOT NULL,
address TEXT NOT NULL,
device_id INTEGER NOT NULL
) STRICT;
CREATE TABLE message_send_log_content (
@ -79,7 +79,7 @@ public class MessageSendLogStore implements AutoCloseable {
urgent INTEGER NOT NULL
) STRICT;
CREATE INDEX mslc_timestamp_index ON message_send_log_content (timestamp);
CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
CREATE INDEX msl_recipient_index ON message_send_log (address, device_id, content_id);
CREATE INDEX msl_content_index ON message_send_log (content_id);
""");
}
@ -92,13 +92,13 @@ public class MessageSendLogStore implements AutoCloseable {
SELECT group_id, content, content_hint, urgent
FROM %s l
INNER JOIN %s lc ON l.content_id = lc._id
WHERE l.uuid = ? AND l.device_id = ? AND lc.timestamp = ?
WHERE l.address = ? AND l.device_id = ? AND lc.timestamp = ?
""".formatted(TABLE_MESSAGE_SEND_LOG, TABLE_MESSAGE_SEND_LOG_CONTENT);
try (final var connection = database.getConnection()) {
deleteOutdatedEntries(connection);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, serviceId.toString());
statement.setInt(2, deviceId);
statement.setLong(3, timestamp);
try (var result = Utils.executeQueryForStream(statement, this::getMessageSendLogEntryFromResultSet)) {
@ -202,13 +202,13 @@ public class MessageSendLogStore implements AutoCloseable {
public void deleteEntryForRecipientNonGroup(long sentTimestamp, ServiceId serviceId) {
final var sql = """
DELETE FROM %s AS lc
WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.uuid = ?)
WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.address = ?)
""".formatted(TABLE_MESSAGE_SEND_LOG_CONTENT, TABLE_MESSAGE_SEND_LOG);
try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
try (final var statement = connection.prepareStatement(sql)) {
statement.setLong(1, sentTimestamp);
statement.setBytes(2, serviceId.toByteArray());
statement.setString(2, serviceId.toString());
statement.executeUpdate();
}
@ -226,14 +226,14 @@ public class MessageSendLogStore implements AutoCloseable {
public void deleteEntriesForRecipient(List<Long> sentTimestamps, ServiceId serviceId, int deviceId) {
final var sql = """
DELETE FROM %s AS l
WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.uuid = ? AND l.device_id = ?
WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.address = ? AND l.device_id = ?
""".formatted(TABLE_MESSAGE_SEND_LOG, TABLE_MESSAGE_SEND_LOG_CONTENT);
try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
try (final var statement = connection.prepareStatement(sql)) {
for (final var sentTimestamp : sentTimestamps) {
statement.setLong(1, sentTimestamp);
statement.setBytes(2, serviceId.toByteArray());
statement.setString(2, serviceId.toString());
statement.setInt(3, deviceId);
statement.executeUpdate();
}
@ -342,13 +342,13 @@ public class MessageSendLogStore implements AutoCloseable {
final long contentId, final List<RecipientDevices> recipientDevices, final Connection connection
) throws SQLException {
final var sql = """
INSERT INTO %s (uuid, device_id, content_id)
INSERT INTO %s (address, device_id, content_id)
VALUES (?,?,?)
""".formatted(TABLE_MESSAGE_SEND_LOG);
try (final var statement = connection.prepareStatement(sql)) {
for (final var recipientDevice : recipientDevices) {
for (final var deviceId : recipientDevice.deviceIds()) {
statement.setBytes(1, recipientDevice.serviceId().toByteArray());
statement.setString(1, recipientDevice.serviceId().toString());
statement.setInt(2, deviceId);
statement.setLong(3, contentId);
statement.executeUpdate();

View file

@ -41,7 +41,9 @@ public class LegacySenderKeyRecordStore {
if (record == null || serviceId.isEmpty()) {
return null;
}
return new Pair<>(new SenderKeyRecordStore.Key(serviceId.get(), key.deviceId, key.distributionId), record);
return new Pair<>(new SenderKeyRecordStore.Key(serviceId.get().toString(),
key.deviceId,
key.distributionId), record);
}).filter(Objects::nonNull).toList();
senderKeyStore.addLegacySenderKeys(senderKeys);

View file

@ -40,7 +40,7 @@ public class LegacySenderKeySharedStore {
if (serviceId.isEmpty()) {
continue;
}
final var entry = new SenderKeySharedEntry(serviceId.get(), senderKey.deviceId);
final var entry = new SenderKeySharedEntry(serviceId.get().toString(), senderKey.deviceId);
final var distributionId = DistributionId.from(senderKey.distributionId);
var entries = sharedSenderKeys.get(distributionId);
if (entries == null) {

View file

@ -31,12 +31,12 @@ public class SenderKeyRecordStore implements SenderKeyStore {
statement.executeUpdate("""
CREATE TABLE sender_key (
_id INTEGER PRIMARY KEY,
uuid BLOB NOT NULL,
address TEXT NOT NULL,
device_id INTEGER NOT NULL,
distribution_id BLOB NOT NULL,
record BLOB NOT NULL,
created_timestamp INTEGER NOT NULL,
UNIQUE(uuid, device_id, distribution_id)
UNIQUE(address, device_id, distribution_id)
) STRICT;
""");
}
@ -75,12 +75,12 @@ public class SenderKeyRecordStore implements SenderKeyStore {
"""
SELECT s.created_timestamp
FROM %s AS s
WHERE s.uuid = ? AND s.device_id = ? AND s.distribution_id = ?
WHERE s.address = ? AND s.device_id = ? AND s.distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, selfServiceId.toByteArray());
statement.setString(1, selfServiceId.toString());
statement.setInt(2, selfDeviceId);
statement.setBytes(3, UuidUtil.toByteArray(distributionId));
return Utils.executeQueryForOptional(statement, res -> res.getLong("created_timestamp")).orElse(-1L);
@ -94,12 +94,12 @@ public class SenderKeyRecordStore implements SenderKeyStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE s.uuid = ? AND s.distribution_id = ?
WHERE s.address = ? AND s.distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, serviceId.toString());
statement.setBytes(2, UuidUtil.toByteArray(distributionId));
statement.executeUpdate();
}
@ -145,8 +145,7 @@ public class SenderKeyRecordStore implements SenderKeyStore {
}
private Key getKey(final SignalProtocolAddress address, final UUID distributionId) {
final var serviceId = ServiceId.parseOrThrow(address.getName());
return new Key(serviceId, address.getDeviceId(), distributionId);
return new Key(address.getName(), address.getDeviceId(), distributionId);
}
private SenderKeyRecord loadSenderKey(final Connection connection, final Key key) throws SQLException {
@ -154,11 +153,11 @@ public class SenderKeyRecordStore implements SenderKeyStore {
"""
SELECT s.record
FROM %s AS s
WHERE s.uuid = ? AND s.device_id = ? AND s.distribution_id = ?
WHERE s.address = ? AND s.device_id = ? AND s.distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, key.serviceId().toByteArray());
statement.setString(1, key.address());
statement.setInt(2, key.deviceId());
statement.setBytes(3, UuidUtil.toByteArray(key.distributionId()));
return Utils.executeQueryForOptional(statement, this::getSenderKeyRecordFromResultSet).orElse(null);
@ -171,11 +170,11 @@ public class SenderKeyRecordStore implements SenderKeyStore {
final var sqlUpdate = """
UPDATE %s
SET record = ?
WHERE uuid = ? AND device_id = ? and distribution_id = ?
WHERE address = ? AND device_id = ? and distribution_id = ?
""".formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sqlUpdate)) {
statement.setBytes(1, senderKeyRecord.serialize());
statement.setBytes(2, key.serviceId().toByteArray());
statement.setString(2, key.address());
statement.setLong(3, key.deviceId());
statement.setBytes(4, UuidUtil.toByteArray(key.distributionId()));
final var rows = statement.executeUpdate();
@ -187,12 +186,12 @@ public class SenderKeyRecordStore implements SenderKeyStore {
// Record doesn't exist yet, creating a new one
final var sqlInsert = (
"""
INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, record, created_timestamp)
INSERT OR REPLACE INTO %s (address, device_id, distribution_id, record, created_timestamp)
VALUES (?, ?, ?, ?, ?)
"""
).formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sqlInsert)) {
statement.setBytes(1, key.serviceId().toByteArray());
statement.setString(1, key.address());
statement.setInt(2, key.deviceId());
statement.setBytes(3, UuidUtil.toByteArray(key.distributionId()));
statement.setBytes(4, senderKeyRecord.serialize());
@ -205,11 +204,11 @@ public class SenderKeyRecordStore implements SenderKeyStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE s.uuid = ?
WHERE s.address = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, serviceId.toString());
statement.executeUpdate();
}
}
@ -225,5 +224,5 @@ public class SenderKeyRecordStore implements SenderKeyStore {
}
}
record Key(ServiceId serviceId, int deviceId, UUID distributionId) {}
record Key(String address, int deviceId, UUID distributionId) {}
}

View file

@ -30,11 +30,11 @@ public class SenderKeySharedStore {
statement.executeUpdate("""
CREATE TABLE sender_key_shared (
_id INTEGER PRIMARY KEY,
uuid BLOB NOT NULL,
address TEXT NOT NULL,
device_id INTEGER NOT NULL,
distribution_id BLOB NOT NULL,
timestamp INTEGER NOT NULL,
UNIQUE(uuid, device_id, distribution_id)
UNIQUE(address, device_id, distribution_id)
) STRICT;
""");
}
@ -48,7 +48,7 @@ public class SenderKeySharedStore {
try (final var connection = database.getConnection()) {
final var sql = (
"""
SELECT s.uuid, s.device_id
SELECT s.address, s.device_id
FROM %s AS s
WHERE s.distribution_id = ?
"""
@ -56,7 +56,7 @@ public class SenderKeySharedStore {
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, UuidUtil.toByteArray(distributionId.asUuid()));
return Utils.executeQueryForStream(statement, this::getSenderKeySharedEntryFromResultSet)
.map(k -> k.serviceId.toProtocolAddress(k.deviceId()))
.map(k -> new SignalProtocolAddress(k.address, k.deviceId()))
.collect(Collectors.toSet());
}
} catch (SQLException e) {
@ -68,7 +68,7 @@ public class SenderKeySharedStore {
final DistributionId distributionId, final Collection<SignalProtocolAddress> addresses
) {
final var newEntries = addresses.stream()
.map(a -> new SenderKeySharedEntry(ServiceId.parseOrThrow(a.getName()), a.getDeviceId()))
.map(a -> new SenderKeySharedEntry(a.getName(), a.getDeviceId()))
.collect(Collectors.toSet());
try (final var connection = database.getConnection()) {
@ -82,8 +82,7 @@ public class SenderKeySharedStore {
public void clearSenderKeySharedWith(final Collection<SignalProtocolAddress> addresses) {
final var entriesToDelete = addresses.stream()
.filter(a -> UuidUtil.isUuid(a.getName()))
.map(a -> new SenderKeySharedEntry(ServiceId.parseOrThrow(a.getName()), a.getDeviceId()))
.map(a -> new SenderKeySharedEntry(a.getName(), a.getDeviceId()))
.collect(Collectors.toSet());
try (final var connection = database.getConnection()) {
@ -91,12 +90,12 @@ public class SenderKeySharedStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE uuid = ? AND device_id = ?
WHERE address = ? AND device_id = ?
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
for (final var entry : entriesToDelete) {
statement.setBytes(1, entry.serviceId().toByteArray());
statement.setString(1, entry.address());
statement.setInt(2, entry.deviceId());
statement.executeUpdate();
}
@ -127,11 +126,11 @@ public class SenderKeySharedStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE uuid = ?
WHERE address = ?
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, serviceId.toString());
statement.executeUpdate();
}
} catch (SQLException e) {
@ -146,11 +145,11 @@ public class SenderKeySharedStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE uuid = ? AND device_id = ? AND distribution_id = ?
WHERE address = ? AND device_id = ? AND distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, serviceId.toByteArray());
statement.setString(1, serviceId.toString());
statement.setInt(2, deviceId);
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
statement.executeUpdate();
@ -197,13 +196,13 @@ public class SenderKeySharedStore {
) throws SQLException {
final var sql = (
"""
INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, timestamp)
INSERT OR REPLACE INTO %s (address, device_id, distribution_id, timestamp)
VALUES (?, ?, ?, ?)
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
for (final var entry : newEntries) {
statement.setBytes(1, entry.serviceId().toByteArray());
statement.setString(1, entry.toString());
statement.setInt(2, entry.deviceId());
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
statement.setLong(4, System.currentTimeMillis());
@ -213,10 +212,10 @@ public class SenderKeySharedStore {
}
private SenderKeySharedEntry getSenderKeySharedEntryFromResultSet(ResultSet resultSet) throws SQLException {
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
final var address = resultSet.getString("address");
final var deviceId = resultSet.getInt("device_id");
return new SenderKeySharedEntry(serviceId, deviceId);
return new SenderKeySharedEntry(address, deviceId);
}
record SenderKeySharedEntry(ServiceId serviceId, int deviceId) {}
record SenderKeySharedEntry(String address, int deviceId) {}
}

View file

@ -37,7 +37,7 @@ public class LegacySessionStore {
if (record == null || serviceId.isEmpty()) {
return null;
}
return new Pair<>(new SessionStore.Key(serviceId.get(), key.deviceId()), record);
return new Pair<>(new SessionStore.Key(serviceId.get().toString(), key.deviceId()), record);
}).filter(Objects::nonNull).toList();
sessionStore.addLegacySessions(sessions);
deleteAllSessions(sessionsPath);

View file

@ -6,15 +6,12 @@ import org.asamk.signal.manager.storage.Utils;
import org.signal.libsignal.protocol.NoSessionException;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.protocol.message.CiphertextMessage;
import org.signal.libsignal.protocol.state.SessionRecord;
import org.signal.libsignal.protocol.util.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceSessionStore;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
import java.sql.ResultSet;
@ -45,10 +42,10 @@ public class SessionStore implements SignalServiceSessionStore {
CREATE TABLE session (
_id INTEGER PRIMARY KEY,
account_id_type INTEGER NOT NULL,
uuid BLOB NOT NULL,
address TEXT NOT NULL,
device_id INTEGER NOT NULL,
record BLOB NOT NULL,
UNIQUE(account_id_type, uuid, device_id)
UNIQUE(account_id_type, address, device_id)
) STRICT;
""");
}
@ -107,13 +104,13 @@ public class SessionStore implements SignalServiceSessionStore {
"""
SELECT s.device_id
FROM %s AS s
WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id != 1
WHERE s.account_id_type = ? AND s.address = ? AND s.device_id != 1
"""
).formatted(TABLE_SESSION);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
statement.setBytes(2, serviceId.toByteArray());
statement.setString(2, serviceId.toString());
return Utils.executeQueryForStream(statement, res -> res.getInt("device_id")).toList();
}
} catch (SQLException e) {
@ -122,7 +119,7 @@ public class SessionStore implements SignalServiceSessionStore {
}
public boolean isCurrentRatchetKey(ServiceId serviceId, int deviceId, ECPublicKey ratchetKey) {
final var key = new Key(serviceId, deviceId);
final var key = new Key(serviceId.toString(), deviceId);
try (final var connection = database.getConnection()) {
final var session = loadSession(connection, key);
@ -177,7 +174,7 @@ public class SessionStore implements SignalServiceSessionStore {
public void deleteAllSessions(ServiceId serviceId) {
try (final var connection = database.getConnection()) {
deleteAllSessions(connection, serviceId);
deleteAllSessions(connection, serviceId.toString());
} catch (SQLException e) {
throw new RuntimeException("Failed update session store", e);
}
@ -185,10 +182,6 @@ public class SessionStore implements SignalServiceSessionStore {
@Override
public void archiveSession(final SignalProtocolAddress address) {
if (!UuidUtil.isUuid(address.getName())) {
return;
}
final var key = getKey(address);
try (final var connection = database.getConnection()) {
@ -207,15 +200,13 @@ public class SessionStore implements SignalServiceSessionStore {
@Override
public Set<SignalProtocolAddress> getAllAddressesWithActiveSessions(final List<String> addressNames) {
final var serviceIdsCommaSeparated = addressNames.stream()
.map(ServiceId::parseOrThrow)
.map(ServiceId::toByteArray)
.map(uuid -> "x'" + Hex.toStringCondensed(uuid) + "'")
.map(address -> "'" + address.replaceAll("'", "''") + "'")
.collect(Collectors.joining(","));
final var sql = (
"""
SELECT s.uuid, s.device_id, s.record
SELECT s.address, s.device_id, s.record
FROM %s AS s
WHERE s.account_id_type = ? AND s.uuid IN (%s)
WHERE s.account_id_type = ? AND s.address IN (%s)
"""
).formatted(TABLE_SESSION, serviceIdsCommaSeparated);
try (final var connection = database.getConnection()) {
@ -225,7 +216,7 @@ public class SessionStore implements SignalServiceSessionStore {
res -> new Pair<>(getKeyFromResultSet(res), getSessionRecordFromResultSet(res)))
.filter(pair -> isActive(pair.second()))
.map(Pair::first)
.map(key -> key.serviceId().toProtocolAddress(key.deviceId()))
.map(key -> new SignalProtocolAddress(key.address(), key.deviceId()))
.collect(Collectors.toSet());
}
} catch (SQLException e) {
@ -236,7 +227,7 @@ public class SessionStore implements SignalServiceSessionStore {
public void archiveAllSessions() {
final var sql = (
"""
SELECT s.uuid, s.device_id, s.record
SELECT s.address, s.device_id, s.record
FROM %s AS s
WHERE s.account_id_type = ?
"""
@ -264,9 +255,9 @@ public class SessionStore implements SignalServiceSessionStore {
public void archiveSessions(final ServiceId serviceId) {
final var sql = (
"""
SELECT s.uuid, s.device_id, s.record
SELECT s.address, s.device_id, s.record
FROM %s AS s
WHERE s.account_id_type = ? AND s.uuid = ?
WHERE s.account_id_type = ? AND s.address = ?
"""
).formatted(TABLE_SESSION);
try (final var connection = database.getConnection()) {
@ -274,7 +265,7 @@ public class SessionStore implements SignalServiceSessionStore {
final List<Pair<Key, SessionRecord>> records;
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
statement.setBytes(2, serviceId.toByteArray());
statement.setString(2, serviceId.toString());
records = Utils.executeQueryForStream(statement,
res -> new Pair<>(getKeyFromResultSet(res), getSessionRecordFromResultSet(res)))
.filter(Objects::nonNull)
@ -306,8 +297,7 @@ public class SessionStore implements SignalServiceSessionStore {
}
private Key getKey(final SignalProtocolAddress address) {
final var serviceId = ServiceId.parseOrThrow(address.getName());
return new Key(serviceId, address.getDeviceId());
return new Key(address.getName(), address.getDeviceId());
}
private SessionRecord loadSession(Connection connection, final Key key) throws SQLException {
@ -321,21 +311,21 @@ public class SessionStore implements SignalServiceSessionStore {
"""
SELECT s.record
FROM %s AS s
WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id = ?
WHERE s.account_id_type = ? AND s.address = ? AND s.device_id = ?
"""
).formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
statement.setBytes(2, key.serviceId().toByteArray());
statement.setString(2, key.address());
statement.setInt(3, key.deviceId());
return Utils.executeQueryForOptional(statement, this::getSessionRecordFromResultSet).orElse(null);
}
}
private Key getKeyFromResultSet(ResultSet resultSet) throws SQLException {
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
final var address = resultSet.getString("address");
final var deviceId = resultSet.getInt("device_id");
return new Key(serviceId, deviceId);
return new Key(address, deviceId);
}
private SessionRecord getSessionRecordFromResultSet(ResultSet resultSet) throws SQLException {
@ -356,19 +346,19 @@ public class SessionStore implements SignalServiceSessionStore {
}
final var sql = """
INSERT OR REPLACE INTO %s (account_id_type, uuid, device_id, record)
INSERT OR REPLACE INTO %s (account_id_type, address, device_id, record)
VALUES (?, ?, ?, ?)
""".formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
statement.setBytes(2, key.serviceId().toByteArray());
statement.setString(2, key.address());
statement.setInt(3, key.deviceId());
statement.setBytes(4, session.serialize());
statement.executeUpdate();
}
}
private void deleteAllSessions(final Connection connection, final ServiceId serviceId) throws SQLException {
private void deleteAllSessions(final Connection connection, final String address) throws SQLException {
synchronized (cachedSessions) {
cachedSessions.clear();
}
@ -376,12 +366,12 @@ public class SessionStore implements SignalServiceSessionStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE s.account_id_type = ? AND s.uuid = ?
WHERE s.account_id_type = ? AND s.address = ?
"""
).formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
statement.setBytes(2, serviceId.toByteArray());
statement.setString(2, address);
statement.executeUpdate();
}
}
@ -394,12 +384,12 @@ public class SessionStore implements SignalServiceSessionStore {
final var sql = (
"""
DELETE FROM %s AS s
WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id = ?
WHERE s.account_id_type = ? AND s.address = ? AND s.device_id = ?
"""
).formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
statement.setBytes(2, key.serviceId().toByteArray());
statement.setString(2, key.address());
statement.setInt(3, key.deviceId());
statement.executeUpdate();
}
@ -409,5 +399,5 @@ public class SessionStore implements SignalServiceSessionStore {
return record != null && record.hasSenderChain();
}
record Key(ServiceId serviceId, int deviceId) {}
record Key(String address, int deviceId) {}
}