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