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
.RecipientStore
;
13 import org
.asamk
.signal
.manager
.storage
.sendLog
.MessageSendLogStore
;
14 import org
.asamk
.signal
.manager
.storage
.senderKeys
.SenderKeyRecordStore
;
15 import org
.asamk
.signal
.manager
.storage
.senderKeys
.SenderKeySharedStore
;
16 import org
.asamk
.signal
.manager
.storage
.sessions
.SessionStore
;
17 import org
.asamk
.signal
.manager
.storage
.stickers
.StickerStore
;
18 import org
.slf4j
.Logger
;
19 import org
.slf4j
.LoggerFactory
;
20 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
21 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
.ACI
;
22 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
25 import java
.sql
.Connection
;
26 import java
.sql
.SQLException
;
27 import java
.util
.HashMap
;
28 import java
.util
.Optional
;
29 import java
.util
.UUID
;
31 public class AccountDatabase
extends Database
{
33 private final static Logger logger
= LoggerFactory
.getLogger(AccountDatabase
.class);
34 private static final long DATABASE_VERSION
= 17;
36 private AccountDatabase(final HikariDataSource dataSource
) {
37 super(logger
, DATABASE_VERSION
, dataSource
);
40 public static AccountDatabase
init(File databaseFile
) throws SQLException
{
41 return initDatabase(databaseFile
, AccountDatabase
::new);
45 protected void createDatabase(final Connection connection
) throws SQLException
{
46 RecipientStore
.createSql(connection
);
47 MessageSendLogStore
.createSql(connection
);
48 StickerStore
.createSql(connection
);
49 PreKeyStore
.createSql(connection
);
50 SignedPreKeyStore
.createSql(connection
);
51 KyberPreKeyStore
.createSql(connection
);
52 GroupStore
.createSql(connection
);
53 SessionStore
.createSql(connection
);
54 IdentityKeyStore
.createSql(connection
);
55 SenderKeyRecordStore
.createSql(connection
);
56 SenderKeySharedStore
.createSql(connection
);
57 KeyValueStore
.createSql(connection
);
61 protected void upgradeDatabase(final Connection connection
, final long oldVersion
) throws SQLException
{
63 logger
.debug("Updating database: Creating recipient table");
64 try (final var statement
= connection
.createStatement()) {
65 statement
.executeUpdate("""
66 CREATE TABLE recipient (
67 _id INTEGER PRIMARY KEY AUTOINCREMENT,
71 profile_key_credential BLOB,
77 expiration_time INTEGER NOT NULL DEFAULT 0,
78 blocked INTEGER NOT NULL DEFAULT FALSE,
79 archived INTEGER NOT NULL DEFAULT FALSE,
80 profile_sharing INTEGER NOT NULL DEFAULT FALSE,
82 profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0,
83 profile_given_name TEXT,
84 profile_family_name TEXT,
86 profile_about_emoji TEXT,
87 profile_avatar_url_path TEXT,
88 profile_mobile_coin_address BLOB,
89 profile_unidentified_access_mode TEXT,
90 profile_capabilities TEXT
96 logger
.debug("Updating database: Creating sticker table");
97 try (final var statement
= connection
.createStatement()) {
98 statement
.executeUpdate("""
99 CREATE TABLE sticker (
100 _id INTEGER PRIMARY KEY,
101 pack_id BLOB UNIQUE NOT NULL,
102 pack_key BLOB NOT NULL,
103 installed INTEGER NOT NULL DEFAULT FALSE
108 if (oldVersion
< 4) {
109 logger
.debug("Updating database: Creating pre key tables");
110 try (final var statement
= connection
.createStatement()) {
111 statement
.executeUpdate("""
112 CREATE TABLE signed_pre_key (
113 _id INTEGER PRIMARY KEY,
114 account_id_type INTEGER NOT NULL,
115 key_id INTEGER NOT NULL,
116 public_key BLOB NOT NULL,
117 private_key BLOB NOT NULL,
118 signature BLOB NOT NULL,
119 timestamp INTEGER DEFAULT 0,
120 UNIQUE(account_id_type, key_id)
122 CREATE TABLE pre_key (
123 _id INTEGER PRIMARY KEY,
124 account_id_type INTEGER NOT NULL,
125 key_id INTEGER NOT NULL,
126 public_key BLOB NOT NULL,
127 private_key BLOB NOT NULL,
128 UNIQUE(account_id_type, key_id)
133 if (oldVersion
< 5) {
134 logger
.debug("Updating database: Creating group tables");
135 try (final var statement
= connection
.createStatement()) {
136 statement
.executeUpdate("""
137 CREATE TABLE group_v2 (
138 _id INTEGER PRIMARY KEY,
139 group_id BLOB UNIQUE NOT NULL,
140 master_key BLOB NOT NULL,
142 distribution_id BLOB UNIQUE NOT NULL,
143 blocked INTEGER NOT NULL DEFAULT FALSE,
144 permission_denied INTEGER NOT NULL DEFAULT FALSE
146 CREATE TABLE group_v1 (
147 _id INTEGER PRIMARY KEY,
148 group_id BLOB UNIQUE NOT NULL,
149 group_id_v2 BLOB UNIQUE,
152 expiration_time INTEGER NOT NULL DEFAULT 0,
153 blocked INTEGER NOT NULL DEFAULT FALSE,
154 archived INTEGER NOT NULL DEFAULT FALSE
156 CREATE TABLE group_v1_member (
157 _id INTEGER PRIMARY KEY,
158 group_id INTEGER NOT NULL REFERENCES group_v1 (_id) ON DELETE CASCADE,
159 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
160 UNIQUE(group_id, recipient_id)
165 if (oldVersion
< 6) {
166 logger
.debug("Updating database: Creating session tables");
167 try (final var statement
= connection
.createStatement()) {
168 statement
.executeUpdate("""
169 CREATE TABLE session (
170 _id INTEGER PRIMARY KEY,
171 account_id_type INTEGER NOT NULL,
172 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
173 device_id INTEGER NOT NULL,
174 record BLOB NOT NULL,
175 UNIQUE(account_id_type, recipient_id, device_id)
180 if (oldVersion
< 7) {
181 logger
.debug("Updating database: Creating identity table");
182 try (final var statement
= connection
.createStatement()) {
183 statement
.executeUpdate("""
184 CREATE TABLE identity (
185 _id INTEGER PRIMARY KEY,
186 recipient_id INTEGER UNIQUE NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
187 identity_key BLOB NOT NULL,
188 added_timestamp INTEGER NOT NULL,
189 trust_level INTEGER NOT NULL
194 if (oldVersion
< 8) {
195 logger
.debug("Updating database: Creating sender key tables");
196 try (final var statement
= connection
.createStatement()) {
197 statement
.executeUpdate("""
198 CREATE TABLE sender_key (
199 _id INTEGER PRIMARY KEY,
200 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
201 device_id INTEGER NOT NULL,
202 distribution_id BLOB NOT NULL,
203 record BLOB NOT NULL,
204 created_timestamp INTEGER NOT NULL,
205 UNIQUE(recipient_id, device_id, distribution_id)
207 CREATE TABLE sender_key_shared (
208 _id INTEGER PRIMARY KEY,
209 recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
210 device_id INTEGER NOT NULL,
211 distribution_id BLOB NOT NULL,
212 timestamp INTEGER NOT NULL,
213 UNIQUE(recipient_id, device_id, distribution_id)
218 if (oldVersion
< 9) {
219 logger
.debug("Updating database: Adding urgent field");
220 try (final var statement
= connection
.createStatement()) {
221 statement
.executeUpdate("""
222 ALTER TABLE message_send_log_content ADD COLUMN urgent INTEGER NOT NULL DEFAULT TRUE;
226 if (oldVersion
< 10) {
227 logger
.debug("Updating database: Key tables on serviceId instead of recipientId");
228 try (final var statement
= connection
.createStatement()) {
229 statement
.executeUpdate("""
230 CREATE TABLE identity2 (
231 _id INTEGER PRIMARY KEY,
232 uuid BLOB UNIQUE NOT NULL,
233 identity_key BLOB NOT NULL,
234 added_timestamp INTEGER NOT NULL,
235 trust_level INTEGER NOT NULL
237 INSERT INTO identity2 (_id, uuid, identity_key, added_timestamp, trust_level)
238 SELECT i._id, r.uuid, i.identity_key, i.added_timestamp, i.trust_level
239 FROM identity i LEFT JOIN recipient r ON i.recipient_id = r._id
240 WHERE uuid IS NOT NULL;
242 ALTER TABLE identity2 RENAME TO identity;
244 DROP INDEX msl_recipient_index;
245 ALTER TABLE message_send_log ADD COLUMN uuid BLOB;
246 UPDATE message_send_log
248 FROM message_send_log i, (SELECT _id, uuid FROM recipient) AS r
249 WHERE i.recipient_id = r._id;
250 DELETE FROM message_send_log WHERE uuid IS NULL;
251 ALTER TABLE message_send_log DROP COLUMN recipient_id;
252 CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
254 CREATE TABLE sender_key2 (
255 _id INTEGER PRIMARY KEY,
257 device_id INTEGER NOT NULL,
258 distribution_id BLOB NOT NULL,
259 record BLOB NOT NULL,
260 created_timestamp INTEGER NOT NULL,
261 UNIQUE(uuid, device_id, distribution_id)
263 INSERT INTO sender_key2 (_id, uuid, device_id, distribution_id, record, created_timestamp)
264 SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.record, s.created_timestamp
265 FROM sender_key s LEFT JOIN recipient r ON s.recipient_id = r._id
266 WHERE uuid IS NOT NULL;
267 DROP TABLE sender_key;
268 ALTER TABLE sender_key2 RENAME TO sender_key;
270 CREATE TABLE sender_key_shared2 (
271 _id INTEGER PRIMARY KEY,
273 device_id INTEGER NOT NULL,
274 distribution_id BLOB NOT NULL,
275 timestamp INTEGER NOT NULL,
276 UNIQUE(uuid, device_id, distribution_id)
278 INSERT INTO sender_key_shared2 (_id, uuid, device_id, distribution_id, timestamp)
279 SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.timestamp
280 FROM sender_key_shared s LEFT JOIN recipient r ON s.recipient_id = r._id
281 WHERE uuid IS NOT NULL;
282 DROP TABLE sender_key_shared;
283 ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
285 CREATE TABLE session2 (
286 _id INTEGER PRIMARY KEY,
287 account_id_type INTEGER NOT NULL,
289 device_id INTEGER NOT NULL,
290 record BLOB NOT NULL,
291 UNIQUE(account_id_type, uuid, device_id)
293 INSERT INTO session2 (_id, account_id_type, uuid, device_id, record)
294 SELECT s._id, s.account_id_type, r.uuid, s.device_id, s.record
295 FROM session s LEFT JOIN recipient r ON s.recipient_id = r._id
296 WHERE uuid IS NOT NULL;
298 ALTER TABLE session2 RENAME TO session;
302 if (oldVersion
< 11) {
303 logger
.debug("Updating database: Adding pni field");
304 try (final var statement
= connection
.createStatement()) {
305 statement
.executeUpdate("""
306 ALTER TABLE recipient ADD COLUMN pni BLOB;
310 if (oldVersion
< 12) {
311 logger
.debug("Updating database: Adding username field");
312 try (final var statement
= connection
.createStatement()) {
313 statement
.executeUpdate("""
314 ALTER TABLE recipient ADD COLUMN username TEXT;
318 if (oldVersion
< 13) {
319 logger
.debug("Updating database: Cleanup unknown service ids");
322 DELETE FROM identity AS i
325 try (final var statement
= connection
.prepareStatement(sql
)) {
326 statement
.setBytes(1, ACI
.UNKNOWN
.toByteArray());
327 statement
.executeUpdate();
332 DELETE FROM sender_key_shared AS i
335 try (final var statement
= connection
.prepareStatement(sql
)) {
336 statement
.setBytes(1, ACI
.UNKNOWN
.toByteArray());
337 statement
.executeUpdate();
341 if (oldVersion
< 14) {
342 logger
.debug("Updating database: Creating kyber_pre_key table");
344 try (final var statement
= connection
.createStatement()) {
345 statement
.executeUpdate("""
346 CREATE TABLE kyber_pre_key (
347 _id INTEGER PRIMARY KEY,
348 account_id_type INTEGER NOT NULL,
349 key_id INTEGER NOT NULL,
350 serialized BLOB NOT NULL,
351 is_last_resort INTEGER NOT NULL,
352 UNIQUE(account_id_type, key_id)
358 if (oldVersion
< 15) {
359 logger
.debug("Updating database: Store serviceId as TEXT");
360 try (final var statement
= connection
.createStatement()) {
361 statement
.executeUpdate("""
362 CREATE TABLE tmp_mapping_table (
364 address TEXT NOT NULL
374 final var uuidAddressMapping
= new HashMap
<UUID
, ServiceId
>();
375 try (final var preparedStatement
= connection
.prepareStatement(sql
)) {
376 try (var result
= Utils
.executeQueryForStream(preparedStatement
, (resultSet
) -> {
377 final var pni
= Optional
.ofNullable(resultSet
.getBytes("pni"))
378 .map(UuidUtil
::parseOrNull
)
379 .map(ServiceId
.PNI
::from
);
380 final var serviceIdUuid
= Optional
.ofNullable(resultSet
.getBytes("uuid"))
381 .map(UuidUtil
::parseOrNull
);
382 final var serviceId
= serviceIdUuid
.isPresent() && pni
.isPresent() && serviceIdUuid
.get()
383 .equals(pni
.get().getRawUuid())
384 ? pni
.<ServiceId
>map(p
-> p
)
385 : serviceIdUuid
.<ServiceId
>map(ACI
::from
);
387 return new Pair
<>(serviceId
, pni
);
389 result
.forEach(p
-> {
390 final var serviceId
= p
.first();
391 final var pni
= p
.second();
392 if (serviceId
.isPresent()) {
393 uuidAddressMapping
.put(serviceId
.get().getRawUuid(), serviceId
.get());
395 if (pni
.isPresent()) {
396 uuidAddressMapping
.put(pni
.get().getRawUuid(), pni
.get());
402 final var insertSql
= """
403 INSERT INTO tmp_mapping_table (uuid, address)
406 try (final var insertStatement
= connection
.prepareStatement(insertSql
)) {
407 for (final var entry
: uuidAddressMapping
.entrySet()) {
408 final var uuid
= entry
.getKey();
409 final var serviceId
= entry
.getValue();
410 insertStatement
.setBytes(1, UuidUtil
.toByteArray(uuid
));
411 insertStatement
.setString(2, serviceId
.toString());
412 insertStatement
.execute();
416 statement
.executeUpdate("""
417 CREATE TABLE identity2 (
418 _id INTEGER PRIMARY KEY,
419 address TEXT UNIQUE NOT NULL,
420 identity_key BLOB NOT NULL,
421 added_timestamp INTEGER NOT NULL,
422 trust_level INTEGER NOT NULL
424 INSERT INTO identity2 (_id, address, identity_key, added_timestamp, trust_level)
425 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
427 WHERE address IS NOT NULL;
429 ALTER TABLE identity2 RENAME TO identity;
431 CREATE TABLE message_send_log2 (
432 _id INTEGER PRIMARY KEY,
433 content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE,
434 address TEXT NOT NULL,
435 device_id INTEGER NOT NULL
437 INSERT INTO message_send_log2 (_id, content_id, address, device_id)
438 SELECT m._id, m.content_id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = m.uuid) address, m.device_id
439 FROM message_send_log m
440 WHERE address IS NOT NULL;
441 DROP INDEX msl_recipient_index;
442 DROP INDEX msl_content_index;
443 DROP TABLE message_send_log;
444 ALTER TABLE message_send_log2 RENAME TO message_send_log;
445 CREATE INDEX msl_recipient_index ON message_send_log (address, device_id, content_id);
446 CREATE INDEX msl_content_index ON message_send_log (content_id);
448 CREATE TABLE sender_key2 (
449 _id INTEGER PRIMARY KEY,
450 address TEXT NOT NULL,
451 device_id INTEGER NOT NULL,
452 distribution_id BLOB NOT NULL,
453 record BLOB NOT NULL,
454 created_timestamp INTEGER NOT NULL,
455 UNIQUE(address, device_id, distribution_id)
457 INSERT INTO sender_key2 (_id, address, device_id, distribution_id, record, created_timestamp)
458 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
460 WHERE address IS NOT NULL;
461 DROP TABLE sender_key;
462 ALTER TABLE sender_key2 RENAME TO sender_key;
464 CREATE TABLE sender_key_shared2 (
465 _id INTEGER PRIMARY KEY,
466 address TEXT NOT NULL,
467 device_id INTEGER NOT NULL,
468 distribution_id BLOB NOT NULL,
469 timestamp INTEGER NOT NULL,
470 UNIQUE(address, device_id, distribution_id)
472 INSERT INTO sender_key_shared2 (_id, address, device_id, distribution_id, timestamp)
473 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
474 FROM sender_key_shared s
475 WHERE address IS NOT NULL;
476 DROP TABLE sender_key_shared;
477 ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
479 CREATE TABLE session2 (
480 _id INTEGER PRIMARY KEY,
481 account_id_type INTEGER NOT NULL,
482 address TEXT NOT NULL,
483 device_id INTEGER NOT NULL,
484 record BLOB NOT NULL,
485 UNIQUE(account_id_type, address, device_id)
487 INSERT INTO session2 (_id, account_id_type, address, device_id, record)
488 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
490 WHERE address IS NOT NULL;
492 ALTER TABLE session2 RENAME TO session;
494 DROP TABLE tmp_mapping_table;
498 if (oldVersion
< 16) {
499 logger
.debug("Updating database: Adding stale_timestamp prekey field");
500 try (final var statement
= connection
.createStatement()) {
501 statement
.executeUpdate("""
502 ALTER TABLE pre_key ADD COLUMN stale_timestamp INTEGER;
503 ALTER TABLE kyber_pre_key ADD COLUMN stale_timestamp INTEGER;
504 ALTER TABLE kyber_pre_key ADD COLUMN timestamp INTEGER DEFAULT 0;
508 if (oldVersion
< 17) {
509 logger
.debug("Updating database: Adding key_value table");
510 try (final var statement
= connection
.createStatement()) {
511 statement
.executeUpdate("""
512 CREATE TABLE key_value (
513 _id INTEGER PRIMARY KEY,
514 key TEXT UNIQUE NOT NULL,