1 package org
.asamk
.signal
.manager
.storage
;
3 import com
.zaxxer
.hikari
.HikariDataSource
;
5 import org
.asamk
.signal
.manager
.api
.Pair
;
6 import org
.asamk
.signal
.manager
.storage
.groups
.GroupStore
;
7 import org
.asamk
.signal
.manager
.storage
.identities
.IdentityKeyStore
;
8 import org
.asamk
.signal
.manager
.storage
.keyValue
.KeyValueStore
;
9 import org
.asamk
.signal
.manager
.storage
.prekeys
.KyberPreKeyStore
;
10 import org
.asamk
.signal
.manager
.storage
.prekeys
.PreKeyStore
;
11 import org
.asamk
.signal
.manager
.storage
.prekeys
.SignedPreKeyStore
;
12 import org
.asamk
.signal
.manager
.storage
.recipients
.CdsiStore
;
13 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientStore
;
14 import org
.asamk
.signal
.manager
.storage
.sendLog
.MessageSendLogStore
;
15 import org
.asamk
.signal
.manager
.storage
.senderKeys
.SenderKeyRecordStore
;
16 import org
.asamk
.signal
.manager
.storage
.senderKeys
.SenderKeySharedStore
;
17 import org
.asamk
.signal
.manager
.storage
.sessions
.SessionStore
;
18 import org
.asamk
.signal
.manager
.storage
.stickers
.StickerStore
;
19 import org
.slf4j
.Logger
;
20 import org
.slf4j
.LoggerFactory
;
21 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
22 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
.ACI
;
23 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
26 import java
.sql
.Connection
;
27 import java
.sql
.SQLException
;
28 import java
.util
.HashMap
;
29 import java
.util
.Optional
;
30 import java
.util
.UUID
;
32 public class AccountDatabase
extends Database
{
34 private static final Logger logger
= LoggerFactory
.getLogger(AccountDatabase
.class);
35 private static final long DATABASE_VERSION
= 19;
37 private AccountDatabase(final HikariDataSource dataSource
) {
38 super(logger
, DATABASE_VERSION
, dataSource
);
41 public static AccountDatabase
init(File databaseFile
) throws SQLException
{
42 return initDatabase(databaseFile
, AccountDatabase
::new);
46 protected void createDatabase(final Connection connection
) throws SQLException
{
47 RecipientStore
.createSql(connection
);
48 MessageSendLogStore
.createSql(connection
);
49 StickerStore
.createSql(connection
);
50 PreKeyStore
.createSql(connection
);
51 SignedPreKeyStore
.createSql(connection
);
52 KyberPreKeyStore
.createSql(connection
);
53 GroupStore
.createSql(connection
);
54 SessionStore
.createSql(connection
);
55 IdentityKeyStore
.createSql(connection
);
56 SenderKeyRecordStore
.createSql(connection
);
57 SenderKeySharedStore
.createSql(connection
);
58 KeyValueStore
.createSql(connection
);
59 CdsiStore
.createSql(connection
);
63 protected void upgradeDatabase(final Connection connection
, final long oldVersion
) throws SQLException
{
65 logger
.debug("Updating database: Creating recipient table");
66 try (final var statement
= connection
.createStatement()) {
67 statement
.executeUpdate("""
68 CREATE TABLE recipient (
69 _id INTEGER PRIMARY KEY AUTOINCREMENT,
73 profile_key_credential BLOB,
79 expiration_time INTEGER NOT NULL DEFAULT 0,
80 blocked INTEGER NOT NULL DEFAULT FALSE,
81 archived INTEGER NOT NULL DEFAULT FALSE,
82 profile_sharing INTEGER NOT NULL DEFAULT FALSE,
84 profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0,
85 profile_given_name TEXT,
86 profile_family_name TEXT,
88 profile_about_emoji TEXT,
89 profile_avatar_url_path TEXT,
90 profile_mobile_coin_address BLOB,
91 profile_unidentified_access_mode TEXT,
92 profile_capabilities TEXT
98 logger
.debug("Updating database: Creating sticker table");
99 try (final var statement
= connection
.createStatement()) {
100 statement
.executeUpdate("""
101 CREATE TABLE sticker (
102 _id INTEGER PRIMARY KEY,
103 pack_id BLOB UNIQUE NOT NULL,
104 pack_key BLOB NOT NULL,
105 installed INTEGER NOT NULL DEFAULT FALSE
110 if (oldVersion
< 4) {
111 logger
.debug("Updating database: Creating pre key tables");
112 try (final var statement
= connection
.createStatement()) {
113 statement
.executeUpdate("""
114 CREATE TABLE signed_pre_key (
115 _id INTEGER PRIMARY KEY,
116 account_id_type INTEGER NOT NULL,
117 key_id INTEGER NOT NULL,
118 public_key BLOB NOT NULL,
119 private_key BLOB NOT NULL,
120 signature BLOB NOT NULL,
121 timestamp INTEGER DEFAULT 0,
122 UNIQUE(account_id_type, key_id)
124 CREATE TABLE pre_key (
125 _id INTEGER PRIMARY KEY,
126 account_id_type INTEGER NOT NULL,
127 key_id INTEGER NOT NULL,
128 public_key BLOB NOT NULL,
129 private_key BLOB NOT NULL,
130 UNIQUE(account_id_type, key_id)
135 if (oldVersion
< 5) {
136 logger
.debug("Updating database: Creating group tables");
137 try (final var statement
= connection
.createStatement()) {
138 statement
.executeUpdate("""
139 CREATE TABLE group_v2 (
140 _id INTEGER PRIMARY KEY,
141 group_id BLOB UNIQUE NOT NULL,
142 master_key BLOB NOT NULL,
144 distribution_id BLOB UNIQUE NOT NULL,
145 blocked INTEGER NOT NULL DEFAULT FALSE,
146 permission_denied INTEGER NOT NULL DEFAULT FALSE
148 CREATE TABLE group_v1 (
149 _id INTEGER PRIMARY KEY,
150 group_id BLOB UNIQUE NOT NULL,
151 group_id_v2 BLOB UNIQUE,
154 expiration_time INTEGER NOT NULL DEFAULT 0,
155 blocked INTEGER NOT NULL DEFAULT FALSE,
156 archived INTEGER NOT NULL DEFAULT FALSE
158 CREATE TABLE group_v1_member (
159 _id INTEGER PRIMARY KEY,
160 group_id INTEGER NOT NULL REFERENCES group_v1 (_id) ON DELETE CASCADE,
161 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
162 UNIQUE(group_id, recipient_id)
167 if (oldVersion
< 6) {
168 logger
.debug("Updating database: Creating session tables");
169 try (final var statement
= connection
.createStatement()) {
170 statement
.executeUpdate("""
171 CREATE TABLE session (
172 _id INTEGER PRIMARY KEY,
173 account_id_type INTEGER NOT NULL,
174 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
175 device_id INTEGER NOT NULL,
176 record BLOB NOT NULL,
177 UNIQUE(account_id_type, recipient_id, device_id)
182 if (oldVersion
< 7) {
183 logger
.debug("Updating database: Creating identity table");
184 try (final var statement
= connection
.createStatement()) {
185 statement
.executeUpdate("""
186 CREATE TABLE identity (
187 _id INTEGER PRIMARY KEY,
188 recipient_id INTEGER UNIQUE NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
189 identity_key BLOB NOT NULL,
190 added_timestamp INTEGER NOT NULL,
191 trust_level INTEGER NOT NULL
196 if (oldVersion
< 8) {
197 logger
.debug("Updating database: Creating sender key tables");
198 try (final var statement
= connection
.createStatement()) {
199 statement
.executeUpdate("""
200 CREATE TABLE sender_key (
201 _id INTEGER PRIMARY KEY,
202 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
203 device_id INTEGER NOT NULL,
204 distribution_id BLOB NOT NULL,
205 record BLOB NOT NULL,
206 created_timestamp INTEGER NOT NULL,
207 UNIQUE(recipient_id, device_id, distribution_id)
209 CREATE TABLE sender_key_shared (
210 _id INTEGER PRIMARY KEY,
211 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
212 device_id INTEGER NOT NULL,
213 distribution_id BLOB NOT NULL,
214 timestamp INTEGER NOT NULL,
215 UNIQUE(recipient_id, device_id, distribution_id)
220 if (oldVersion
< 9) {
221 logger
.debug("Updating database: Adding urgent field");
222 try (final var statement
= connection
.createStatement()) {
223 statement
.executeUpdate("""
224 ALTER TABLE message_send_log_content ADD COLUMN urgent INTEGER NOT NULL DEFAULT TRUE;
228 if (oldVersion
< 10) {
229 logger
.debug("Updating database: Key tables on serviceId instead of recipientId");
230 try (final var statement
= connection
.createStatement()) {
231 statement
.executeUpdate("""
232 CREATE TABLE identity2 (
233 _id INTEGER PRIMARY KEY,
234 uuid BLOB UNIQUE NOT NULL,
235 identity_key BLOB NOT NULL,
236 added_timestamp INTEGER NOT NULL,
237 trust_level INTEGER NOT NULL
239 INSERT INTO identity2 (_id, uuid, identity_key, added_timestamp, trust_level)
240 SELECT i._id, r.uuid, i.identity_key, i.added_timestamp, i.trust_level
241 FROM identity i LEFT JOIN recipient r ON i.recipient_id = r._id
242 WHERE uuid IS NOT NULL;
244 ALTER TABLE identity2 RENAME TO identity;
246 DROP INDEX msl_recipient_index;
247 ALTER TABLE message_send_log ADD COLUMN uuid BLOB;
248 UPDATE message_send_log
250 FROM message_send_log i, (SELECT _id, uuid FROM recipient) AS r
251 WHERE i.recipient_id = r._id;
252 DELETE FROM message_send_log WHERE uuid IS NULL;
253 ALTER TABLE message_send_log DROP COLUMN recipient_id;
254 CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
256 CREATE TABLE sender_key2 (
257 _id INTEGER PRIMARY KEY,
259 device_id INTEGER NOT NULL,
260 distribution_id BLOB NOT NULL,
261 record BLOB NOT NULL,
262 created_timestamp INTEGER NOT NULL,
263 UNIQUE(uuid, device_id, distribution_id)
265 INSERT INTO sender_key2 (_id, uuid, device_id, distribution_id, record, created_timestamp)
266 SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.record, s.created_timestamp
267 FROM sender_key s LEFT JOIN recipient r ON s.recipient_id = r._id
268 WHERE uuid IS NOT NULL;
269 DROP TABLE sender_key;
270 ALTER TABLE sender_key2 RENAME TO sender_key;
272 CREATE TABLE sender_key_shared2 (
273 _id INTEGER PRIMARY KEY,
275 device_id INTEGER NOT NULL,
276 distribution_id BLOB NOT NULL,
277 timestamp INTEGER NOT NULL,
278 UNIQUE(uuid, device_id, distribution_id)
280 INSERT INTO sender_key_shared2 (_id, uuid, device_id, distribution_id, timestamp)
281 SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.timestamp
282 FROM sender_key_shared s LEFT JOIN recipient r ON s.recipient_id = r._id
283 WHERE uuid IS NOT NULL;
284 DROP TABLE sender_key_shared;
285 ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
287 CREATE TABLE session2 (
288 _id INTEGER PRIMARY KEY,
289 account_id_type INTEGER NOT NULL,
291 device_id INTEGER NOT NULL,
292 record BLOB NOT NULL,
293 UNIQUE(account_id_type, uuid, device_id)
295 INSERT INTO session2 (_id, account_id_type, uuid, device_id, record)
296 SELECT s._id, s.account_id_type, r.uuid, s.device_id, s.record
297 FROM session s LEFT JOIN recipient r ON s.recipient_id = r._id
298 WHERE uuid IS NOT NULL;
300 ALTER TABLE session2 RENAME TO session;
304 if (oldVersion
< 11) {
305 logger
.debug("Updating database: Adding pni field");
306 try (final var statement
= connection
.createStatement()) {
307 statement
.executeUpdate("""
308 ALTER TABLE recipient ADD COLUMN pni BLOB;
312 if (oldVersion
< 12) {
313 logger
.debug("Updating database: Adding username field");
314 try (final var statement
= connection
.createStatement()) {
315 statement
.executeUpdate("""
316 ALTER TABLE recipient ADD COLUMN username TEXT;
320 if (oldVersion
< 13) {
321 logger
.debug("Updating database: Cleanup unknown service ids");
324 DELETE FROM identity AS i
327 try (final var statement
= connection
.prepareStatement(sql
)) {
328 statement
.setBytes(1, ACI
.UNKNOWN
.toByteArray());
329 statement
.executeUpdate();
334 DELETE FROM sender_key_shared AS i
337 try (final var statement
= connection
.prepareStatement(sql
)) {
338 statement
.setBytes(1, ACI
.UNKNOWN
.toByteArray());
339 statement
.executeUpdate();
343 if (oldVersion
< 14) {
344 logger
.debug("Updating database: Creating kyber_pre_key table");
346 try (final var statement
= connection
.createStatement()) {
347 statement
.executeUpdate("""
348 CREATE TABLE kyber_pre_key (
349 _id INTEGER PRIMARY KEY,
350 account_id_type INTEGER NOT NULL,
351 key_id INTEGER NOT NULL,
352 serialized BLOB NOT NULL,
353 is_last_resort INTEGER NOT NULL,
354 UNIQUE(account_id_type, key_id)
360 if (oldVersion
< 15) {
361 logger
.debug("Updating database: Store serviceId as TEXT");
362 try (final var statement
= connection
.createStatement()) {
363 statement
.executeUpdate("""
364 CREATE TABLE tmp_mapping_table (
366 address TEXT NOT NULL
376 final var uuidAddressMapping
= new HashMap
<UUID
, ServiceId
>();
377 try (final var preparedStatement
= connection
.prepareStatement(sql
)) {
378 try (var result
= Utils
.executeQueryForStream(preparedStatement
, (resultSet
) -> {
379 final var pni
= Optional
.ofNullable(resultSet
.getBytes("pni"))
380 .map(UuidUtil
::parseOrNull
)
381 .map(ServiceId
.PNI
::from
);
382 final var serviceIdUuid
= Optional
.ofNullable(resultSet
.getBytes("uuid"))
383 .map(UuidUtil
::parseOrNull
);
384 final var serviceId
= serviceIdUuid
.isPresent() && pni
.isPresent() && serviceIdUuid
.get()
385 .equals(pni
.get().getRawUuid())
386 ? pni
.<ServiceId
>map(p
-> p
)
387 : serviceIdUuid
.<ServiceId
>map(ACI
::from
);
389 return new Pair
<>(serviceId
, pni
);
391 result
.forEach(p
-> {
392 final var serviceId
= p
.first();
393 final var pni
= p
.second();
394 if (serviceId
.isPresent()) {
395 uuidAddressMapping
.put(serviceId
.get().getRawUuid(), serviceId
.get());
397 if (pni
.isPresent()) {
398 uuidAddressMapping
.put(pni
.get().getRawUuid(), pni
.get());
404 final var insertSql
= """
405 INSERT INTO tmp_mapping_table (uuid, address)
408 try (final var insertStatement
= connection
.prepareStatement(insertSql
)) {
409 for (final var entry
: uuidAddressMapping
.entrySet()) {
410 final var uuid
= entry
.getKey();
411 final var serviceId
= entry
.getValue();
412 insertStatement
.setBytes(1, UuidUtil
.toByteArray(uuid
));
413 insertStatement
.setString(2, serviceId
.toString());
414 insertStatement
.execute();
418 statement
.executeUpdate("""
419 CREATE TABLE identity2 (
420 _id INTEGER PRIMARY KEY,
421 address TEXT UNIQUE NOT NULL,
422 identity_key BLOB NOT NULL,
423 added_timestamp INTEGER NOT NULL,
424 trust_level INTEGER NOT NULL
426 INSERT INTO identity2 (_id, address, identity_key, added_timestamp, trust_level)
427 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
429 WHERE address IS NOT NULL;
431 ALTER TABLE identity2 RENAME TO identity;
433 CREATE TABLE message_send_log2 (
434 _id INTEGER PRIMARY KEY,
435 content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE,
436 address TEXT NOT NULL,
437 device_id INTEGER NOT NULL
439 INSERT INTO message_send_log2 (_id, content_id, address, device_id)
440 SELECT m._id, m.content_id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = m.uuid) address, m.device_id
441 FROM message_send_log m
442 WHERE address IS NOT NULL;
443 DROP INDEX msl_recipient_index;
444 DROP INDEX msl_content_index;
445 DROP TABLE message_send_log;
446 ALTER TABLE message_send_log2 RENAME TO message_send_log;
447 CREATE INDEX msl_recipient_index ON message_send_log (address, device_id, content_id);
448 CREATE INDEX msl_content_index ON message_send_log (content_id);
450 CREATE TABLE sender_key2 (
451 _id INTEGER PRIMARY KEY,
452 address TEXT NOT NULL,
453 device_id INTEGER NOT NULL,
454 distribution_id BLOB NOT NULL,
455 record BLOB NOT NULL,
456 created_timestamp INTEGER NOT NULL,
457 UNIQUE(address, device_id, distribution_id)
459 INSERT INTO sender_key2 (_id, address, device_id, distribution_id, record, created_timestamp)
460 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
462 WHERE address IS NOT NULL;
463 DROP TABLE sender_key;
464 ALTER TABLE sender_key2 RENAME TO sender_key;
466 CREATE TABLE sender_key_shared2 (
467 _id INTEGER PRIMARY KEY,
468 address TEXT NOT NULL,
469 device_id INTEGER NOT NULL,
470 distribution_id BLOB NOT NULL,
471 timestamp INTEGER NOT NULL,
472 UNIQUE(address, device_id, distribution_id)
474 INSERT INTO sender_key_shared2 (_id, address, device_id, distribution_id, timestamp)
475 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
476 FROM sender_key_shared s
477 WHERE address IS NOT NULL;
478 DROP TABLE sender_key_shared;
479 ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
481 CREATE TABLE session2 (
482 _id INTEGER PRIMARY KEY,
483 account_id_type INTEGER NOT NULL,
484 address TEXT NOT NULL,
485 device_id INTEGER NOT NULL,
486 record BLOB NOT NULL,
487 UNIQUE(account_id_type, address, device_id)
489 INSERT INTO session2 (_id, account_id_type, address, device_id, record)
490 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
492 WHERE address IS NOT NULL;
494 ALTER TABLE session2 RENAME TO session;
496 DROP TABLE tmp_mapping_table;
500 if (oldVersion
< 16) {
501 logger
.debug("Updating database: Adding stale_timestamp prekey field");
502 try (final var statement
= connection
.createStatement()) {
503 statement
.executeUpdate("""
504 ALTER TABLE pre_key ADD COLUMN stale_timestamp INTEGER;
505 ALTER TABLE kyber_pre_key ADD COLUMN stale_timestamp INTEGER;
506 ALTER TABLE kyber_pre_key ADD COLUMN timestamp INTEGER DEFAULT 0;
510 if (oldVersion
< 17) {
511 logger
.debug("Updating database: Adding key_value table");
512 try (final var statement
= connection
.createStatement()) {
513 statement
.executeUpdate("""
514 CREATE TABLE key_value (
515 _id INTEGER PRIMARY KEY,
516 key TEXT UNIQUE NOT NULL,
522 if (oldVersion
< 18) {
523 logger
.debug("Updating database: Adding cdsi table");
524 try (final var statement
= connection
.createStatement()) {
525 statement
.executeUpdate("""
527 _id INTEGER PRIMARY KEY,
528 number TEXT NOT NULL UNIQUE,
529 last_seen_at INTEGER NOT NULL
534 if (oldVersion
< 19) {
535 logger
.debug("Updating database: Adding contact hidden column");
536 try (final var statement
= connection
.createStatement()) {
537 statement
.executeUpdate("""
538 ALTER TABLE recipient ADD COLUMN hidden INTEGER NOT NULL DEFAULT FALSE;