1package eu.siacs.conversations.persistance;
2
3import android.content.ContentValues;
4import android.content.Context;
5import android.database.Cursor;
6import android.database.DatabaseUtils;
7import android.database.sqlite.SQLiteCantOpenDatabaseException;
8import android.database.sqlite.SQLiteDatabase;
9import android.database.sqlite.SQLiteOpenHelper;
10import android.util.Base64;
11import android.util.Log;
12import android.util.Pair;
13
14import org.json.JSONObject;
15import org.whispersystems.libaxolotl.AxolotlAddress;
16import org.whispersystems.libaxolotl.IdentityKey;
17import org.whispersystems.libaxolotl.IdentityKeyPair;
18import org.whispersystems.libaxolotl.InvalidKeyException;
19import org.whispersystems.libaxolotl.state.PreKeyRecord;
20import org.whispersystems.libaxolotl.state.SessionRecord;
21import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
22
23import java.io.ByteArrayInputStream;
24import java.io.IOException;
25import java.security.cert.CertificateEncodingException;
26import java.security.cert.CertificateException;
27import java.security.cert.CertificateFactory;
28import java.security.cert.X509Certificate;
29import java.util.ArrayList;
30import java.util.HashSet;
31import java.util.Iterator;
32import java.util.List;
33import java.util.Set;
34import java.util.concurrent.CopyOnWriteArrayList;
35import org.json.JSONException;
36
37import eu.siacs.conversations.Config;
38import eu.siacs.conversations.crypto.axolotl.AxolotlService;
39import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
40import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
41import eu.siacs.conversations.entities.Account;
42import eu.siacs.conversations.entities.Contact;
43import eu.siacs.conversations.entities.Conversation;
44import eu.siacs.conversations.entities.Message;
45import eu.siacs.conversations.entities.PresenceTemplate;
46import eu.siacs.conversations.entities.Roster;
47import eu.siacs.conversations.entities.ServiceDiscoveryResult;
48import eu.siacs.conversations.generator.AbstractGenerator;
49import eu.siacs.conversations.xmpp.jid.InvalidJidException;
50import eu.siacs.conversations.xmpp.jid.Jid;
51
52public class DatabaseBackend extends SQLiteOpenHelper {
53
54 private static DatabaseBackend instance = null;
55
56 private static final String DATABASE_NAME = "history";
57 private static final int DATABASE_VERSION = 30;
58
59 private static String CREATE_CONTATCS_STATEMENT = "create table "
60 + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
61 + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
62 + Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
63 + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
64 + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
65 + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
66 + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
67 + Account.TABLENAME + "(" + Account.UUID
68 + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
69 + Contact.JID + ") ON CONFLICT REPLACE);";
70
71 private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
72 + ServiceDiscoveryResult.TABLENAME + "("
73 + ServiceDiscoveryResult.HASH + " TEXT, "
74 + ServiceDiscoveryResult.VER + " TEXT, "
75 + ServiceDiscoveryResult.RESULT + " TEXT, "
76 + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
77 + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
78
79 private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE "
80 + PresenceTemplate.TABELNAME + "("
81 + PresenceTemplate.UUID + " TEXT, "
82 + PresenceTemplate.LAST_USED + " NUMBER,"
83 + PresenceTemplate.MESSAGE + " TEXT,"
84 + PresenceTemplate.STATUS + " TEXT,"
85 + "UNIQUE("+PresenceTemplate.MESSAGE + "," +PresenceTemplate.STATUS+") ON CONFLICT REPLACE);";
86
87 private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
88 + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
89 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
90 + SQLiteAxolotlStore.ID + " INTEGER, "
91 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
92 + SQLiteAxolotlStore.ACCOUNT
93 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
94 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
95 + SQLiteAxolotlStore.ID
96 + ") ON CONFLICT REPLACE"
97 + ");";
98
99 private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
100 + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
101 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
102 + SQLiteAxolotlStore.ID + " INTEGER, "
103 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
104 + SQLiteAxolotlStore.ACCOUNT
105 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
106 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
107 + SQLiteAxolotlStore.ID
108 + ") ON CONFLICT REPLACE" +
109 ");";
110
111 private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
112 + SQLiteAxolotlStore.SESSION_TABLENAME + "("
113 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
114 + SQLiteAxolotlStore.NAME + " TEXT, "
115 + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
116 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
117 + SQLiteAxolotlStore.ACCOUNT
118 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
119 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
120 + SQLiteAxolotlStore.NAME + ", "
121 + SQLiteAxolotlStore.DEVICE_ID
122 + ") ON CONFLICT REPLACE"
123 + ");";
124
125 private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
126 + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
127 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
128 + SQLiteAxolotlStore.NAME + " TEXT, "
129 + SQLiteAxolotlStore.OWN + " INTEGER, "
130 + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
131 + SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
132 + SQLiteAxolotlStore.TRUSTED + " INTEGER, "
133 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
134 + SQLiteAxolotlStore.ACCOUNT
135 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
136 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
137 + SQLiteAxolotlStore.NAME + ", "
138 + SQLiteAxolotlStore.FINGERPRINT
139 + ") ON CONFLICT IGNORE"
140 + ");";
141
142 private static String START_TIMES_TABLE = "start_times";
143
144 private static String CREATE_START_TIMES_TABLE = "create table "+START_TIMES_TABLE+" (timestamp NUMBER);";
145
146 private DatabaseBackend(Context context) {
147 super(context, DATABASE_NAME, null, DATABASE_VERSION);
148 }
149
150 @Override
151 public void onCreate(SQLiteDatabase db) {
152 db.execSQL("PRAGMA foreign_keys=ON;");
153 db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID+ " TEXT PRIMARY KEY,"
154 + Account.USERNAME + " TEXT,"
155 + Account.SERVER + " TEXT,"
156 + Account.PASSWORD + " TEXT,"
157 + Account.DISPLAY_NAME + " TEXT, "
158 + Account.STATUS + " TEXT,"
159 + Account.STATUS_MESSAGE + " TEXT,"
160 + Account.ROSTERVERSION + " TEXT,"
161 + Account.OPTIONS + " NUMBER, "
162 + Account.AVATAR + " TEXT, "
163 + Account.KEYS + " TEXT, "
164 + Account.HOSTNAME + " TEXT, "
165 + Account.PORT + " NUMBER DEFAULT 5222)");
166 db.execSQL("create table " + Conversation.TABLENAME + " ("
167 + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
168 + " TEXT, " + Conversation.CONTACT + " TEXT, "
169 + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
170 + " TEXT, " + Conversation.CREATED + " NUMBER, "
171 + Conversation.STATUS + " NUMBER, " + Conversation.MODE
172 + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
173 + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
174 + "(" + Account.UUID + ") ON DELETE CASCADE);");
175 db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
176 + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
177 + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
178 + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
179 + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
180 + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
181 + Message.RELATIVE_FILE_PATH + " TEXT, "
182 + Message.SERVER_MSG_ID + " TEXT, "
183 + Message.FINGERPRINT + " TEXT, "
184 + Message.CARBON + " INTEGER, "
185 + Message.EDITED + " TEXT, "
186 + Message.READ + " NUMBER DEFAULT 1, "
187 + Message.OOB + " INTEGER, "
188 + Message.ERROR_MESSAGE + " TEXT,"
189 + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
190 + Message.CONVERSATION + ") REFERENCES "
191 + Conversation.TABLENAME + "(" + Conversation.UUID
192 + ") ON DELETE CASCADE);");
193
194 db.execSQL(CREATE_CONTATCS_STATEMENT);
195 db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
196 db.execSQL(CREATE_SESSIONS_STATEMENT);
197 db.execSQL(CREATE_PREKEYS_STATEMENT);
198 db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
199 db.execSQL(CREATE_IDENTITIES_STATEMENT);
200 db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
201 db.execSQL(CREATE_START_TIMES_TABLE);
202 }
203
204 @Override
205 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
206 if (oldVersion < 2 && newVersion >= 2) {
207 db.execSQL("update " + Account.TABLENAME + " set "
208 + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
209 }
210 if (oldVersion < 3 && newVersion >= 3) {
211 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
212 + Message.TYPE + " NUMBER");
213 }
214 if (oldVersion < 5 && newVersion >= 5) {
215 db.execSQL("DROP TABLE " + Contact.TABLENAME);
216 db.execSQL(CREATE_CONTATCS_STATEMENT);
217 db.execSQL("UPDATE " + Account.TABLENAME + " SET "
218 + Account.ROSTERVERSION + " = NULL");
219 }
220 if (oldVersion < 6 && newVersion >= 6) {
221 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
222 + Message.TRUE_COUNTERPART + " TEXT");
223 }
224 if (oldVersion < 7 && newVersion >= 7) {
225 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
226 + Message.REMOTE_MSG_ID + " TEXT");
227 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
228 + Contact.AVATAR + " TEXT");
229 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
230 + Account.AVATAR + " TEXT");
231 }
232 if (oldVersion < 8 && newVersion >= 8) {
233 db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
234 + Conversation.ATTRIBUTES + " TEXT");
235 }
236 if (oldVersion < 9 && newVersion >= 9) {
237 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
238 + Contact.LAST_TIME + " NUMBER");
239 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
240 + Contact.LAST_PRESENCE + " TEXT");
241 }
242 if (oldVersion < 10 && newVersion >= 10) {
243 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
244 + Message.RELATIVE_FILE_PATH + " TEXT");
245 }
246 if (oldVersion < 11 && newVersion >= 11) {
247 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
248 + Contact.GROUPS + " TEXT");
249 db.execSQL("delete from " + Contact.TABLENAME);
250 db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
251 }
252 if (oldVersion < 12 && newVersion >= 12) {
253 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
254 + Message.SERVER_MSG_ID + " TEXT");
255 }
256 if (oldVersion < 13 && newVersion >= 13) {
257 db.execSQL("delete from " + Contact.TABLENAME);
258 db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
259 }
260 if (oldVersion < 14 && newVersion >= 14) {
261 canonicalizeJids(db);
262 }
263 if (oldVersion < 15 && newVersion >= 15) {
264 recreateAxolotlDb(db);
265 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
266 + Message.FINGERPRINT + " TEXT");
267 } else if (oldVersion < 22 && newVersion >= 22) {
268 db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
269 }
270 if (oldVersion < 16 && newVersion >= 16) {
271 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
272 + Message.CARBON + " INTEGER");
273 }
274 if (oldVersion < 19 && newVersion >= 19) {
275 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
276 }
277 if (oldVersion < 20 && newVersion >= 20) {
278 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
279 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
280 }
281 if (oldVersion < 26 && newVersion >= 26) {
282 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
283 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT");
284 }
285 /* Any migrations that alter the Account table need to happen BEFORE this migration, as it
286 * depends on account de-serialization.
287 */
288 if (oldVersion < 17 && newVersion >= 17) {
289 List<Account> accounts = getAccounts(db);
290 for (Account account : accounts) {
291 String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
292 if (ownDeviceIdString == null) {
293 continue;
294 }
295 int ownDeviceId = Integer.valueOf(ownDeviceIdString);
296 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId);
297 deleteSession(db, account, ownAddress);
298 IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
299 if (identityKeyPair != null) {
300 setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED);
301 } else {
302 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair");
303 }
304 }
305 }
306 if (oldVersion < 18 && newVersion >= 18) {
307 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
308 }
309
310 if (oldVersion < 21 && newVersion >= 21) {
311 List<Account> accounts = getAccounts(db);
312 for (Account account : accounts) {
313 account.unsetPgpSignature();
314 db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
315 + "=?", new String[]{account.getUuid()});
316 }
317 }
318
319 if (oldVersion < 23 && newVersion >= 23) {
320 db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
321 }
322
323 if (oldVersion < 24 && newVersion >= 24) {
324 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
325 }
326
327 if (oldVersion < 25 && newVersion >= 25) {
328 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
329 }
330
331 if (oldVersion < 26 && newVersion >= 26) {
332 db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
333 }
334
335 if (oldVersion < 27 && newVersion >= 27) {
336 db.execSQL("DELETE FROM "+ServiceDiscoveryResult.TABLENAME);
337 }
338
339 if (oldVersion < 28 && newVersion >= 28) {
340 canonicalizeJids(db);
341 }
342
343 if (oldVersion < 29 && newVersion >= 29) {
344 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT");
345 }
346 if (oldVersion < 30 && newVersion >= 30) {
347 db.execSQL(CREATE_START_TIMES_TABLE);
348 }
349 }
350
351 private void canonicalizeJids(SQLiteDatabase db) {
352 // migrate db to new, canonicalized JID domainpart representation
353
354 // Conversation table
355 Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
356 while (cursor.moveToNext()) {
357 String newJid;
358 try {
359 newJid = Jid.fromString(
360 cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
361 ).toPreppedString();
362 } catch (InvalidJidException ignored) {
363 Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
364 + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
365 + ": " + ignored + ". Skipping...");
366 continue;
367 }
368
369 String updateArgs[] = {
370 newJid,
371 cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
372 };
373 db.execSQL("update " + Conversation.TABLENAME
374 + " set " + Conversation.CONTACTJID + " = ? "
375 + " where " + Conversation.UUID + " = ?", updateArgs);
376 }
377 cursor.close();
378
379 // Contact table
380 cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
381 while (cursor.moveToNext()) {
382 String newJid;
383 try {
384 newJid = Jid.fromString(
385 cursor.getString(cursor.getColumnIndex(Contact.JID))
386 ).toPreppedString();
387 } catch (InvalidJidException ignored) {
388 Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
389 + cursor.getString(cursor.getColumnIndex(Contact.JID))
390 + ": " + ignored + ". Skipping...");
391 continue;
392 }
393
394 String updateArgs[] = {
395 newJid,
396 cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
397 cursor.getString(cursor.getColumnIndex(Contact.JID)),
398 };
399 db.execSQL("update " + Contact.TABLENAME
400 + " set " + Contact.JID + " = ? "
401 + " where " + Contact.ACCOUNT + " = ? "
402 + " AND " + Contact.JID + " = ?", updateArgs);
403 }
404 cursor.close();
405
406 // Account table
407 cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
408 while (cursor.moveToNext()) {
409 String newServer;
410 try {
411 newServer = Jid.fromParts(
412 cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
413 cursor.getString(cursor.getColumnIndex(Account.SERVER)),
414 "mobile"
415 ).getDomainpart();
416 } catch (InvalidJidException ignored) {
417 Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
418 + cursor.getString(cursor.getColumnIndex(Account.SERVER))
419 + ": " + ignored + ". Skipping...");
420 continue;
421 }
422
423 String updateArgs[] = {
424 newServer,
425 cursor.getString(cursor.getColumnIndex(Account.UUID)),
426 };
427 db.execSQL("update " + Account.TABLENAME
428 + " set " + Account.SERVER + " = ? "
429 + " where " + Account.UUID + " = ?", updateArgs);
430 }
431 cursor.close();
432 }
433
434 public static synchronized DatabaseBackend getInstance(Context context) {
435 if (instance == null) {
436 instance = new DatabaseBackend(context);
437 }
438 return instance;
439 }
440
441 public void createConversation(Conversation conversation) {
442 SQLiteDatabase db = this.getWritableDatabase();
443 db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
444 }
445
446 public void createMessage(Message message) {
447 SQLiteDatabase db = this.getWritableDatabase();
448 db.insert(Message.TABLENAME, null, message.getContentValues());
449 }
450
451 public void createAccount(Account account) {
452 SQLiteDatabase db = this.getWritableDatabase();
453 db.insert(Account.TABLENAME, null, account.getContentValues());
454 }
455
456 public void insertDiscoveryResult(ServiceDiscoveryResult result) {
457 SQLiteDatabase db = this.getWritableDatabase();
458 db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues());
459 }
460
461 public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) {
462 SQLiteDatabase db = this.getReadableDatabase();
463 String[] selectionArgs = {hash, ver};
464 Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null,
465 ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?",
466 selectionArgs, null, null, null);
467 if (cursor.getCount() == 0) {
468 cursor.close();
469 return null;
470 }
471 cursor.moveToFirst();
472
473 ServiceDiscoveryResult result = null;
474 try {
475 result = new ServiceDiscoveryResult(cursor);
476 } catch (JSONException e) { /* result is still null */ }
477
478 cursor.close();
479 return result;
480 }
481
482 public void insertPresenceTemplate(PresenceTemplate template) {
483 SQLiteDatabase db = this.getWritableDatabase();
484 db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues());
485 }
486
487 public List<PresenceTemplate> getPresenceTemplates() {
488 ArrayList<PresenceTemplate> templates = new ArrayList<>();
489 SQLiteDatabase db = this.getReadableDatabase();
490 Cursor cursor = db.query(PresenceTemplate.TABELNAME,null,null,null,null,null,PresenceTemplate.LAST_USED+" desc");
491 while (cursor.moveToNext()) {
492 templates.add(PresenceTemplate.fromCursor(cursor));
493 }
494 cursor.close();
495 return templates;
496 }
497
498 public void deletePresenceTemplate(PresenceTemplate template) {
499 Log.d(Config.LOGTAG,"deleting presence template with uuid "+template.getUuid());
500 SQLiteDatabase db = this.getWritableDatabase();
501 String where = PresenceTemplate.UUID+"=?";
502 String[] whereArgs = {template.getUuid()};
503 db.delete(PresenceTemplate.TABELNAME,where,whereArgs);
504 }
505
506 public CopyOnWriteArrayList<Conversation> getConversations(int status) {
507 CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
508 SQLiteDatabase db = this.getReadableDatabase();
509 String[] selectionArgs = {Integer.toString(status)};
510 Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
511 + " where " + Conversation.STATUS + " = ? order by "
512 + Conversation.CREATED + " desc", selectionArgs);
513 while (cursor.moveToNext()) {
514 list.add(Conversation.fromCursor(cursor));
515 }
516 cursor.close();
517 return list;
518 }
519
520 public ArrayList<Message> getMessages(Conversation conversations, int limit) {
521 return getMessages(conversations, limit, -1);
522 }
523
524 public ArrayList<Message> getMessages(Conversation conversation, int limit,
525 long timestamp) {
526 ArrayList<Message> list = new ArrayList<>();
527 SQLiteDatabase db = this.getReadableDatabase();
528 Cursor cursor;
529 if (timestamp == -1) {
530 String[] selectionArgs = {conversation.getUuid()};
531 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
532 + "=?", selectionArgs, null, null, Message.TIME_SENT
533 + " DESC", String.valueOf(limit));
534 } else {
535 String[] selectionArgs = {conversation.getUuid(),
536 Long.toString(timestamp)};
537 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
538 + "=? and " + Message.TIME_SENT + "<?", selectionArgs,
539 null, null, Message.TIME_SENT + " DESC",
540 String.valueOf(limit));
541 }
542 if (cursor.getCount() > 0) {
543 cursor.moveToLast();
544 do {
545 Message message = Message.fromCursor(cursor);
546 message.setConversation(conversation);
547 list.add(message);
548 } while (cursor.moveToPrevious());
549 }
550 cursor.close();
551 return list;
552 }
553
554 public Iterable<Message> getMessagesIterable(final Conversation conversation) {
555 return new Iterable<Message>() {
556 @Override
557 public Iterator<Message> iterator() {
558 class MessageIterator implements Iterator<Message> {
559 SQLiteDatabase db = getReadableDatabase();
560 String[] selectionArgs = {conversation.getUuid()};
561 Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
562 + "=?", selectionArgs, null, null, Message.TIME_SENT
563 + " ASC", null);
564
565 public MessageIterator() {
566 cursor.moveToFirst();
567 }
568
569 @Override
570 public boolean hasNext() {
571 return !cursor.isAfterLast();
572 }
573
574 @Override
575 public Message next() {
576 Message message = Message.fromCursor(cursor);
577 cursor.moveToNext();
578 return message;
579 }
580
581 @Override
582 public void remove() {
583 throw new UnsupportedOperationException();
584 }
585 }
586 return new MessageIterator();
587 }
588 };
589 }
590
591 public Conversation findConversation(final Account account, final Jid contactJid) {
592 SQLiteDatabase db = this.getReadableDatabase();
593 String[] selectionArgs = {account.getUuid(),
594 contactJid.toBareJid().toPreppedString() + "/%",
595 contactJid.toBareJid().toPreppedString()
596 };
597 Cursor cursor = db.query(Conversation.TABLENAME, null,
598 Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
599 + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
600 if (cursor.getCount() == 0) {
601 cursor.close();
602 return null;
603 }
604 cursor.moveToFirst();
605 Conversation conversation = Conversation.fromCursor(cursor);
606 cursor.close();
607 return conversation;
608 }
609
610 public void updateConversation(final Conversation conversation) {
611 final SQLiteDatabase db = this.getWritableDatabase();
612 final String[] args = {conversation.getUuid()};
613 db.update(Conversation.TABLENAME, conversation.getContentValues(),
614 Conversation.UUID + "=?", args);
615 }
616
617 public List<Account> getAccounts() {
618 SQLiteDatabase db = this.getReadableDatabase();
619 return getAccounts(db);
620 }
621
622 private List<Account> getAccounts(SQLiteDatabase db) {
623 List<Account> list = new ArrayList<>();
624 Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
625 null, null);
626 while (cursor.moveToNext()) {
627 list.add(Account.fromCursor(cursor));
628 }
629 cursor.close();
630 return list;
631 }
632
633 public boolean updateAccount(Account account) {
634 SQLiteDatabase db = this.getWritableDatabase();
635 String[] args = {account.getUuid()};
636 final int rows = db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + "=?", args);
637 return rows == 1;
638 }
639
640 public boolean deleteAccount(Account account) {
641 SQLiteDatabase db = this.getWritableDatabase();
642 String[] args = {account.getUuid()};
643 final int rows = db.delete(Account.TABLENAME, Account.UUID + "=?", args);
644 return rows == 1;
645 }
646
647 public boolean hasEnabledAccounts() {
648 SQLiteDatabase db = this.getReadableDatabase();
649 Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
650 + Account.TABLENAME + " where not options & (1 <<1)", null);
651 try {
652 cursor.moveToFirst();
653 int count = cursor.getInt(0);
654 return (count > 0);
655 } catch (SQLiteCantOpenDatabaseException e) {
656 return true; // better safe than sorry
657 } catch (RuntimeException e) {
658 return true; // better safe than sorry
659 } finally {
660 if (cursor != null) {
661 cursor.close();
662 }
663 }
664 }
665
666 @Override
667 public SQLiteDatabase getWritableDatabase() {
668 SQLiteDatabase db = super.getWritableDatabase();
669 db.execSQL("PRAGMA foreign_keys=ON;");
670 return db;
671 }
672
673 public void updateMessage(Message message) {
674 SQLiteDatabase db = this.getWritableDatabase();
675 String[] args = {message.getUuid()};
676 db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
677 + "=?", args);
678 }
679
680 public void updateMessage(Message message, String uuid) {
681 SQLiteDatabase db = this.getWritableDatabase();
682 String[] args = {uuid};
683 db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
684 + "=?", args);
685 }
686
687 public void readRoster(Roster roster) {
688 SQLiteDatabase db = this.getReadableDatabase();
689 Cursor cursor;
690 String args[] = {roster.getAccount().getUuid()};
691 cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
692 while (cursor.moveToNext()) {
693 roster.initContact(Contact.fromCursor(cursor));
694 }
695 cursor.close();
696 }
697
698 public void writeRoster(final Roster roster) {
699 final Account account = roster.getAccount();
700 final SQLiteDatabase db = this.getWritableDatabase();
701 db.beginTransaction();
702 for (Contact contact : roster.getContacts()) {
703 if (contact.getOption(Contact.Options.IN_ROSTER)) {
704 db.insert(Contact.TABLENAME, null, contact.getContentValues());
705 } else {
706 String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
707 String[] whereArgs = {account.getUuid(), contact.getJid().toPreppedString()};
708 db.delete(Contact.TABLENAME, where, whereArgs);
709 }
710 }
711 db.setTransactionSuccessful();
712 db.endTransaction();
713 account.setRosterVersion(roster.getVersion());
714 updateAccount(account);
715 }
716
717 public void deleteMessagesInConversation(Conversation conversation) {
718 SQLiteDatabase db = this.getWritableDatabase();
719 String[] args = {conversation.getUuid()};
720 db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
721 }
722
723 public Pair<Long, String> getLastMessageReceived(Account account) {
724 Cursor cursor = null;
725 try {
726 SQLiteDatabase db = this.getReadableDatabase();
727 String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
728 String[] args = {account.getUuid()};
729 cursor = db.rawQuery(sql, args);
730 if (cursor.getCount() == 0) {
731 return null;
732 } else {
733 cursor.moveToFirst();
734 return new Pair<>(cursor.getLong(0), cursor.getString(1));
735 }
736 } catch (Exception e) {
737 return null;
738 } finally {
739 if (cursor != null) {
740 cursor.close();
741 }
742 }
743 }
744
745 public Pair<Long,String> getLastClearDate(Account account) {
746 SQLiteDatabase db = this.getReadableDatabase();
747 String[] columns = {Conversation.ATTRIBUTES};
748 String selection = Conversation.ACCOUNT + "=?";
749 String[] args = {account.getUuid()};
750 Cursor cursor = db.query(Conversation.TABLENAME,columns,selection,args,null,null,null);
751 long maxClearDate = 0;
752 while (cursor.moveToNext()) {
753 try {
754 final JSONObject jsonObject = new JSONObject(cursor.getString(0));
755 maxClearDate = Math.max(maxClearDate, jsonObject.getLong(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY));
756 } catch (Exception e) {
757 //ignored
758 }
759 }
760 cursor.close();
761 return new Pair<>(maxClearDate,null);
762 }
763
764 private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
765 final SQLiteDatabase db = this.getReadableDatabase();
766 String[] selectionArgs = {account.getUuid(),
767 contact.getName(),
768 Integer.toString(contact.getDeviceId())};
769 return db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
770 null,
771 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
772 + SQLiteAxolotlStore.NAME + " = ? AND "
773 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
774 selectionArgs,
775 null, null, null);
776 }
777
778 public SessionRecord loadSession(Account account, AxolotlAddress contact) {
779 SessionRecord session = null;
780 Cursor cursor = getCursorForSession(account, contact);
781 if (cursor.getCount() != 0) {
782 cursor.moveToFirst();
783 try {
784 session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
785 } catch (IOException e) {
786 cursor.close();
787 throw new AssertionError(e);
788 }
789 }
790 cursor.close();
791 return session;
792 }
793
794 public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
795 final SQLiteDatabase db = this.getReadableDatabase();
796 return getSubDeviceSessions(db, account, contact);
797 }
798
799 private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
800 List<Integer> devices = new ArrayList<>();
801 String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
802 String[] selectionArgs = {account.getUuid(),
803 contact.getName()};
804 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
805 columns,
806 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
807 + SQLiteAxolotlStore.NAME + " = ?",
808 selectionArgs,
809 null, null, null);
810
811 while (cursor.moveToNext()) {
812 devices.add(cursor.getInt(
813 cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
814 }
815
816 cursor.close();
817 return devices;
818 }
819
820 public boolean containsSession(Account account, AxolotlAddress contact) {
821 Cursor cursor = getCursorForSession(account, contact);
822 int count = cursor.getCount();
823 cursor.close();
824 return count != 0;
825 }
826
827 public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
828 SQLiteDatabase db = this.getWritableDatabase();
829 ContentValues values = new ContentValues();
830 values.put(SQLiteAxolotlStore.NAME, contact.getName());
831 values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
832 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
833 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
834 db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
835 }
836
837 public void deleteSession(Account account, AxolotlAddress contact) {
838 SQLiteDatabase db = this.getWritableDatabase();
839 deleteSession(db, account, contact);
840 }
841
842 private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
843 String[] args = {account.getUuid(),
844 contact.getName(),
845 Integer.toString(contact.getDeviceId())};
846 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
847 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
848 + SQLiteAxolotlStore.NAME + " = ? AND "
849 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
850 args);
851 }
852
853 public void deleteAllSessions(Account account, AxolotlAddress contact) {
854 SQLiteDatabase db = this.getWritableDatabase();
855 String[] args = {account.getUuid(), contact.getName()};
856 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
857 SQLiteAxolotlStore.ACCOUNT + "=? AND "
858 + SQLiteAxolotlStore.NAME + " = ?",
859 args);
860 }
861
862 private Cursor getCursorForPreKey(Account account, int preKeyId) {
863 SQLiteDatabase db = this.getReadableDatabase();
864 String[] columns = {SQLiteAxolotlStore.KEY};
865 String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
866 Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
867 columns,
868 SQLiteAxolotlStore.ACCOUNT + "=? AND "
869 + SQLiteAxolotlStore.ID + "=?",
870 selectionArgs,
871 null, null, null);
872
873 return cursor;
874 }
875
876 public PreKeyRecord loadPreKey(Account account, int preKeyId) {
877 PreKeyRecord record = null;
878 Cursor cursor = getCursorForPreKey(account, preKeyId);
879 if (cursor.getCount() != 0) {
880 cursor.moveToFirst();
881 try {
882 record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
883 } catch (IOException e) {
884 throw new AssertionError(e);
885 }
886 }
887 cursor.close();
888 return record;
889 }
890
891 public boolean containsPreKey(Account account, int preKeyId) {
892 Cursor cursor = getCursorForPreKey(account, preKeyId);
893 int count = cursor.getCount();
894 cursor.close();
895 return count != 0;
896 }
897
898 public void storePreKey(Account account, PreKeyRecord record) {
899 SQLiteDatabase db = this.getWritableDatabase();
900 ContentValues values = new ContentValues();
901 values.put(SQLiteAxolotlStore.ID, record.getId());
902 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
903 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
904 db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
905 }
906
907 public void deletePreKey(Account account, int preKeyId) {
908 SQLiteDatabase db = this.getWritableDatabase();
909 String[] args = {account.getUuid(), Integer.toString(preKeyId)};
910 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
911 SQLiteAxolotlStore.ACCOUNT + "=? AND "
912 + SQLiteAxolotlStore.ID + "=?",
913 args);
914 }
915
916 private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
917 SQLiteDatabase db = this.getReadableDatabase();
918 String[] columns = {SQLiteAxolotlStore.KEY};
919 String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
920 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
921 columns,
922 SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
923 selectionArgs,
924 null, null, null);
925
926 return cursor;
927 }
928
929 public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
930 SignedPreKeyRecord record = null;
931 Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
932 if (cursor.getCount() != 0) {
933 cursor.moveToFirst();
934 try {
935 record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
936 } catch (IOException e) {
937 throw new AssertionError(e);
938 }
939 }
940 cursor.close();
941 return record;
942 }
943
944 public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
945 List<SignedPreKeyRecord> prekeys = new ArrayList<>();
946 SQLiteDatabase db = this.getReadableDatabase();
947 String[] columns = {SQLiteAxolotlStore.KEY};
948 String[] selectionArgs = {account.getUuid()};
949 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
950 columns,
951 SQLiteAxolotlStore.ACCOUNT + "=?",
952 selectionArgs,
953 null, null, null);
954
955 while (cursor.moveToNext()) {
956 try {
957 prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
958 } catch (IOException ignored) {
959 }
960 }
961 cursor.close();
962 return prekeys;
963 }
964
965 public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
966 Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
967 int count = cursor.getCount();
968 cursor.close();
969 return count != 0;
970 }
971
972 public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
973 SQLiteDatabase db = this.getWritableDatabase();
974 ContentValues values = new ContentValues();
975 values.put(SQLiteAxolotlStore.ID, record.getId());
976 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
977 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
978 db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
979 }
980
981 public void deleteSignedPreKey(Account account, int signedPreKeyId) {
982 SQLiteDatabase db = this.getWritableDatabase();
983 String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
984 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
985 SQLiteAxolotlStore.ACCOUNT + "=? AND "
986 + SQLiteAxolotlStore.ID + "=?",
987 args);
988 }
989
990 private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
991 final SQLiteDatabase db = this.getReadableDatabase();
992 return getIdentityKeyCursor(db, account, name, own);
993 }
994
995 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
996 return getIdentityKeyCursor(db, account, name, own, null);
997 }
998
999 private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
1000 final SQLiteDatabase db = this.getReadableDatabase();
1001 return getIdentityKeyCursor(db, account, fingerprint);
1002 }
1003
1004 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
1005 return getIdentityKeyCursor(db, account, null, null, fingerprint);
1006 }
1007
1008 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
1009 String[] columns = {SQLiteAxolotlStore.TRUSTED,
1010 SQLiteAxolotlStore.KEY};
1011 ArrayList<String> selectionArgs = new ArrayList<>(4);
1012 selectionArgs.add(account.getUuid());
1013 String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
1014 if (name != null) {
1015 selectionArgs.add(name);
1016 selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
1017 }
1018 if (fingerprint != null) {
1019 selectionArgs.add(fingerprint);
1020 selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
1021 }
1022 if (own != null) {
1023 selectionArgs.add(own ? "1" : "0");
1024 selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
1025 }
1026 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1027 columns,
1028 selectionString,
1029 selectionArgs.toArray(new String[selectionArgs.size()]),
1030 null, null, null);
1031
1032 return cursor;
1033 }
1034
1035 public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
1036 SQLiteDatabase db = getReadableDatabase();
1037 return loadOwnIdentityKeyPair(db, account);
1038 }
1039
1040 private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
1041 String name = account.getJid().toBareJid().toPreppedString();
1042 IdentityKeyPair identityKeyPair = null;
1043 Cursor cursor = getIdentityKeyCursor(db, account, name, true);
1044 if (cursor.getCount() != 0) {
1045 cursor.moveToFirst();
1046 try {
1047 identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
1048 } catch (InvalidKeyException e) {
1049 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
1050 }
1051 }
1052 cursor.close();
1053
1054 return identityKeyPair;
1055 }
1056
1057 public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
1058 return loadIdentityKeys(account, name, null);
1059 }
1060
1061 public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) {
1062 Set<IdentityKey> identityKeys = new HashSet<>();
1063 Cursor cursor = getIdentityKeyCursor(account, name, false);
1064
1065 while (cursor.moveToNext()) {
1066 if (trust != null &&
1067 cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
1068 != trust.getCode()) {
1069 continue;
1070 }
1071 try {
1072 identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
1073 } catch (InvalidKeyException e) {
1074 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
1075 }
1076 }
1077 cursor.close();
1078
1079 return identityKeys;
1080 }
1081
1082 public long numTrustedKeys(Account account, String name) {
1083 SQLiteDatabase db = getReadableDatabase();
1084 String[] args = {
1085 account.getUuid(),
1086 name,
1087 String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()),
1088 String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode())
1089 };
1090 return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1091 SQLiteAxolotlStore.ACCOUNT + " = ?"
1092 + " AND " + SQLiteAxolotlStore.NAME + " = ?"
1093 + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)",
1094 args
1095 );
1096 }
1097
1098 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
1099 storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED);
1100 }
1101
1102 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) {
1103 SQLiteDatabase db = this.getWritableDatabase();
1104 ContentValues values = new ContentValues();
1105 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
1106 values.put(SQLiteAxolotlStore.NAME, name);
1107 values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
1108 values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
1109 values.put(SQLiteAxolotlStore.KEY, base64Serialized);
1110 values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
1111 db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
1112 }
1113
1114 public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
1115 Cursor cursor = getIdentityKeyCursor(account, fingerprint);
1116 XmppAxolotlSession.Trust trust = null;
1117 if (cursor.getCount() > 0) {
1118 cursor.moveToFirst();
1119 int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
1120 trust = XmppAxolotlSession.Trust.fromCode(trustValue);
1121 }
1122 cursor.close();
1123 return trust;
1124 }
1125
1126 public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1127 SQLiteDatabase db = this.getWritableDatabase();
1128 return setIdentityKeyTrust(db, account, fingerprint, trust);
1129 }
1130
1131 private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1132 String[] selectionArgs = {
1133 account.getUuid(),
1134 fingerprint
1135 };
1136 ContentValues values = new ContentValues();
1137 values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
1138 int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1139 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1140 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1141 selectionArgs);
1142 return rows == 1;
1143 }
1144
1145 public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
1146 SQLiteDatabase db = this.getWritableDatabase();
1147 String[] selectionArgs = {
1148 account.getUuid(),
1149 fingerprint
1150 };
1151 try {
1152 ContentValues values = new ContentValues();
1153 values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
1154 return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1155 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1156 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1157 selectionArgs) == 1;
1158 } catch (CertificateEncodingException e) {
1159 Log.d(Config.LOGTAG, "could not encode certificate");
1160 return false;
1161 }
1162 }
1163
1164 public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
1165 SQLiteDatabase db = this.getReadableDatabase();
1166 String[] selectionArgs = {
1167 account.getUuid(),
1168 fingerprint
1169 };
1170 String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
1171 String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
1172 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
1173 if (cursor.getCount() < 1) {
1174 return null;
1175 } else {
1176 cursor.moveToFirst();
1177 byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
1178 cursor.close();
1179 if (certificate == null || certificate.length == 0) {
1180 return null;
1181 }
1182 try {
1183 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1184 return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
1185 } catch (CertificateException e) {
1186 Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
1187 return null;
1188 }
1189 }
1190 }
1191
1192 public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
1193 storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
1194 }
1195
1196 public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
1197 storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
1198 }
1199
1200
1201 public void recreateAxolotlDb(SQLiteDatabase db) {
1202 Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
1203 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
1204 db.execSQL(CREATE_SESSIONS_STATEMENT);
1205 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
1206 db.execSQL(CREATE_PREKEYS_STATEMENT);
1207 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
1208 db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
1209 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
1210 db.execSQL(CREATE_IDENTITIES_STATEMENT);
1211 }
1212
1213 public void wipeAxolotlDb(Account account) {
1214 String accountName = account.getUuid();
1215 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
1216 SQLiteDatabase db = this.getWritableDatabase();
1217 String[] deleteArgs = {
1218 accountName
1219 };
1220 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
1221 SQLiteAxolotlStore.ACCOUNT + " = ?",
1222 deleteArgs);
1223 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
1224 SQLiteAxolotlStore.ACCOUNT + " = ?",
1225 deleteArgs);
1226 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
1227 SQLiteAxolotlStore.ACCOUNT + " = ?",
1228 deleteArgs);
1229 db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1230 SQLiteAxolotlStore.ACCOUNT + " = ?",
1231 deleteArgs);
1232 }
1233
1234 public boolean startTimeCountExceedsThreshold() {
1235 SQLiteDatabase db = this.getWritableDatabase();
1236 long cleanBeforeTimestamp = System.currentTimeMillis() - Config.FREQUENT_RESTARTS_DETECTION_WINDOW;
1237 db.execSQL("delete from "+START_TIMES_TABLE+" where timestamp < "+cleanBeforeTimestamp);
1238 ContentValues values = new ContentValues();
1239 values.put("timestamp",System.currentTimeMillis());
1240 db.insert(START_TIMES_TABLE,null,values);
1241 String[] columns = new String[]{"count(timestamp)"};
1242 Cursor cursor = db.query(START_TIMES_TABLE,columns,null,null,null,null,null);
1243 int count;
1244 if (cursor.moveToFirst()) {
1245 count = cursor.getInt(0);
1246 } else {
1247 count = 0;
1248 }
1249 cursor.close();
1250 return count >= Config.FREQUENT_RESTARTS_THRESHOLD;
1251 }
1252
1253 public void clearStartTimeCounter() {
1254 SQLiteDatabase db = this.getWritableDatabase();
1255 db.execSQL("delete from "+START_TIMES_TABLE);
1256 }
1257}