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 static final Logger logger
= LoggerFactory
.getLogger(SenderKeySharedStore
.class);
23 private static final 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,
33 address TEXT NOT NULL,
34 device_id INTEGER NOT NULL,
35 distribution_id BLOB NOT NULL,
36 timestamp INTEGER NOT NULL,
37 UNIQUE(address, 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.address, 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
-> new SignalProtocolAddress(k
.address
, 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(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 .map(a
-> new SenderKeySharedEntry(a
.getName(), a
.getDeviceId()))
86 .collect(Collectors
.toSet());
88 try (final var connection
= database
.getConnection()) {
89 connection
.setAutoCommit(false);
93 WHERE address = ? AND device_id = ?
95 ).formatted(TABLE_SENDER_KEY_SHARED
);
96 try (final var statement
= connection
.prepareStatement(sql
)) {
97 for (final var entry
: entriesToDelete
) {
98 statement
.setString(1, entry
.address());
99 statement
.setInt(2, entry
.deviceId());
100 statement
.executeUpdate();
104 } catch (SQLException e
) {
105 throw new RuntimeException("Failed update shared sender key store", e
);
109 public void deleteAll() {
110 try (final var connection
= database
.getConnection()) {
115 ).formatted(TABLE_SENDER_KEY_SHARED
);
116 try (final var statement
= connection
.prepareStatement(sql
)) {
117 statement
.executeUpdate();
119 } catch (SQLException e
) {
120 throw new RuntimeException("Failed update shared sender key store", e
);
124 public void deleteAllFor(final ServiceId serviceId
) {
125 try (final var connection
= database
.getConnection()) {
131 ).formatted(TABLE_SENDER_KEY_SHARED
);
132 try (final var statement
= connection
.prepareStatement(sql
)) {
133 statement
.setString(1, serviceId
.toString());
134 statement
.executeUpdate();
136 } catch (SQLException e
) {
137 throw new RuntimeException("Failed update shared sender key store", e
);
141 public void deleteSharedWith(
142 final ServiceId serviceId
, final int deviceId
, final DistributionId distributionId
144 try (final var connection
= database
.getConnection()) {
148 WHERE address = ? AND device_id = ? AND distribution_id = ?
150 ).formatted(TABLE_SENDER_KEY_SHARED
);
151 try (final var statement
= connection
.prepareStatement(sql
)) {
152 statement
.setString(1, serviceId
.toString());
153 statement
.setInt(2, deviceId
);
154 statement
.setBytes(3, UuidUtil
.toByteArray(distributionId
.asUuid()));
155 statement
.executeUpdate();
157 } catch (SQLException e
) {
158 throw new RuntimeException("Failed update shared sender key store", e
);
162 public void deleteAllFor(final DistributionId distributionId
) {
163 try (final var connection
= database
.getConnection()) {
167 WHERE distribution_id = ?
169 ).formatted(TABLE_SENDER_KEY_SHARED
);
170 try (final var statement
= connection
.prepareStatement(sql
)) {
171 statement
.setBytes(1, UuidUtil
.toByteArray(distributionId
.asUuid()));
172 statement
.executeUpdate();
174 } catch (SQLException e
) {
175 throw new RuntimeException("Failed update shared sender key store", e
);
179 void addLegacySenderKeysShared(final Map
<DistributionId
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
) {
180 logger
.debug("Migrating legacy sender keys shared to database");
181 long start
= System
.nanoTime();
182 try (final var connection
= database
.getConnection()) {
183 connection
.setAutoCommit(false);
184 for (final var entry
: sharedSenderKeys
.entrySet()) {
185 markSenderKeysSharedWith(connection
, entry
.getKey(), entry
.getValue());
188 } catch (SQLException e
) {
189 throw new RuntimeException("Failed update shared sender key store", e
);
191 logger
.debug("Complete sender keys shared migration took {}ms", (System
.nanoTime() - start
) / 1000000);
194 private void markSenderKeysSharedWith(
195 final Connection connection
, final DistributionId distributionId
, final Set
<SenderKeySharedEntry
> newEntries
196 ) throws SQLException
{
199 INSERT OR REPLACE INTO %s (address, device_id, distribution_id, timestamp)
202 ).formatted(TABLE_SENDER_KEY_SHARED
);
203 try (final var statement
= connection
.prepareStatement(sql
)) {
204 for (final var entry
: newEntries
) {
205 statement
.setString(1, entry
.toString());
206 statement
.setInt(2, entry
.deviceId());
207 statement
.setBytes(3, UuidUtil
.toByteArray(distributionId
.asUuid()));
208 statement
.setLong(4, System
.currentTimeMillis());
209 statement
.executeUpdate();
214 private SenderKeySharedEntry
getSenderKeySharedEntryFromResultSet(ResultSet resultSet
) throws SQLException
{
215 final var address
= resultSet
.getString("address");
216 final var deviceId
= resultSet
.getInt("device_id");
217 return new SenderKeySharedEntry(address
, deviceId
);
220 record SenderKeySharedEntry(String address
, int deviceId
) {}