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;
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);
""");
}
}
+ }
+ 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;
+ """);
+ }
}
}
}
public SignalIdentityKeyStore getIdentityKeyStore() {
return getOrCreate(() -> identityKeyStore,
- () -> identityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
- () -> identityKeyPair,
+ () -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
localRegistrationId,
SignalAccount.this.getIdentityKeyStore()));
}
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 {
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() {
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
}
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);
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;
}
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());
}
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);
}
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);
}
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);
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);
}
}
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 {
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());
}
}
- 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;
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;
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;
private final IdentityKeyStore identityKeyStore;
public SignalIdentityKeyStore(
- final RecipientResolver resolver,
final Supplier<IdentityKeyPair> identityKeyPairSupplier,
final int localRegistrationId,
final IdentityKeyStore identityKeyStore
@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();
}
}
? 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);
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());
@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);
}
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 (
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);
""");
}
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)) {
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();
}
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();
}
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();
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);
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) {
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;
""");
}
"""
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);
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();
}
}
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 {
"""
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);
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();
// 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());
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();
}
}
}
}
- record Key(ServiceId serviceId, int deviceId, UUID distributionId) {}
+ record Key(String address, int deviceId, UUID distributionId) {}
}
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;
""");
}
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 = ?
"""
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) {
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()) {
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()) {
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();
}
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) {
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();
) 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());
}
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) {}
}
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);
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;
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;
""");
}
"""
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) {
}
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);
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);
}
@Override
public void archiveSession(final SignalProtocolAddress address) {
- if (!UuidUtil.isUuid(address.getName())) {
- return;
- }
-
final var key = getKey(address);
try (final var connection = database.getConnection()) {
@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()) {
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) {
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 = ?
"""
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()) {
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)
}
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 {
"""
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 {
}
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();
}
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();
}
}
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();
}
return record != null && record.hasSenderChain();
}
- record Key(ServiceId serviceId, int deviceId) {}
+ record Key(String address, int deviceId) {}
}