1 package org
.asamk
.signal
.manager
.storage
.senderKeys
;
3 import org
.asamk
.signal
.manager
.storage
.Database
;
4 import org
.asamk
.signal
.manager
.storage
.Utils
;
5 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
6 import org
.slf4j
.Logger
;
7 import org
.slf4j
.LoggerFactory
;
8 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
9 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
10 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
12 import java
.sql
.Connection
;
13 import java
.sql
.ResultSet
;
14 import java
.sql
.SQLException
;
15 import java
.util
.Collection
;
18 import java
.util
.stream
.Collectors
;
20 public class SenderKeySharedStore
{
22 private final static Logger logger
= LoggerFactory
.getLogger(SenderKeySharedStore
.class);
23 private final static String TABLE_SENDER_KEY_SHARED
= "sender_key_shared";
25 private final Database database
;
27 public static void createSql(Connection connection
) throws SQLException
{
28 // When modifying the CREATE statement here, also add a migration in AccountDatabase.java
29 try (final var statement
= connection
.createStatement()) {
30 statement
.executeUpdate("""
31 CREATE TABLE sender_key_shared (
32 _id INTEGER PRIMARY KEY,
34 device_id INTEGER NOT NULL,
35 distribution_id BLOB NOT NULL,
36 timestamp INTEGER NOT NULL,
37 UNIQUE(uuid, device_id, distribution_id)
43 SenderKeySharedStore(final Database database
) {
44 this.database
= database
;
47 public Set
<SignalProtocolAddress
> getSenderKeySharedWith(final DistributionId distributionId
) {
48 try (final var connection
= database
.getConnection()) {
51 SELECT s.uuid, s.device_id
53 WHERE s.distribution_id = ?
55 ).formatted(TABLE_SENDER_KEY_SHARED
);
56 try (final var statement
= connection
.prepareStatement(sql
)) {
57 statement
.setBytes(1, UuidUtil
.toByteArray(distributionId
.asUuid()));
58 return Utils
.executeQueryForStream(statement
, this::getSenderKeySharedEntryFromResultSet
)
59 .map(k
-> k
.serviceId
.toProtocolAddress(k
.deviceId()))
60 .collect(Collectors
.toSet());
62 } catch (SQLException e
) {
63 throw new RuntimeException("Failed read from shared sender key store", e
);
67 public void markSenderKeySharedWith(
68 final DistributionId distributionId
, final Collection
<SignalProtocolAddress
> addresses
70 final var newEntries
= addresses
.stream()
71 .map(a
-> new SenderKeySharedEntry(ServiceId
.parseOrThrow(a
.getName()), a
.getDeviceId()))
72 .collect(Collectors
.toSet());
74 try (final var connection
= database
.getConnection()) {
75 connection
.setAutoCommit(false);
76 markSenderKeysSharedWith(connection
, distributionId
, newEntries
);
78 } catch (SQLException e
) {
79 throw new RuntimeException("Failed update shared sender key store", e
);
83 public void clearSenderKeySharedWith(final Collection
<SignalProtocolAddress
> addresses
) {
84 final var entriesToDelete
= addresses
.stream()
85 .filter(a
-> UuidUtil
.isUuid(a
.getName()))
86 .map(a
-> new SenderKeySharedEntry(ServiceId
.parseOrThrow(a
.getName()), a
.getDeviceId()))
87 .collect(Collectors
.toSet());
89 try (final var connection
= database
.getConnection()) {
90 connection
.setAutoCommit(false);
94 WHERE uuid = ? AND device_id = ?
96 ).formatted(TABLE_SENDER_KEY_SHARED
);
97 try (final var statement
= connection
.prepareStatement(sql
)) {
98 for (final var entry
: entriesToDelete
) {
99 statement
.setBytes(1, entry
.serviceId().toByteArray());
100 statement
.setInt(2, entry
.deviceId());
101 statement
.executeUpdate();
105 } catch (SQLException e
) {
106 throw new RuntimeException("Failed update shared sender key store", e
);
110 public void deleteAll() {
111 try (final var connection
= database
.getConnection()) {
116 ).formatted(TABLE_SENDER_KEY_SHARED
);
117 try (final var statement
= connection
.prepareStatement(sql
)) {
118 statement
.executeUpdate();
120 } catch (SQLException e
) {
121 throw new RuntimeException("Failed update shared sender key store", e
);
125 public void deleteAllFor(final ServiceId serviceId
) {
126 try (final var connection
= database
.getConnection()) {
132 ).formatted(TABLE_SENDER_KEY_SHARED
);
133 try (final var statement
= connection
.prepareStatement(sql
)) {
134 statement
.setBytes(1, serviceId
.toByteArray());
135 statement
.executeUpdate();
137 } catch (SQLException e
) {
138 throw new RuntimeException("Failed update shared sender key store", e
);
142 public void deleteSharedWith(
143 final ServiceId serviceId
, final int deviceId
, final DistributionId distributionId
145 try (final var connection
= database
.getConnection()) {
149 WHERE uuid = ? AND device_id = ? AND distribution_id = ?
151 ).formatted(TABLE_SENDER_KEY_SHARED
);
152 try (final var statement
= connection
.prepareStatement(sql
)) {
153 statement
.setBytes(1, serviceId
.toByteArray());
154 statement
.setInt(2, deviceId
);
155 statement
.setBytes(3, UuidUtil
.toByteArray(distributionId
.asUuid()));
156 statement
.executeUpdate();
158 } catch (SQLException e
) {
159 throw new RuntimeException("Failed update shared sender key store", e
);
163 public void deleteAllFor(final DistributionId distributionId
) {
164 try (final var connection
= database
.getConnection()) {
168 WHERE distribution_id = ?
170 ).formatted(TABLE_SENDER_KEY_SHARED
);
171 try (final var statement
= connection
.prepareStatement(sql
)) {
172 statement
.setBytes(1, UuidUtil
.toByteArray(distributionId
.asUuid()));
173 statement
.executeUpdate();
175 } catch (SQLException e
) {
176 throw new RuntimeException("Failed update shared sender key store", e
);
180 void addLegacySenderKeysShared(final Map
<DistributionId
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
) {
181 logger
.debug("Migrating legacy sender keys shared to database");
182 long start
= System
.nanoTime();
183 try (final var connection
= database
.getConnection()) {
184 connection
.setAutoCommit(false);
185 for (final var entry
: sharedSenderKeys
.entrySet()) {
186 markSenderKeysSharedWith(connection
, entry
.getKey(), entry
.getValue());
189 } catch (SQLException e
) {
190 throw new RuntimeException("Failed update shared sender key store", e
);
192 logger
.debug("Complete sender keys shared migration took {}ms", (System
.nanoTime() - start
) / 1000000);
195 private void markSenderKeysSharedWith(
196 final Connection connection
, final DistributionId distributionId
, final Set
<SenderKeySharedEntry
> newEntries
197 ) throws SQLException
{
200 INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, timestamp)
203 ).formatted(TABLE_SENDER_KEY_SHARED
);
204 try (final var statement
= connection
.prepareStatement(sql
)) {
205 for (final var entry
: newEntries
) {
206 statement
.setBytes(1, entry
.serviceId().toByteArray());
207 statement
.setInt(2, entry
.deviceId());
208 statement
.setBytes(3, UuidUtil
.toByteArray(distributionId
.asUuid()));
209 statement
.setLong(4, System
.currentTimeMillis());
210 statement
.executeUpdate();
215 private SenderKeySharedEntry
getSenderKeySharedEntryFromResultSet(ResultSet resultSet
) throws SQLException
{
216 final var serviceId
= ServiceId
.parseOrThrow(resultSet
.getBytes("uuid"));
217 final var deviceId
= resultSet
.getInt("device_id");
218 return new SenderKeySharedEntry(serviceId
, deviceId
);
221 record SenderKeySharedEntry(ServiceId serviceId
, int deviceId
) {}